Initial commit"

This commit is contained in:
ikpil 2023-03-14 14:02:43 +09:00
parent b198ed49b4
commit 19af708461
350 changed files with 78647 additions and 1 deletions

399
.gitignore vendored Normal file
View File

@ -0,0 +1,399 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
#*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
.idea/

120
DotRecast.sln Normal file
View File

@ -0,0 +1,120 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8ED75CF7-A3D6-423D-8499-9316DD413DAD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Core", "src\DotRecast.Core\DotRecast.Core.csproj", "{C19E4BFA-63A0-4815-9815-869A9DC52DBC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Recast", "src\DotRecast.Recast\DotRecast.Recast.csproj", "{38933A87-4568-40A5-A3DA-E2445E8C2B99}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour", "src\DotRecast.Detour\DotRecast.Detour.csproj", "{FFE40BBF-843B-41FA-8504-F4ABD166762E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.Crowd", "src\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj", "{FA7EF26A-BA47-43FD-86F8-0A33CFDF643F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.Dynamic", "src\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj", "{53AF87DA-37F8-4504-B623-B2113F4438CA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.Extras", "src\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj", "{17E4F2F0-FC27-416E-9CB6-9F2CAAC49C9D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.TileCache", "src\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj", "{DEB16B90-CCD4-497E-A2E9-4CC66FD7EF47}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A7CB8D8B-70DA-4567-8316-0659FCAE1C73}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Recast.Test", "test\DotRecast.Recast.Test\DotRecast.Recast.Test.csproj", "{88754FE2-A05A-4D4D-A81A-90418AD32362}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.Test", "test\DotRecast.Detour.Test\DotRecast.Detour.Test.csproj", "{554CB5BD-D58A-4856-BFE1-666A62C9BEA3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.Crowd.Test", "test\DotRecast.Detour.Crowd.Test\DotRecast.Detour.Crowd.Test.csproj", "{F9C5B52E-C01D-4514-94E9-B1A6895352E2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.Dynamic.Test", "test\DotRecast.Detour.Dynamic.Test\DotRecast.Detour.Dynamic.Test.csproj", "{67C68B34-118A-439C-88E1-D6D1ED78DC59}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Detour.Extras.Test", "test\DotRecast.Detour.Extras.Test\DotRecast.Detour.Extras.Test.csproj", "{7BAA69B2-EDC7-4603-B16F-BC7B24353F81}"
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotRecast.Recast.Demo", "src\DotRecast.Recast.Demo\DotRecast.Recast.Demo.csproj", "{023E1E6A-4895-4573-89AE-3D5D8E0B39C8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FFE40BBF-843B-41FA-8504-F4ABD166762E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFE40BBF-843B-41FA-8504-F4ABD166762E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFE40BBF-843B-41FA-8504-F4ABD166762E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFE40BBF-843B-41FA-8504-F4ABD166762E}.Release|Any CPU.Build.0 = Release|Any CPU
{38933A87-4568-40A5-A3DA-E2445E8C2B99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38933A87-4568-40A5-A3DA-E2445E8C2B99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38933A87-4568-40A5-A3DA-E2445E8C2B99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38933A87-4568-40A5-A3DA-E2445E8C2B99}.Release|Any CPU.Build.0 = Release|Any CPU
{C19E4BFA-63A0-4815-9815-869A9DC52DBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C19E4BFA-63A0-4815-9815-869A9DC52DBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C19E4BFA-63A0-4815-9815-869A9DC52DBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C19E4BFA-63A0-4815-9815-869A9DC52DBC}.Release|Any CPU.Build.0 = Release|Any CPU
{88754FE2-A05A-4D4D-A81A-90418AD32362}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88754FE2-A05A-4D4D-A81A-90418AD32362}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88754FE2-A05A-4D4D-A81A-90418AD32362}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88754FE2-A05A-4D4D-A81A-90418AD32362}.Release|Any CPU.Build.0 = Release|Any CPU
{554CB5BD-D58A-4856-BFE1-666A62C9BEA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{554CB5BD-D58A-4856-BFE1-666A62C9BEA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{554CB5BD-D58A-4856-BFE1-666A62C9BEA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{554CB5BD-D58A-4856-BFE1-666A62C9BEA3}.Release|Any CPU.Build.0 = Release|Any CPU
{FA7EF26A-BA47-43FD-86F8-0A33CFDF643F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA7EF26A-BA47-43FD-86F8-0A33CFDF643F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA7EF26A-BA47-43FD-86F8-0A33CFDF643F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA7EF26A-BA47-43FD-86F8-0A33CFDF643F}.Release|Any CPU.Build.0 = Release|Any CPU
{F9C5B52E-C01D-4514-94E9-B1A6895352E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9C5B52E-C01D-4514-94E9-B1A6895352E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9C5B52E-C01D-4514-94E9-B1A6895352E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9C5B52E-C01D-4514-94E9-B1A6895352E2}.Release|Any CPU.Build.0 = Release|Any CPU
{53AF87DA-37F8-4504-B623-B2113F4438CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53AF87DA-37F8-4504-B623-B2113F4438CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53AF87DA-37F8-4504-B623-B2113F4438CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53AF87DA-37F8-4504-B623-B2113F4438CA}.Release|Any CPU.Build.0 = Release|Any CPU
{67C68B34-118A-439C-88E1-D6D1ED78DC59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67C68B34-118A-439C-88E1-D6D1ED78DC59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67C68B34-118A-439C-88E1-D6D1ED78DC59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67C68B34-118A-439C-88E1-D6D1ED78DC59}.Release|Any CPU.Build.0 = Release|Any CPU
{17E4F2F0-FC27-416E-9CB6-9F2CAAC49C9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17E4F2F0-FC27-416E-9CB6-9F2CAAC49C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17E4F2F0-FC27-416E-9CB6-9F2CAAC49C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17E4F2F0-FC27-416E-9CB6-9F2CAAC49C9D}.Release|Any CPU.Build.0 = Release|Any CPU
{7BAA69B2-EDC7-4603-B16F-BC7B24353F81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7BAA69B2-EDC7-4603-B16F-BC7B24353F81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BAA69B2-EDC7-4603-B16F-BC7B24353F81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BAA69B2-EDC7-4603-B16F-BC7B24353F81}.Release|Any CPU.Build.0 = Release|Any CPU
{DEB16B90-CCD4-497E-A2E9-4CC66FD7EF47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEB16B90-CCD4-497E-A2E9-4CC66FD7EF47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEB16B90-CCD4-497E-A2E9-4CC66FD7EF47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEB16B90-CCD4-497E-A2E9-4CC66FD7EF47}.Release|Any CPU.Build.0 = Release|Any CPU
{3CAA7306-088E-4373-A406-99755CC2B605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3CAA7306-088E-4373-A406-99755CC2B605}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3CAA7306-088E-4373-A406-99755CC2B605}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3CAA7306-088E-4373-A406-99755CC2B605}.Release|Any CPU.Build.0 = Release|Any CPU
{023E1E6A-4895-4573-89AE-3D5D8E0B39C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{023E1E6A-4895-4573-89AE-3D5D8E0B39C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{023E1E6A-4895-4573-89AE-3D5D8E0B39C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{023E1E6A-4895-4573-89AE-3D5D8E0B39C8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FFE40BBF-843B-41FA-8504-F4ABD166762E} = {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}
{88754FE2-A05A-4D4D-A81A-90418AD32362} = {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}
{53AF87DA-37F8-4504-B623-B2113F4438CA} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{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}
{DEB16B90-CCD4-497E-A2E9-4CC66FD7EF47} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
{3CAA7306-088E-4373-A406-99755CC2B605} = {A7CB8D8B-70DA-4567-8316-0659FCAE1C73}
{023E1E6A-4895-4573-89AE-3D5D8E0B39C8} = {8ED75CF7-A3D6-423D-8499-9316DD413DAD}
EndGlobalSection
EndGlobal

View File

@ -1 +0,0 @@
DotRecast

1
README.md Normal file
View File

@ -0,0 +1 @@
# DotRecast

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

87
resources/bridge.obj Normal file
View File

@ -0,0 +1,87 @@
# Blender v2.83.5 OBJ File: ''
# www.blender.org
o Cube.001
v -2.087493 4.416752 -12.092585
v 2.181665 4.416749 10.462940
v 2.181665 4.416752 -12.092585
v -0.524636 -0.804926 20.192308
v 0.542654 -0.804926 20.192308
v -2.087493 3.565045 -0.287136
v -2.087493 4.416749 10.462940
v 2.181665 3.651016 9.952583
v 1.114376 3.651016 9.952583
v 2.181665 3.565045 -0.287136
v 0.047086 -0.780343 -20.766577
v -1.020204 -0.780343 -20.766577
v 0.047086 3.651016 9.952583
v -1.591925 -0.804926 20.192308
v -1.020204 3.651016 9.952583
v 0.047086 3.684258 -11.824307
v -1.020204 3.565045 -0.287136
v -1.020204 3.684258 -11.824307
v 2.181665 3.684258 -11.824307
v 1.114376 3.565045 -0.287136
v 1.114376 3.684258 -11.824307
v 0.047086 3.565045 -0.287136
v 1.114376 -0.780343 -20.766577
v 2.181665 -0.780343 -20.766577
v -2.087493 3.684258 -11.824307
v -2.087493 -0.780343 -20.766577
v -2.659215 -0.804926 20.192308
v -2.087493 3.651016 9.952583
v 1.609943 -0.804926 20.192308
s 1
f 1 2 3
f 4 5 2
f 1 6 7
f 8 5 9
f 2 10 3
f 11 12 1
f 13 14 15
f 16 17 18
f 19 20 21
f 21 22 16
f 23 16 11
f 24 21 23
f 18 6 25
f 12 25 26
f 11 18 12
f 15 27 28
f 17 28 6
f 22 15 17
f 9 4 13
f 20 13 22
f 10 9 20
f 1 7 2
f 5 29 2
f 2 7 4
f 7 27 14
f 14 4 7
f 28 27 7
f 1 26 25
f 25 6 1
f 6 28 7
f 8 29 5
f 19 24 3
f 2 29 8
f 8 10 2
f 10 19 3
f 12 26 1
f 1 3 11
f 3 24 23
f 23 11 3
f 13 4 14
f 16 22 17
f 19 10 20
f 21 20 22
f 23 21 16
f 24 19 21
f 18 17 6
f 12 18 25
f 11 16 18
f 15 14 27
f 17 15 28
f 22 13 15
f 9 5 4
f 20 9 13
f 10 8 9

408
resources/convex.obj Normal file
View File

@ -0,0 +1,408 @@
# Blender v2.83.5 OBJ File: 'convex.blend'
# www.blender.org
o Cube
v 1.305899 2.880516 2.691374
v 1.305899 2.499842 3.072048
v 0.381956 2.894057 3.085589
v 3.079623 2.430736 1.226648
v 2.701850 2.830478 1.234663
v 3.056046 2.798204 0.295109
v 2.915617 -3.144600 -0.158337
v 2.915617 -2.758400 -1.090707
v 2.529417 -3.144600 -1.090707
v 2.529417 -1.282239 -2.953068
v 2.915617 -1.282239 -2.566868
v 2.915617 -0.349869 -2.953068
v 1.053257 -3.144600 -2.566868
v 1.053257 -2.758400 -2.953068
v 0.120887 -3.144600 -2.953068
v 2.915617 -0.108101 -2.953068
v 2.932010 0.830836 -2.573903
v 2.529417 0.824269 -2.953068
v 2.618234 2.750781 -1.085477
v 2.995637 2.357680 -1.093115
v 3.036258 2.782483 -0.162588
v 1.053257 2.300430 -2.953068
v 1.061504 2.699598 -2.575518
v 0.120887 2.686630 -2.953068
v 0.120887 2.686630 -2.953068
v 1.061504 2.699598 -2.575518
v 1.053257 2.300430 -2.953068
v 3.036258 2.782483 -0.162588
v 2.996289 2.370983 -1.087093
v 2.618234 2.750781 -1.085477
v 2.529417 0.824269 -2.953068
v 2.932010 0.830836 -2.573903
v 2.915617 -0.108101 -2.953068
v 0.120887 -3.144600 -2.953068
v 1.053257 -2.758400 -2.953068
v 1.053257 -3.144600 -2.566868
v 2.915617 -0.349869 -2.953068
v 2.915617 -1.282239 -2.566868
v 2.529417 -1.282239 -2.953068
v 2.529417 -3.144600 -1.090707
v 2.915617 -2.758400 -1.090707
v 2.915617 -3.144600 -0.158337
v 3.056046 2.798204 0.295109
v 2.701850 2.830478 1.234663
v 3.079623 2.430736 1.226648
v 0.381956 2.894057 3.085589
v 1.305899 2.499842 3.072048
v 1.305899 2.880516 2.691374
v 3.079623 1.035117 2.622268
v 2.701850 1.043132 3.022010
v 3.056046 0.103578 2.989736
v 3.036258 -0.354120 2.974014
v 2.618234 -1.277009 2.942313
v 2.996289 -1.278624 2.562515
v 0.120887 -3.144600 2.878162
v 1.053257 -3.144600 2.491961
v 1.061504 -2.767050 2.891130
v 2.932010 -2.765435 1.022368
v 2.529417 -3.144600 1.015801
v 2.915617 -3.144600 0.083431
v -2.915612 -0.108101 -2.953068
v -2.932004 0.830836 -2.573903
v -2.529412 0.824269 -2.953068
v -3.036253 2.782483 -0.162588
v -2.618229 2.750781 -1.085477
v -2.996284 2.370983 -1.087093
v -1.053251 2.300430 -2.953068
v -1.061499 2.699598 -2.575518
v -0.120881 2.686630 -2.953068
v -2.915612 -0.349869 -2.953068
v -2.529412 -1.282239 -2.953068
v -2.915612 -1.282239 -2.566868
v -0.120881 -3.144600 -2.953068
v -1.053251 -3.144600 -2.566868
v -1.053251 -2.758400 -2.953068
v -2.915612 -2.758400 -1.090707
v -2.529412 -3.144600 -1.090707
v -2.915612 -3.144600 -0.158337
v -3.056041 0.103578 2.989736
v -2.701844 1.043132 3.022010
v -3.079617 1.035117 2.622268
v -0.381951 2.894057 3.085589
v -1.305893 2.880516 2.691374
v -1.305893 2.499842 3.072048
v -3.079617 2.430736 1.226648
v -2.701844 2.830478 1.234663
v -3.056041 2.798204 0.295109
v -0.120881 -3.144600 2.878162
v -1.061499 -2.767050 2.891130
v -1.053251 -3.144600 2.491961
v -3.036253 -0.354120 2.974014
v -2.996284 -1.278624 2.562515
v -2.618229 -1.277009 2.942313
v -2.529412 -3.144600 1.015801
v -2.932004 -2.765435 1.022368
v -2.915612 -3.144600 0.083431
v 3.079623 1.035117 2.622268
v 2.701850 1.043132 3.022010
v 3.056046 0.103578 2.989736
v 3.036258 -0.354120 2.974014
v 2.618234 -1.277009 2.942313
v 2.996289 -1.278624 2.562515
v 0.120887 -3.144600 2.878162
v 1.053257 -3.144600 2.491961
v 1.061504 -2.767050 2.891130
v 2.932010 -2.765435 1.022368
v 2.529417 -3.144600 1.015801
v 2.915617 -3.144600 0.083431
v -2.915612 -0.108101 -2.953068
v -2.932004 0.830836 -2.573903
v -2.529412 0.824269 -2.953068
v -3.036253 2.782483 -0.162588
v -2.618229 2.750781 -1.085477
v -2.996284 2.370983 -1.087093
v -1.053251 2.300430 -2.953068
v -1.061499 2.699598 -2.575518
v -0.120881 2.686630 -2.953068
v -2.915612 -0.349869 -2.953068
v -2.529412 -1.282239 -2.953068
v -2.915612 -1.282239 -2.566868
v -0.120881 -3.144600 -2.953068
v -1.053251 -3.144600 -2.566868
v -1.053251 -2.758400 -2.953068
v -2.915612 -2.758400 -1.090707
v -2.529412 -3.144600 -1.090707
v -2.915612 -3.144600 -0.158337
v -3.056041 0.103578 2.989736
v -2.701844 1.043132 3.022010
v -3.079617 1.035117 2.622268
v -0.381951 2.894057 3.085589
v -1.305893 2.880516 2.691374
v -1.305893 2.499842 3.072048
v -3.079617 2.430736 1.226648
v -2.701844 2.830478 1.234663
v -3.056041 2.798204 0.295109
v -0.120881 -3.144600 2.878162
v -1.061499 -2.767050 2.891130
v -1.053251 -3.144600 2.491961
v -3.036253 -0.354120 2.974014
v -2.996284 -1.278624 2.562515
v -2.618229 -1.277009 2.942313
v -2.529412 -3.144600 1.015801
v -2.932004 -2.765435 1.022368
v -2.915612 -3.144600 0.083431
v 0.591196 2.693114 -2.764293
v 2.827246 2.766632 -0.624033
v 2.878948 2.814341 0.764886
v 0.843927 2.887286 2.888481
v -2.827241 2.766632 -0.624033
v -0.591190 2.693114 -2.764293
v -0.843922 2.887286 2.888481
v -2.878943 2.814341 0.764886
v 0.000003 2.686630 -2.953068
v 1.839869 2.725190 -1.830498
v 3.046152 2.790344 0.066260
v -1.839864 2.725190 -1.830498
v -3.046147 2.790344 0.066260
v -2.003869 2.855497 1.963019
v 0.000003 2.894057 3.085589
v 2.003874 2.855497 1.963019
s off
f 50 49 160
f 74 77 76
f 58 57 59
f 41 32 58
f 35 38 40
f 156 160 154
f 36 94 77
f 53 84 93
f 156 68 67
f 76 95 62
f 158 86 85
f 95 94 89
f 32 31 154
f 145 27 25
f 29 146 28
f 31 32 33
f 34 35 36
f 37 38 39
f 40 41 42
f 147 45 43
f 47 148 46
f 49 50 51
f 52 53 54
f 55 56 57
f 58 59 60
f 61 62 63
f 149 66 64
f 67 150 69
f 70 71 72
f 73 74 75
f 76 77 78
f 79 80 81
f 151 84 82
f 85 152 87
f 88 89 90
f 91 92 93
f 94 95 96
f 35 75 31
f 49 45 160
f 45 44 160
f 160 48 47
f 47 50 160
f 76 72 71
f 71 75 76
f 75 74 76
f 58 54 53
f 53 57 58
f 57 56 59
f 60 42 41
f 41 38 32
f 38 37 33
f 32 38 33
f 32 29 54
f 29 28 155
f 155 43 45
f 29 155 45
f 58 60 41
f 52 54 49
f 54 58 32
f 49 54 29
f 49 51 52
f 45 49 29
f 40 36 35
f 35 39 38
f 38 41 40
f 155 28 146
f 146 30 154
f 154 26 145
f 145 25 153
f 153 69 150
f 150 68 156
f 156 65 149
f 149 64 157
f 157 87 152
f 152 86 158
f 158 83 151
f 151 82 159
f 159 46 148
f 148 48 160
f 160 44 147
f 147 43 155
f 155 146 147
f 146 154 160
f 154 145 150
f 145 153 150
f 150 156 154
f 156 149 158
f 149 157 152
f 158 149 152
f 158 151 160
f 151 159 148
f 160 151 148
f 160 147 146
f 156 158 160
f 96 78 77
f 77 74 36
f 74 73 36
f 73 34 36
f 36 40 90
f 40 42 59
f 90 40 59
f 42 60 59
f 59 56 90
f 56 55 90
f 55 88 90
f 90 94 36
f 94 96 77
f 88 55 57
f 57 53 93
f 53 52 50
f 52 51 50
f 50 47 53
f 47 46 159
f 159 82 84
f 47 159 84
f 84 53 47
f 89 88 57
f 91 93 80
f 93 89 57
f 80 93 84
f 80 79 91
f 67 63 156
f 63 62 156
f 62 66 156
f 66 65 156
f 78 96 76
f 96 95 76
f 95 92 66
f 92 91 81
f 91 79 81
f 81 85 66
f 85 87 157
f 157 64 66
f 85 157 66
f 92 81 66
f 70 72 61
f 72 76 62
f 62 61 72
f 66 62 95
f 85 81 158
f 81 80 158
f 80 84 158
f 84 83 158
f 94 90 89
f 89 93 95
f 93 92 95
f 31 27 154
f 27 26 154
f 154 30 29
f 32 154 29
f 145 26 27
f 29 30 146
f 147 44 45
f 47 48 148
f 149 65 66
f 67 68 150
f 151 83 84
f 85 86 152
f 34 73 75
f 75 71 63
f 71 70 63
f 70 61 63
f 63 67 27
f 67 69 153
f 153 25 27
f 67 153 27
f 35 34 75
f 37 39 31
f 39 35 31
f 31 33 37
f 27 31 75
f 75 63 27
l 60 108
l 59 107
l 57 105
l 54 102
l 53 101
l 51 99
l 49 97
l 48 1
l 47 2
l 44 5
l 43 6
l 42 7
l 40 9
l 39 10
l 37 12
l 35 14
l 34 15
l 33 16
l 31 18
l 30 19
l 28 21
l 27 22
l 26 23
l 25 24
l 52 100
l 38 11
l 58 106
l 55 103
l 50 98
l 56 104
l 45 4
l 36 13
l 46 3
l 32 17
l 41 8
l 29 20
l 61 109
l 62 110
l 63 111
l 64 112
l 65 113
l 66 114
l 67 115
l 68 116
l 69 117
l 70 118
l 71 119
l 72 120
l 73 121
l 74 122
l 75 123
l 76 124
l 77 125
l 78 126
l 79 127
l 80 128
l 81 129
l 82 130
l 83 131
l 84 132
l 85 133
l 86 134
l 87 135
l 88 136
l 89 137
l 90 138
l 91 139
l 92 140
l 93 141
l 94 142
l 95 143
l 96 144

15234
resources/dungeon.obj Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/graph.zip Normal file

Binary file not shown.

BIN
resources/graph_v4_1_16.zip Normal file

Binary file not shown.

7321
resources/house.obj Normal file

File diff suppressed because it is too large Load Diff

3506
resources/nav_test.obj Normal file

File diff suppressed because it is too large Load Diff

BIN
resources/test.voxels Normal file

Binary file not shown.

Binary file not shown.

BIN
resources/test_tiles.voxels Normal file

Binary file not shown.

View File

@ -0,0 +1,41 @@
using System;
namespace DotRecast.Core;
public static class ArrayUtils
{
public static T[] CopyOf<T>(T[] source, int startIdx, int length)
{
var deatArr = new T[length];
for (int i = 0; i < length; ++i)
{
deatArr[i] = source[startIdx + i];
}
return deatArr;
}
public static T[] CopyOf<T>(T[] source, int length)
{
var deatArr = new T[length];
var count = Math.Max(0, Math.Min(source.Length, length));
for (int i = 0; i < count; ++i)
{
deatArr[i] = source[i];
}
return deatArr;
}
public static T[][] Of<T>(int len1, int len2)
{
var temp = new T[len1][];
for (int i = 0; i < len1; ++i)
{
temp[i] = new T[len2];
}
return temp;
}
}

View File

@ -0,0 +1,18 @@
using System.Threading;
namespace DotRecast.Core;
public class AtomicBoolean
{
private volatile int _location;
public bool set(bool v)
{
return 0 != Interlocked.Exchange(ref _location, v ? 1 : 0);
}
public bool get()
{
return 0 != _location;
}
}

View File

@ -0,0 +1,28 @@
using System.Threading;
namespace DotRecast.Core;
public class AtomicFloat
{
private volatile float _location;
public AtomicFloat(float location)
{
_location = location;
}
public float Get()
{
return _location;
}
public float Exchange(float exchange)
{
return Interlocked.Exchange(ref _location, exchange);
}
public float CompareExchange(float value, float comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
}
}

View File

@ -0,0 +1,66 @@
using System.Threading;
namespace DotRecast.Core;
public class AtomicInteger
{
private volatile int _location;
public AtomicInteger() : this(0)
{
}
public AtomicInteger(int location)
{
_location = location;
}
public int IncrementAndGet()
{
return Interlocked.Increment(ref _location);
}
public int GetAndIncrement()
{
var next = Interlocked.Increment(ref _location);
return next - 1;
}
public int DecrementAndGet()
{
return Interlocked.Decrement(ref _location);
}
public int Read()
{
return _location;
}
public int GetSoft()
{
return _location;
}
public int Exchange(int exchange)
{
return Interlocked.Exchange(ref _location, exchange);
}
public int Decrease(int value)
{
return Interlocked.Add(ref _location, -value);
}
public int CompareExchange(int value, int comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
}
public int Add(int value)
{
return Interlocked.Add(ref _location, value);
}
}

View File

@ -0,0 +1,52 @@
using System.Threading;
namespace DotRecast.Core;
public class AtomicLong
{
private long _location;
public AtomicLong() : this(0)
{
}
public AtomicLong(long location)
{
_location = location;
}
public long IncrementAndGet()
{
return Interlocked.Increment(ref _location);
}
public long DecrementAndGet()
{
return Interlocked.Decrement(ref _location);
}
public long Read()
{
return Interlocked.Read(ref _location);
}
public long Exchange(long exchange)
{
return Interlocked.Exchange(ref _location, exchange);
}
public long Decrease(long value)
{
return Interlocked.Add(ref _location, -value);
}
public long CompareExchange(long value, long comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
}
public long AddAndGet(long value)
{
return Interlocked.Add(ref _location, value);
}
}

View File

@ -0,0 +1,128 @@
using System;
using System.Buffers.Binary;
namespace DotRecast.Core;
public class ByteBuffer
{
private ByteOrder _order;
private byte[] _bytes;
private int _position;
public ByteBuffer(byte[] bytes)
{
_order = BitConverter.IsLittleEndian
? ByteOrder.LITTLE_ENDIAN
: ByteOrder.BIG_ENDIAN;
_bytes = bytes;
_position = 0;
}
public ByteOrder order()
{
return _order;
}
public void order(ByteOrder order)
{
_order = order;
}
public int limit() {
return _bytes.Length - _position;
}
public int remaining() {
int rem = limit();
return rem > 0 ? rem : 0;
}
public void position(int pos)
{
_position = pos;
}
public int position()
{
return _position;
}
public Span<byte> ReadBytes(int length)
{
var nextPos = _position + length;
(nextPos, _position) = (_position, nextPos);
return _bytes.AsSpan(nextPos, length);
}
public byte get()
{
var span = ReadBytes(1);
return span[0];
}
public short getShort()
{
var span = ReadBytes(2);
if (_order == ByteOrder.BIG_ENDIAN)
{
return BinaryPrimitives.ReadInt16BigEndian(span);
}
else
{
return BinaryPrimitives.ReadInt16LittleEndian(span);
}
}
public int getInt()
{
var span = ReadBytes(4);
if (_order == ByteOrder.BIG_ENDIAN)
{
return BinaryPrimitives.ReadInt32BigEndian(span);
}
else
{
return BinaryPrimitives.ReadInt32LittleEndian(span);
}
}
public float getFloat()
{
var span = ReadBytes(4);
if (_order == ByteOrder.BIG_ENDIAN)
{
return BinaryPrimitives.ReadSingleBigEndian(span);
}
else
{
return BinaryPrimitives.ReadSingleLittleEndian(span);
}
}
public long getLong()
{
var span = ReadBytes(8);
if (_order == ByteOrder.BIG_ENDIAN)
{
return BinaryPrimitives.ReadInt64BigEndian(span);
}
else
{
return BinaryPrimitives.ReadInt64LittleEndian(span);
}
}
public void putFloat(float v)
{
// ?
}
public void putInt(int v)
{
// ?
}
}

View File

@ -0,0 +1,8 @@
namespace DotRecast.Core;
public enum ByteOrder
{
/// <summary>Default on most Windows systems</summary>
LITTLE_ENDIAN,
BIG_ENDIAN,
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace DotRecast.Core;
public static class CollectionExtensions
{
public static void forEach<T>(this IEnumerable<T> collection, Action<T> action)
{
foreach (var item in collection)
{
action.Invoke(item);
}
}
public static void Shuffle<T>(this IList<T> list)
{
Random random = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = random.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}

View File

@ -0,0 +1,85 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
namespace DotRecast.Core;
public static class ConvexUtils {
// Calculates convex hull on xz-plane of points on 'pts',
// stores the indices of the resulting hull in 'out' and
// returns number of points on hull.
public static List<int> convexhull(List<float> pts) {
int npts = pts.Count / 3;
List<int> @out = new();
// Find lower-leftmost point.
int hull = 0;
for (int i = 1; i < npts; ++i) {
float[] a = new float[] { pts[i * 3], pts[i * 3 + 1], pts[i * 3 + 2] };
float[] b = new float[] { pts[hull * 3], pts[hull * 3 + 1], pts[hull * 3 + 2] };
if (cmppt(a, b)) {
hull = i;
}
}
// Gift wrap hull.
int endpt = 0;
do {
@out.Add(hull);
endpt = 0;
for (int j = 1; j < npts; ++j) {
float[] a = new float[] { pts[hull * 3], pts[hull * 3 + 1], pts[hull * 3 + 2] };
float[] b = new float[] { pts[endpt * 3], pts[endpt * 3 + 1], pts[endpt * 3 + 2] };
float[] c = new float[] { pts[j * 3], pts[j * 3 + 1], pts[j * 3 + 2] };
if (hull == endpt || left(a, b, c)) {
endpt = j;
}
}
hull = endpt;
} while (endpt != @out[0]);
return @out;
}
// Returns true if 'a' is more lower-left than 'b'.
private static bool cmppt(float[] a, float[] b) {
if (a[0] < b[0]) {
return true;
}
if (a[0] > b[0]) {
return false;
}
if (a[2] < b[2]) {
return true;
}
if (a[2] > b[2]) {
return false;
}
return false;
}
// Returns true if 'c' is left of line 'a'-'b'.
private static bool left(float[] a, float[] b, float[] c) {
float u1 = b[0] - a[0];
float v1 = b[2] - a[2];
float u2 = c[0] - a[0];
float v2 = c[2] - a[2];
return u1 * v2 - v1 * u2 < 0;
}
}

View File

@ -0,0 +1,78 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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;
namespace DotRecast.Core;
public class DemoMath {
public static float vDistSqr(float[] v1, float[] v2, int i) {
float dx = v2[i] - v1[0];
float dy = v2[i + 1] - v1[1];
float dz = v2[i + 2] - v1[2];
return dx * dx + dy * dy + dz * dz;
}
public static float[] vCross(float[] v1, float[] v2) {
float[] dest = new float[3];
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
return dest;
}
public static float vDot(float[] v1, float[] v2) {
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
public static float sqr(float f) {
return f * f;
}
public static float getPathLen(float[] path, int npath) {
float totd = 0;
for (int i = 0; i < npath - 1; ++i) {
totd += (float)Math.Sqrt(vDistSqr(path, i * 3, (i + 1) * 3));
}
return totd;
}
public static float vDistSqr(float[] v, int i, int j) {
float dx = v[i] - v[j];
float dy = v[i + 1] - v[j + 1];
float dz = v[i + 2] - v[j + 2];
return dx * dx + dy * dy + dz * dz;
}
public static float step(float threshold, float v) {
return v < threshold ? 0.0f : 1.0f;
}
public static int clamp(int v, int min, int max) {
return Math.Max(Math.Min(v, max), min);
}
public static float clamp(float v, float min, float max) {
return Math.Max(Math.Min(v, max), min);
}
public static float lerp(float f, float g, float u) {
return u * g + (1f - u) * f;
}
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,32 @@
using System.IO;
namespace DotRecast.Core;
public static class Loader
{
public static byte[] ToBytes(string filename)
{
var filepath = ToRPath(filename);
using var fs = new FileStream(filepath, FileMode.Open);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
return buffer;
}
public static string ToRPath(string filename)
{
string filePath = Path.Combine("resources", filename);
for (int i = 0; i < 10; ++i)
{
if (File.Exists(filePath))
{
return Path.GetFullPath(filePath);
}
filePath = Path.Combine("..", filePath);
}
return filename;
}
}

View File

@ -0,0 +1,72 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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;
namespace DotRecast.Core;
using System.Collections.Generic;
public class OrderedQueue<T>
{
private readonly List<T> _items;
private readonly Comparison<T> _comparison;
public OrderedQueue(Comparison<T> comparison)
{
_items = new();
_comparison = comparison;
}
public int count()
{
return _items.Count;
}
public void clear() {
_items.Clear();
}
public T top()
{
return _items[0];
}
public T Dequeue()
{
var node = top();
_items.Remove(node);
return node;
}
public void Enqueue(T item) {
_items.Add(item);
_items.Sort(_comparison);
}
public void Remove(T item) {
_items.Remove(item);
}
public bool isEmpty()
{
return 0 == _items.Count;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,191 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour.Crowd;
using static DetourCommon;
/// Represents an agent managed by a #dtCrowd object.
/// @ingroup crowd
public class CrowdAgent {
/// The type of navigation mesh polygon the agent is currently traversing.
/// @ingroup crowd
public enum CrowdAgentState {
DT_CROWDAGENT_STATE_INVALID, /// < The agent is not in a valid state.
DT_CROWDAGENT_STATE_WALKING, /// < The agent is traversing a normal navigation mesh polygon.
DT_CROWDAGENT_STATE_OFFMESH, /// < The agent is traversing an off-mesh connection.
};
public enum MoveRequestState {
DT_CROWDAGENT_TARGET_NONE,
DT_CROWDAGENT_TARGET_FAILED,
DT_CROWDAGENT_TARGET_VALID,
DT_CROWDAGENT_TARGET_REQUESTING,
DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE,
DT_CROWDAGENT_TARGET_WAITING_FOR_PATH,
DT_CROWDAGENT_TARGET_VELOCITY,
};
public readonly long idx;
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
public CrowdAgentState 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.
public bool partial;
/// The path corridor the agent is using.
public PathCorridor corridor;
/// The local boundary data for the agent.
public LocalBoundary boundary;
/// Time since the agent's path corridor was optimized.
public float topologyOptTime;
/// The known neighbors of the agent.
public List<Crowd.CrowdNeighbour> neis = new();
/// The desired speed.
public float desiredSpeed;
public float[] npos = new float[3]; /// < The current agent position. [(x, y, z)]
public float[] disp = new float[3]; /// < A temporary value used to accumulate agent displacement during iterative
/// collision resolution. [(x, y, z)]
public float[] dvel = new float[3]; /// < The desired velocity of the agent. Based on the current path, calculated
/// from
/// scratch each frame. [(x, y, z)]
public float[] nvel = new float[3]; /// < The desired velocity adjusted by obstacle avoidance, calculated from scratch each
/// frame. [(x, y, z)]
public float[] vel = new float[3]; /// < The actual velocity of the agent. The change from nvel -> vel is
/// constrained by max acceleration. [(x, y, z)]
/// The agent's configuration parameters.
public CrowdAgentParams option;
/// The local path corridor corners for the agent.
public List<StraightPathItem> corners = new();
public MoveRequestState targetState; /// < State of the movement request.
public long targetRef; /// < Target polyref of the movement request.
public float[] targetPos = new float[3]; /// < Target position of the movement request (or velocity in case of
/// DT_CROWDAGENT_TARGET_VELOCITY).
public PathQueryResult 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 CrowdAgentAnimation animation;
public CrowdAgent(int idx) {
this.idx = idx;
corridor = new PathCorridor();
boundary = new LocalBoundary();
animation = new CrowdAgentAnimation();
}
public void integrate(float dt) {
// Fake dynamic constraint.
float maxDelta = option.maxAcceleration * dt;
float[] dv = vSub(nvel, vel);
float ds = vLen(dv);
if (ds > maxDelta)
dv = vScale(dv, maxDelta / ds);
vel = vAdd(vel, dv);
// Integrate
if (vLen(vel) > 0.0001f)
npos = vMad(npos, vel, dt);
else
vSet(vel, 0, 0, 0);
}
public bool overOffmeshConnection(float radius) {
if (0 == corners.Count)
return false;
bool offMeshConnection = ((corners[corners.Count - 1].getFlags()
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) ? true : false;
if (offMeshConnection) {
float distSq = vDist2DSqr(npos, corners[corners.Count - 1].getPos());
if (distSq < radius * radius)
return true;
}
return false;
}
public float getDistanceToGoal(float range) {
if (0 == corners.Count)
return range;
bool endOfPath = ((corners[corners.Count - 1].getFlags() & NavMeshQuery.DT_STRAIGHTPATH_END) != 0) ? true : false;
if (endOfPath)
return Math.Min(vDist2D(npos, corners[corners.Count - 1].getPos()), range);
return range;
}
public float[] calcSmoothSteerDirection() {
float[] dir = new float[3];
if (0 < corners.Count) {
int ip0 = 0;
int ip1 = Math.Min(1, corners.Count - 1);
float[] p0 = corners[ip0].getPos();
float[] p1 = corners[ip1].getPos();
float[] dir0 = vSub(p0, npos);
float[] dir1 = vSub(p1, npos);
dir0[1] = 0;
dir1[1] = 0;
float len0 = vLen(dir0);
float len1 = vLen(dir1);
if (len1 > 0.001f)
dir1 = vScale(dir1, 1.0f / len1);
dir[0] = dir0[0] - dir1[0] * len0 * 0.5f;
dir[1] = 0;
dir[2] = dir0[2] - dir1[2] * len0 * 0.5f;
vNormalize(dir);
}
return dir;
}
public float[] calcStraightSteerDirection() {
float[] dir = new float[3];
if (0 < corners.Count) {
dir = vSub(corners[0].getPos(), npos);
dir[1] = 0;
vNormalize(dir);
}
return dir;
}
public void setTarget(long refs, float[] pos) {
targetRef = refs;
vCopy(targetPos, pos);
targetPathQueryResult = null;
if (targetRef != 0) {
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING;
} else {
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
}
}
}

View File

@ -0,0 +1,29 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Crowd;
public class CrowdAgentAnimation {
public bool active;
public float[] initPos = new float[3];
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public long polyRef;
public float t, tmax;
}

View File

@ -0,0 +1,61 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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;
namespace DotRecast.Detour.Crowd;
/// Configuration parameters for a crowd agent.
/// @ingroup crowd
public class CrowdAgentParams {
public float radius; /// < Agent radius. [Limit: >= 0]
public float height; /// < Agent height. [Limit: > 0]
public float maxAcceleration; /// < Maximum allowed acceleration. [Limit: >= 0]
public float maxSpeed; /// < Maximum allowed speed. [Limit: >= 0]
/// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
public float collisionQueryRange;
public float pathOptimizationRange; /// < The path visibility optimization range. [Limit: > 0]
/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
public float separationWeight;
/// Crowd agent update flags.
public const int DT_CROWD_ANTICIPATE_TURNS = 1;
public const int DT_CROWD_OBSTACLE_AVOIDANCE = 2;
public const int DT_CROWD_SEPARATION = 4;
public const int DT_CROWD_OPTIMIZE_VIS = 8; /// < Use #dtPathCorridor::optimizePathVisibility() to optimize
/// the agent path.
public const int DT_CROWD_OPTIMIZE_TOPO = 16; /// < Use dtPathCorridor::optimizePathTopology() to optimize
/// the agent path.
/// Flags that impact steering behavior. (See: #UpdateFlags)
public int updateFlags;
/// The index of the avoidance configuration to use for the agent.
/// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
public int obstacleAvoidanceType;
/// The index of the query filter used by this agent.
public int queryFilterType;
/// User defined data attached to the agent.
public object userData;
}

View File

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

View File

@ -0,0 +1,79 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace DotRecast.Detour.Crowd;
public class CrowdTelemetry {
public const int TIMING_SAMPLES = 10;
private float _maxTimeToEnqueueRequest;
private float _maxTimeToFindPath;
private readonly Dictionary<string, long> _executionTimings = new();
private readonly Dictionary<string, List<long>> _executionTimingSamples = new();
public float maxTimeToEnqueueRequest() {
return _maxTimeToEnqueueRequest;
}
public float maxTimeToFindPath() {
return _maxTimeToFindPath;
}
public Dictionary<string, long> executionTimings() {
return _executionTimings;
}
public void start() {
_maxTimeToEnqueueRequest = 0;
_maxTimeToFindPath = 0;
_executionTimings.Clear();
}
public void recordMaxTimeToEnqueueRequest(float time) {
_maxTimeToEnqueueRequest = Math.Max(_maxTimeToEnqueueRequest, time);
}
public void recordMaxTimeToFindPath(float time) {
_maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time);
}
public void start(string name) {
_executionTimings.Add(name, Stopwatch.GetTimestamp());
}
public void stop(string name) {
long duration = Stopwatch.GetTimestamp() - _executionTimings[name];
if (!_executionTimingSamples.TryGetValue(name, out var s))
{
s = new();
_executionTimingSamples.Add(name, s);
}
if (s.Count == TIMING_SAMPLES) {
s.RemoveAt(0);
}
s.Add(duration);
_executionTimings[name] = (long) s.Average();
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,136 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour.Crowd;
using static DetourCommon;
public class LocalBoundary {
public const int MAX_LOCAL_SEGS = 8;
private class Segment {
/** Segment start/end */
public float[] s = new float[6];
/** Distance for pruning. */
public float d;
}
float[] m_center = new float[3];
List<Segment> m_segs = new();
List<long> m_polys = new();
public LocalBoundary() {
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
}
public void reset() {
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
m_polys.Clear();
m_segs.Clear();
}
protected void addSegment(float dist, float[] s) {
// Insert neighbour based on the distance.
Segment seg = new Segment();
Array.Copy(s, seg.s, 6);
seg.d = dist;
if (0 == m_segs.Count) {
m_segs.Add(seg);
} else if (dist >= m_segs[m_segs.Count - 1].d) {
if (m_segs.Count >= MAX_LOCAL_SEGS) {
return;
}
m_segs.Add(seg);
} else {
// Insert inbetween.
int i;
for (i = 0; i < m_segs.Count; ++i) {
if (dist <= m_segs[i].d) {
break;
}
}
m_segs.Insert(i, seg);
}
while (m_segs.Count > MAX_LOCAL_SEGS) {
m_segs.RemoveAt(m_segs.Count - 1);
}
}
public void update(long refs, float[] pos, float collisionQueryRange, NavMeshQuery navquery, QueryFilter filter) {
if (refs == 0) {
reset();
return;
}
vCopy(m_center, pos);
// First query non-overlapping polygons.
Result<FindLocalNeighbourhoodResult> res = navquery.findLocalNeighbourhood(refs, pos, collisionQueryRange,
filter);
if (res.succeeded()) {
m_polys = res.result.getRefs();
m_segs.Clear();
// Secondly, store all polygon edges.
for (int j = 0; j < m_polys.Count; ++j) {
Result<GetPolyWallSegmentsResult> result = navquery.getPolyWallSegments(m_polys[j], false, filter);
if (result.succeeded()) {
GetPolyWallSegmentsResult gpws = result.result;
for (int k = 0; k < gpws.getSegmentRefs().Count; ++k) {
float[] s = gpws.getSegmentVerts()[k];
// Skip too distant segments.
Tuple<float, float> distseg = distancePtSegSqr2D(pos, s, 0, 3);
if (distseg.Item1 > sqr(collisionQueryRange)) {
continue;
}
addSegment(distseg.Item1, s);
}
}
}
}
}
public bool isValid(NavMeshQuery navquery, QueryFilter filter) {
if (m_polys.Count == 0) {
return false;
}
// Check that all polygons still pass query filter.
foreach (long refs in m_polys) {
if (!navquery.isValidPolyRef(refs, filter)) {
return false;
}
}
return true;
}
public float[] getCenter() {
return m_center;
}
public float[] getSegment(int j) {
return m_segs[j].s;
}
public int getSegmentCount() {
return m_segs.Count;
}
}

View File

@ -0,0 +1,508 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 DotRecast.Detour.Crowd.Tracking;
namespace DotRecast.Detour.Crowd;
using static DetourCommon;
public class ObstacleAvoidanceQuery {
public const int DT_MAX_PATTERN_DIVS = 32; /// < Max numver of adaptive divs.
public const int DT_MAX_PATTERN_RINGS = 4; /// < Max number of adaptive rings.
public class ObstacleCircle {
/** Position of the obstacle */
public readonly float[] p = new float[3];
/** Velocity of the obstacle */
public readonly float[] vel = new float[3];
/** Velocity of the obstacle */
public readonly float[] dvel = new float[3];
/** Radius of the obstacle */
public float rad;
/** Use for side selection during sampling. */
public readonly float[] dp = new float[3];
/** Use for side selection during sampling. */
public readonly float[] np = new float[3];
}
public class ObstacleSegment {
/** End points of the obstacle segment */
public readonly float[] p = new float[3];
/** End points of the obstacle segment */
public readonly float[] q = new float[3];
public bool touch;
}
public class ObstacleAvoidanceParams {
public float velBias;
public float weightDesVel;
public float weightCurVel;
public float weightSide;
public float weightToi;
public float horizTime;
public int gridSize; /// < grid
public int adaptiveDivs; /// < adaptive
public int adaptiveRings; /// < adaptive
public int adaptiveDepth; /// < adaptive
public ObstacleAvoidanceParams() {
velBias = 0.4f;
weightDesVel = 2.0f;
weightCurVel = 0.75f;
weightSide = 0.75f;
weightToi = 2.5f;
horizTime = 2.5f;
gridSize = 33;
adaptiveDivs = 7;
adaptiveRings = 2;
adaptiveDepth = 5;
}
public ObstacleAvoidanceParams(ObstacleAvoidanceParams option) {
velBias = option.velBias;
weightDesVel = option.weightDesVel;
weightCurVel = option.weightCurVel;
weightSide = option.weightSide;
weightToi = option.weightToi;
horizTime = option.horizTime;
gridSize = option.gridSize;
adaptiveDivs = option.adaptiveDivs;
adaptiveRings = option.adaptiveRings;
adaptiveDepth = option.adaptiveDepth;
}
};
private ObstacleAvoidanceParams m_params;
private float m_invHorizTime;
private float m_vmax;
private float m_invVmax;
private readonly int m_maxCircles;
private readonly ObstacleCircle[] m_circles;
private int m_ncircles;
private readonly int m_maxSegments;
private readonly ObstacleSegment[] m_segments;
private int m_nsegments;
public ObstacleAvoidanceQuery(int maxCircles, int maxSegments) {
m_maxCircles = maxCircles;
m_ncircles = 0;
m_circles = new ObstacleCircle[m_maxCircles];
for (int i = 0; i < m_maxCircles; i++) {
m_circles[i] = new ObstacleCircle();
}
m_maxSegments = maxSegments;
m_nsegments = 0;
m_segments = new ObstacleSegment[m_maxSegments];
for (int i = 0; i < m_maxSegments; i++) {
m_segments[i] = new ObstacleSegment();
}
}
public void reset() {
m_ncircles = 0;
m_nsegments = 0;
}
public void addCircle(float[] pos, float rad, float[] vel, float[] dvel) {
if (m_ncircles >= m_maxCircles)
return;
ObstacleCircle cir = m_circles[m_ncircles++];
vCopy(cir.p, pos);
cir.rad = rad;
vCopy(cir.vel, vel);
vCopy(cir.dvel, dvel);
}
public void addSegment(float[] p, float[] q) {
if (m_nsegments >= m_maxSegments)
return;
ObstacleSegment seg = m_segments[m_nsegments++];
vCopy(seg.p, p);
vCopy(seg.q, q);
}
public int getObstacleCircleCount() {
return m_ncircles;
}
public ObstacleCircle getObstacleCircle(int i) {
return m_circles[i];
}
public int getObstacleSegmentCount() {
return m_nsegments;
}
public ObstacleSegment getObstacleSegment(int i) {
return m_segments[i];
}
private void prepare(float[] pos, float[] dvel) {
// Prepare obstacles
for (int i = 0; i < m_ncircles; ++i) {
ObstacleCircle cir = m_circles[i];
// Side
float[] pa = pos;
float[] pb = cir.p;
float[] orig = { 0f, 0f, 0f };
float[] dv = new float[3];
vCopy(cir.dp, vSub(pb, pa));
vNormalize(cir.dp);
dv = vSub(cir.dvel, dvel);
float a = triArea2D(orig, cir.dp, dv);
if (a < 0.01f) {
cir.np[0] = -cir.dp[2];
cir.np[2] = cir.dp[0];
} else {
cir.np[0] = cir.dp[2];
cir.np[2] = -cir.dp[0];
}
}
for (int i = 0; i < m_nsegments; ++i) {
ObstacleSegment seg = m_segments[i];
// Precalc if the agent is really close to the segment.
float r = 0.01f;
Tuple<float, float> dt = distancePtSegSqr2D(pos, seg.p, seg.q);
seg.touch = dt.Item1 < sqr(r);
}
}
SweepCircleCircleResult sweepCircleCircle(float[] c0, float r0, float[] v, float[] c1, float r1) {
const float EPS = 0.0001f;
float[] s = vSub(c1, c0);
float r = r0 + r1;
float c = vDot2D(s, s) - r * r;
float a = vDot2D(v, v);
if (a < EPS)
return new SweepCircleCircleResult(false, 0f, 0f); // not moving
// Overlap, calc time to exit.
float b = vDot2D(v, s);
float d = b * b - a * c;
if (d < 0.0f)
return new SweepCircleCircleResult(false, 0f, 0f); // no intersection.
a = 1.0f / a;
float rd = (float) Math.Sqrt(d);
return new SweepCircleCircleResult(true, (b - rd) * a, (b + rd) * a);
}
Tuple<bool, float> isectRaySeg(float[] ap, float[] u, float[] bp, float[] bq) {
float[] v = vSub(bq, bp);
float[] w = vSub(ap, bp);
float d = vPerp2D(u, v);
if (Math.Abs(d) < 1e-6f)
return Tuple.Create(false, 0f);
d = 1.0f / d;
float t = vPerp2D(v, w) * d;
if (t < 0 || t > 1)
return Tuple.Create(false, 0f);
float s = vPerp2D(u, w) * d;
if (s < 0 || s > 1)
return Tuple.Create(false, 0f);
return Tuple.Create(true, t);
}
/**
* Calculate the collision penalty for a given velocity vector
*
* @param vcand
* sampled velocity
* @param dvel
* desired velocity
* @param minPenalty
* threshold penalty for early out
*/
private float processSample(float[] vcand, float cs, float[] pos, float rad, float[] vel, float[] dvel,
float minPenalty, ObstacleAvoidanceDebugData debug) {
// penalty for straying away from the desired and current velocities
float vpen = m_params.weightDesVel * (vDist2D(vcand, dvel) * m_invVmax);
float vcpen = m_params.weightCurVel * (vDist2D(vcand, vel) * m_invVmax);
// find the threshold hit time to bail out based on the early out penalty
// (see how the penalty is calculated below to understnad)
float minPen = minPenalty - vpen - vcpen;
float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
if (tThresold - m_params.horizTime > -float.MinValue)
return minPenalty; // already too much
// Find min time of impact and exit amongst all obstacles.
float tmin = m_params.horizTime;
float side = 0;
int nside = 0;
for (int i = 0; i < m_ncircles; ++i) {
ObstacleCircle cir = m_circles[i];
// RVO
float[] vab = vScale(vcand, 2);
vab = vSub(vab, vel);
vab = vSub(vab, cir.vel);
// Side
side += clamp(Math.Min(vDot2D(cir.dp, vab) * 0.5f + 0.5f, vDot2D(cir.np, vab) * 2), 0.0f, 1.0f);
nside++;
SweepCircleCircleResult sres = sweepCircleCircle(pos, rad, vab, cir.p, cir.rad);
if (!sres.intersection)
continue;
float htmin = sres.htmin, htmax = sres.htmax;
// Handle overlapping obstacles.
if (htmin < 0.0f && htmax > 0.0f) {
// Avoid more when overlapped.
htmin = -htmin * 0.5f;
}
if (htmin >= 0.0f) {
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin) {
tmin = htmin;
if (tmin < tThresold)
return minPenalty;
}
}
}
for (int i = 0; i < m_nsegments; ++i) {
ObstacleSegment seg = m_segments[i];
float htmin = 0;
if (seg.touch) {
// Special case when the agent is very close to the segment.
float[] sdir = vSub(seg.q, seg.p);
float[] snorm = new float[3];
snorm[0] = -sdir[2];
snorm[2] = sdir[0];
// If the velocity is pointing towards the segment, no collision.
if (vDot2D(snorm, vcand) < 0.0f)
continue;
// Else immediate collision.
htmin = 0.0f;
} else {
Tuple<bool, float> ires = isectRaySeg(pos, vcand, seg.p, seg.q);
if (!ires.Item1)
continue;
htmin = ires.Item2;
}
// Avoid less when facing walls.
htmin *= 2.0f;
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin) {
tmin = htmin;
if (tmin < tThresold)
return minPenalty;
}
}
// Normalize side bias, to prevent it dominating too much.
if (nside != 0)
side /= nside;
float spen = m_params.weightSide * side;
float tpen = m_params.weightToi * (1.0f / (0.1f + tmin * m_invHorizTime));
float penalty = vpen + vcpen + spen + tpen;
// Store different penalties for debug viewing
if (debug != null)
debug.addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
return penalty;
}
public Tuple<int, float[]> sampleVelocityGrid(float[] pos, float rad, float vmax, float[] vel, float[] dvel,
ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug) {
prepare(pos, dvel);
m_params = option;
m_invHorizTime = 1.0f / m_params.horizTime;
m_vmax = vmax;
m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
float[] nvel = new float[3];
vSet(nvel, 0f, 0f, 0f);
if (debug != null)
debug.reset();
float cvx = dvel[0] * m_params.velBias;
float cvz = dvel[2] * m_params.velBias;
float cs = vmax * 2 * (1 - m_params.velBias) / (m_params.gridSize - 1);
float half = (m_params.gridSize - 1) * cs * 0.5f;
float minPenalty = float.MaxValue;
int ns = 0;
for (int y = 0; y < m_params.gridSize; ++y) {
for (int x = 0; x < m_params.gridSize; ++x) {
float[] vcand = new float[3];
vSet(vcand, cvx + x * cs - half, 0f, cvz + y * cs - half);
if (sqr(vcand[0]) + sqr(vcand[2]) > sqr(vmax + cs / 2))
continue;
float penalty = processSample(vcand, cs, pos, rad, vel, dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty) {
minPenalty = penalty;
vCopy(nvel, vcand);
}
}
}
return Tuple.Create(ns, nvel);
}
// vector normalization that ignores the y-component.
void dtNormalize2D(float[] v) {
float d = (float) Math.Sqrt(v[0] * v[0] + v[2] * v[2]);
if (d == 0)
return;
d = 1.0f / d;
v[0] *= d;
v[2] *= d;
}
// vector normalization that ignores the y-component.
float[] dtRotate2D(float[] v, float ang) {
float[] dest = new float[3];
float c = (float) Math.Cos(ang);
float s = (float) Math.Sin(ang);
dest[0] = v[0] * c - v[2] * s;
dest[2] = v[0] * s + v[2] * c;
dest[1] = v[1];
return dest;
}
static readonly float DT_PI = 3.14159265f;
public Tuple<int, float[]> sampleVelocityAdaptive(float[] pos, float rad, float vmax, float[] vel,
float[] dvel, ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug) {
prepare(pos, dvel);
m_params = option;
m_invHorizTime = 1.0f / m_params.horizTime;
m_vmax = vmax;
m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
float[] nvel = new float[3];
vSet(nvel, 0f, 0f, 0f);
if (debug != null)
debug.reset();
// Build sampling pattern aligned to desired velocity.
float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];
int npat = 0;
int ndivs = m_params.adaptiveDivs;
int nrings = m_params.adaptiveRings;
int depth = m_params.adaptiveDepth;
int nd = clamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
int nr = clamp(nrings, 1, DT_MAX_PATTERN_RINGS);
float da = (1.0f / nd) * DT_PI * 2;
float ca = (float) Math.Cos(da);
float sa = (float) Math.Sin(da);
// desired direction
float[] ddir = new float[6];
vCopy(ddir, dvel);
dtNormalize2D(ddir);
float[] rotated = dtRotate2D(ddir, da * 0.5f); // rotated by da/2
ddir[3] = rotated[0];
ddir[4] = rotated[1];
ddir[5] = rotated[2];
// Always add sample at zero
pat[npat * 2 + 0] = 0;
pat[npat * 2 + 1] = 0;
npat++;
for (int j = 0; j < nr; ++j) {
float r = (float) (nr - j) / (float) nr;
pat[npat * 2 + 0] = ddir[(j % 2) * 3] * r;
pat[npat * 2 + 1] = ddir[(j % 2) * 3 + 2] * r;
int last1 = npat * 2;
int last2 = last1;
npat++;
for (int i = 1; i < nd - 1; i += 2) {
// get next point on the "right" (rotate CW)
pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa;
pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca;
// get next point on the "left" (rotate CCW)
pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
last1 = npat * 2;
last2 = last1 + 2;
npat += 2;
}
if ((nd & 1) == 0) {
pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
npat++;
}
}
// Start sampling.
float cr = vmax * (1.0f - m_params.velBias);
float[] res = new float[3];
vSet(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias);
int ns = 0;
for (int k = 0; k < depth; ++k) {
float minPenalty = float.MaxValue;
float[] bvel = new float[3];
vSet(bvel, 0, 0, 0);
for (int i = 0; i < npat; ++i) {
float[] vcand = new float[3];
vSet(vcand, res[0] + pat[i * 2 + 0] * cr, 0f, res[2] + pat[i * 2 + 1] * cr);
if (sqr(vcand[0]) + sqr(vcand[2]) > sqr(vmax + 0.001f))
continue;
float penalty = processSample(vcand, cr / 10, pos, rad, vel, dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty) {
minPenalty = penalty;
vCopy(bvel, vcand);
}
}
vCopy(res, bvel);
cr *= 0.5f;
}
vCopy(nvel, res);
return Tuple.Create(ns, nvel);
}
}

View File

@ -0,0 +1,561 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour.Crowd;
using static DetourCommon;
/**
* 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 PathCorridor {
private readonly float[] m_pos = new float[3];
private readonly float[] m_target = new float[3];
private List<long> m_path;
protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited) {
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = path.Count - 1; i >= 0; --i) {
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found) {
break;
}
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1) {
return path;
}
// Concatenate paths.
// Adjust beginning of the buffer to include the visited.
List<long> result = new();
// Store visited
for (int i = visited.Count - 1; i > furthestVisited; --i) {
result.Add(visited[i]);
}
result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
return result;
}
protected List<long> mergeCorridorEndMoved(List<long> path, List<long> visited) {
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = 0; i < path.Count; ++i) {
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found) {
break;
}
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1) {
return path;
}
// Concatenate paths.
List<long> result = path.GetRange(0, furthestPath);
result.AddRange(visited.GetRange(furthestVisited, visited.Count - furthestVisited));
return result;
}
protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited) {
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = path.Count - 1; i >= 0; --i) {
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found) {
break;
}
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited <= 0) {
return path;
}
// Concatenate paths.
// Adjust beginning of the buffer to include the visited.
List<long> result = visited.GetRange(0, furthestVisited);
result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
return result;
}
/**
* Allocates the corridor's path buffer.
*/
public PathCorridor() {
m_path = new();
}
/**
* Resets the path corridor to the specified position.
*
* @param ref
* The polygon reference containing the position.
* @param pos
* The new position in the corridor. [(x, y, z)]
*/
public void reset(long refs, float[] pos) {
m_path.Clear();
m_path.Add(refs);
vCopy(m_pos, pos);
vCopy(m_target, pos);
}
private static readonly float MIN_TARGET_DIST = sqr(0.01f);
/**
* Finds the corners in the corridor from the position toward the target. (The straightened path.)
*
* This is the function used to plan local movement within the corridor. One or more corners can be detected in
* order to plan movement. It performs essentially the same function as #dtNavMeshQuery::findStraightPath.
*
* Due to internal optimizations, the maximum number of corners returned will be (@p maxCorners - 1) For example: If
* the buffers are sized to hold 10 corners, the function will never return more than 9 corners. So if 10 corners
* are needed, the buffers should be sized for 11 corners.
*
* If the target is within range, it will be the last corner and have a polygon reference id of zero.
*
* @param filter
*
* @param[in] navquery The query object used to build the corridor.
* @return Corners
*/
public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter) {
List<StraightPathItem> path = new();
Result<List<StraightPathItem>> result = navquery.findStraightPath(m_pos, m_target, m_path, maxCorners, 0);
if (result.succeeded()) {
path = result.result;
// Prune points in the beginning of the path which are too close.
int start = 0;
foreach (StraightPathItem spi in path) {
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
|| vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST) {
break;
}
start++;
}
int end = path.Count;
// Prune points after an off-mesh connection.
for (int i = start; i < path.Count; i++) {
StraightPathItem spi = path[i];
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
end = i + 1;
break;
}
}
path = path.GetRange(start, end - start);
}
return path;
}
/**
* Attempts to optimize the path if the specified point is visible from the current position.
*
* Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
* original corridor. Over time this can result in the formation of a non-optimal corridor. Non-optimal paths can
* also form near the corners of tiles.
*
* This function uses an efficient local visibility search to try to optimize the corridor between the current
* position and @p next.
*
* The corridor will change only if @p next is visible from the current position and moving directly toward the
* point is better than following the existing path.
*
* The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
* the call to match the needs to the agent.
*
* This function is not suitable for long distance searches.
*
* @param next
* The point to search toward. [(x, y, z])
* @param pathOptimizationRange
* The maximum range to search. [Limit: > 0]
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
*/
public void optimizePathVisibility(float[] next, float pathOptimizationRange, NavMeshQuery navquery,
QueryFilter filter) {
// Clamp the ray to max distance.
float dist = vDist2D(m_pos, next);
// If too close to the goal, do not try to optimize.
if (dist < 0.01f) {
return;
}
// Overshoot a little. This helps to optimize open fields in tiled
// meshes.
dist = Math.Min(dist + 0.01f, pathOptimizationRange);
// Adjust ray length.
float[] delta = vSub(next, m_pos);
float[] goal = vMad(m_pos, delta, pathOptimizationRange / dist);
Result<RaycastHit> rc = navquery.raycast(m_path[0], m_pos, goal, filter, 0, 0);
if (rc.succeeded()) {
if (rc.result.path.Count > 1 && rc.result.t > 0.99f) {
m_path = mergeCorridorStartShortcut(m_path, rc.result.path);
}
}
}
/**
* Attempts to optimize the path using a local area search. (Partial replanning.)
*
* Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
* original corridor. Over time this can result in the formation of a non-optimal corridor. This function will use a
* local area path search to try to re-optimize the corridor.
*
* The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
* the call to match the needs to the agent.
*
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
*
*/
public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations) {
if (m_path.Count < 3) {
return false;
}
navquery.initSlicedFindPath(m_path[0], m_path[m_path.Count - 1], m_pos, m_target, filter, 0);
navquery.updateSlicedFindPath(maxIterations);
Result<List<long>> fpr = navquery.finalizeSlicedFindPathPartial(m_path);
if (fpr.succeeded() && fpr.result.Count > 0) {
m_path = mergeCorridorStartShortcut(m_path, fpr.result);
return true;
}
return false;
}
public bool moveOverOffmeshConnection(long offMeshConRef, long[] refs, float[] start, float[] end,
NavMeshQuery navquery) {
// Advance the path up to and over the off-mesh connection.
long prevRef = 0, polyRef = m_path[0];
int npos = 0;
while (npos < m_path.Count && polyRef != offMeshConRef) {
prevRef = polyRef;
polyRef = m_path[npos];
npos++;
}
if (npos == m_path.Count) {
// Could not find offMeshConRef
return false;
}
// Prune path
m_path = m_path.GetRange(npos, m_path.Count - npos);
refs[0] = prevRef;
refs[1] = polyRef;
NavMesh nav = navquery.getAttachedNavMesh();
Result<Tuple<float[], float[]>> startEnd = nav.getOffMeshConnectionPolyEndPoints(refs[0], refs[1]);
if (startEnd.succeeded()) {
vCopy(m_pos, startEnd.result.Item2);
vCopy(start, startEnd.result.Item1);
vCopy(end, startEnd.result.Item2);
return true;
}
return false;
}
/**
* Moves the position from the current location to the desired location, adjusting the corridor as needed to reflect
* the change.
*
* Behavior:
*
* - The movement is constrained to the surface of the navigation mesh. - The corridor is automatically adjusted
* (shorted or lengthened) in order to remain valid. - The new position will be located in the adjusted corridor's
* first polygon.
*
* The expected use case is that the desired position will be 'near' the current corridor. What is considered 'near'
* depends on local polygon density, query search extents, etc.
*
* The resulting position will differ from the desired position if the desired position is not on the navigation
* mesh, or it can't be reached using a local search.
*
* @param npos
* The desired new position. [(x, y, z)]
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
*/
public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) {
// Move along navmesh and update new position.
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[0], m_pos, npos, filter);
if (masResult.succeeded()) {
m_path = mergeCorridorStartMoved(m_path, masResult.result.getVisited());
// Adjust the position to stay on top of the navmesh.
vCopy(m_pos, masResult.result.getResultPos());
Result<float> hr = navquery.getPolyHeight(m_path[0], masResult.result.getResultPos());
if (hr.succeeded()) {
m_pos[1] = hr.result;
}
return true;
}
return false;
}
/**
* Moves the target from the curent location to the desired location, adjusting the corridor as needed to reflect
* the change. Behavior: - The movement is constrained to the surface of the navigation mesh. - The corridor is
* automatically adjusted (shorted or lengthened) in order to remain valid. - The new target will be located in the
* adjusted corridor's last polygon.
*
* The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near'
* depends on local polygon density, query search extents, etc. The resulting target will differ from the desired
* target if the desired target is not on the navigation mesh, or it can't be reached using a local search.
*
* @param npos
* The desired new target position. [(x, y, z)]
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
*/
public bool moveTargetPosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) {
// Move along navmesh and update new position.
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[m_path.Count - 1], m_target,
npos, filter);
if (masResult.succeeded()) {
m_path = mergeCorridorEndMoved(m_path, masResult.result.getVisited());
// TODO: should we do that?
// Adjust the position to stay on top of the navmesh.
/*
* float h = m_target[1]; navquery->getPolyHeight(m_path[m_npath-1],
* result, &h); result[1] = h;
*/
vCopy(m_target, masResult.result.getResultPos());
return true;
}
return false;
}
/**
* Loads a new path and target into the corridor. The current corridor position is expected to be within the first
* polygon in the path. The target is expected to be in the last polygon.
*
* @warning The size of the path must not exceed the size of corridor's path buffer set during #init().
* @param target
* The target location within the last polygon of the path. [(x, y, z)]
* @param path
* The path corridor.
*/
public void setCorridor(float[] target, List<long> path) {
vCopy(m_target, target);
m_path = new(path);
}
public void fixPathStart(long safeRef, float[] safePos) {
vCopy(m_pos, safePos);
if (m_path.Count < 3 && m_path.Count > 0) {
long p = m_path[m_path.Count - 1];
m_path.Clear();
m_path.Add(safeRef);
m_path.Add(0L);
m_path.Add(p);
} else {
m_path.Clear();
m_path.Add(safeRef);
m_path.Add(0L);
}
}
public void trimInvalidPath(long safeRef, float[] safePos, NavMeshQuery navquery, QueryFilter filter) {
// Keep valid path as far as possible.
int n = 0;
while (n < m_path.Count && navquery.isValidPolyRef(m_path[n], filter)) {
n++;
}
if (n == 0) {
// The first polyref is bad, use current safe values.
vCopy(m_pos, safePos);
m_path.Clear();
m_path.Add(safeRef);
} else if (n < m_path.Count) {
m_path = m_path.GetRange(0, n);
// The path is partially usable.
}
// Clamp target pos to last poly
Result<float[]> result = navquery.closestPointOnPolyBoundary(m_path[m_path.Count - 1], m_target);
if (result.succeeded()) {
vCopy(m_target, result.result);
}
}
/**
* Checks the current corridor path to see if its polygon references remain valid. The path can be invalidated if
* there are structural changes to the underlying navigation mesh, or the state of a polygon within the path changes
* resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.)
*
* @param maxLookAhead
* The number of polygons from the beginning of the corridor to search.
* @param navquery
* The query object used to build the corridor.
* @param filter
* The filter to apply to the operation.
* @return
*/
public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter) {
// Check that all polygons still pass query filter.
int n = Math.Min(m_path.Count, maxLookAhead);
for (int i = 0; i < n; ++i) {
if (!navquery.isValidPolyRef(m_path[i], filter)) {
return false;
}
}
return true;
}
/**
* Gets the current position within the corridor. (In the first polygon.)
*
* @return The current position within the corridor.
*/
public float[] getPos() {
return m_pos;
}
/**
* Gets the current target within the corridor. (In the last polygon.)
*
* @return The current target within the corridor.
*/
public float[] getTarget() {
return m_target;
}
/**
* The polygon reference id of the first polygon in the corridor, the polygon containing the position.
*
* @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
*/
public long getFirstPoly() {
return 0 == m_path.Count ? 0 : m_path[0];
}
/**
* The polygon reference id of the last polygon in the corridor, the polygon containing the target.
*
* @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
*/
public long getLastPoly() {
return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1];
}
/**
* The corridor's path.
*/
public List<long> getPath() {
return m_path;
}
/**
* The number of polygons in the current corridor path.
*
* @return The number of polygons in the current corridor path.
*/
public int getPathCount() {
return m_path.Count;
}
}

View File

@ -0,0 +1,31 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Crowd;
public class PathQuery {
/// Path find start and end location.
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public long startRef;
public long endRef;
public QueryFilter filter; /// < TODO: This is potentially dangerous!
public readonly PathQueryResult result = new PathQueryResult();
public NavMeshQuery navQuery;
}

View File

@ -0,0 +1,26 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
namespace DotRecast.Detour.Crowd;
public class PathQueryResult {
public Status status;
public List<long> path = new();
}

View File

@ -0,0 +1,82 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour.Crowd;
using static DetourCommon;
public class PathQueue {
private readonly CrowdConfig config;
private readonly LinkedList<PathQuery> queue = new();
public PathQueue(CrowdConfig config) {
this.config = config;
}
public void update(NavMesh navMesh) {
// Update path request until there is nothing to update or up to maxIters pathfinder iterations has been
// consumed.
int iterCount = config.maxFindPathIterations;
while (iterCount > 0) {
PathQuery? q = queue.First?.Value;
if (q == null) {
break;
}
// Handle query start.
if (q.result.status == null) {
q.navQuery = new NavMeshQuery(navMesh);
q.result.status = q.navQuery.initSlicedFindPath(q.startRef, q.endRef, q.startPos, q.endPos, q.filter, 0);
}
// Handle query in progress.
if (q.result.status.isInProgress()) {
Result<int> res = q.navQuery.updateSlicedFindPath(iterCount);
q.result.status = res.status;
iterCount -= res.result;
}
if (q.result.status.isSuccess()) {
Result<List<long>> path = q.navQuery.finalizeSlicedFindPath();
q.result.status = path.status;
q.result.path = path.result;
}
if (!(q.result.status.isFailed() || q.result.status.isSuccess())) {
queue.AddFirst(q);
}
}
}
public PathQueryResult request(long startRef, long endRef, float[] startPos, float[] endPos, QueryFilter filter) {
if (queue.Count >= config.pathQueueSize) {
return null;
}
PathQuery q = new PathQuery();
vCopy(q.startPos, startPos);
q.startRef = startRef;
vCopy(q.endPos, endPos);
q.endRef = endRef;
q.result.status = null;
q.filter = filter;
queue.AddLast(q);
return q.result;
}
}

View File

@ -0,0 +1,129 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.Linq;
namespace DotRecast.Detour.Crowd;
public class ProximityGrid {
private readonly float m_cellSize;
private readonly float m_invCellSize;
private readonly Dictionary<ItemKey, List<CrowdAgent>> items;
public ProximityGrid(float m_cellSize) {
this.m_cellSize = m_cellSize;
m_invCellSize = 1.0f / m_cellSize;
items = new();
}
void clear() {
items.Clear();
}
public void addItem(CrowdAgent agent, float minx, float miny, float maxx, float maxy) {
int iminx = (int) Math.Floor(minx * m_invCellSize);
int iminy = (int) Math.Floor(miny * m_invCellSize);
int imaxx = (int) Math.Floor(maxx * m_invCellSize);
int imaxy = (int) Math.Floor(maxy * m_invCellSize);
for (int y = iminy; y <= imaxy; ++y) {
for (int x = iminx; x <= imaxx; ++x) {
ItemKey key = new ItemKey(x, y);
if (!items.TryGetValue(key, out var ids)) {
ids = new();
items.Add(key, ids);
}
ids.Add(agent);
}
}
}
public HashSet<CrowdAgent> queryItems(float minx, float miny, float maxx, float maxy) {
int iminx = (int) Math.Floor(minx * m_invCellSize);
int iminy = (int) Math.Floor(miny * m_invCellSize);
int imaxx = (int) Math.Floor(maxx * m_invCellSize);
int imaxy = (int) Math.Floor(maxy * m_invCellSize);
HashSet<CrowdAgent> result = new();
for (int y = iminy; y <= imaxy; ++y) {
for (int x = iminx; x <= imaxx; ++x) {
ItemKey key = new ItemKey(x, y);
if (items.TryGetValue(key, out var ids)) {
result.UnionWith(ids);
}
}
}
return result;
}
public List<int[]> getItemCounts() {
return items
.Where(e => e.Value.Count > 0)
.Select(e => new int[] { e.Key.x, e.Key.y, e.Value.Count })
.ToList();
}
public float getCellSize() {
return m_cellSize;
}
private class ItemKey {
public readonly int x;
public readonly int y;
public ItemKey(int x, int y) {
this.x = x;
this.y = y;
}
public override int GetHashCode() {
int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
public override bool Equals(object? obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (GetType() != obj.GetType())
return false;
ItemKey other = (ItemKey) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
};
}

View File

@ -0,0 +1,33 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Crowd;
public class SweepCircleCircleResult {
public readonly bool intersection;
public readonly float htmin;
public readonly float htmax;
public SweepCircleCircleResult(bool intersection, float htmin, float htmax) {
this.intersection = intersection;
this.htmin = htmin;
this.htmax = htmax;
}
}

View File

@ -0,0 +1,28 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Crowd.Tracking;
public class CrowdAgentDebugInfo {
public CrowdAgent agent;
public float[] optStart = new float[3];
public float[] optEnd = new float[3];
public ObstacleAvoidanceDebugData vod;
}

View File

@ -0,0 +1,124 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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;
namespace DotRecast.Detour.Crowd.Tracking;
using static DetourCommon;
public class ObstacleAvoidanceDebugData {
int m_nsamples;
int m_maxSamples;
float[] m_vel;
float[] m_ssize;
float[] m_pen;
float[] m_vpen;
float[] m_vcpen;
float[] m_spen;
float[] m_tpen;
public ObstacleAvoidanceDebugData(int maxSamples) {
m_maxSamples = maxSamples;
m_vel = new float[3 * m_maxSamples];
m_pen = new float[m_maxSamples];
m_ssize = new float[m_maxSamples];
m_vpen = new float[m_maxSamples];
m_vcpen = new float[m_maxSamples];
m_spen = new float[m_maxSamples];
m_tpen = new float[m_maxSamples];
}
public void reset() {
m_nsamples = 0;
}
void normalizeArray(float[] arr, int n) {
// Normalize penaly range.
float minPen = float.MaxValue;
float maxPen = -float.MaxValue;
for (int i = 0; i < n; ++i) {
minPen = Math.Min(minPen, arr[i]);
maxPen = Math.Max(maxPen, arr[i]);
}
float penRange = maxPen - minPen;
float s = penRange > 0.001f ? (1.0f / penRange) : 1;
for (int i = 0; i < n; ++i)
arr[i] = clamp((arr[i] - minPen) * s, 0.0f, 1.0f);
}
public void normalizeSamples() {
normalizeArray(m_pen, m_nsamples);
normalizeArray(m_vpen, m_nsamples);
normalizeArray(m_vcpen, m_nsamples);
normalizeArray(m_spen, m_nsamples);
normalizeArray(m_tpen, m_nsamples);
}
public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen) {
if (m_nsamples >= m_maxSamples)
return;
m_vel[m_nsamples * 3] = vel[0];
m_vel[m_nsamples * 3 + 1] = vel[1];
m_vel[m_nsamples * 3 + 2] = vel[2];
m_ssize[m_nsamples] = ssize;
m_pen[m_nsamples] = pen;
m_vpen[m_nsamples] = vpen;
m_vcpen[m_nsamples] = vcpen;
m_spen[m_nsamples] = spen;
m_tpen[m_nsamples] = tpen;
m_nsamples++;
}
public int getSampleCount() {
return m_nsamples;
}
public float[] getSampleVelocity(int i) {
float[] vel = new float[3];
vel[0] = m_vel[i * 3];
vel[1] = m_vel[i * 3 + 1];
vel[2] = m_vel[i * 3 + 2];
return vel;
}
public float getSampleSize(int i) {
return m_ssize[i];
}
public float getSamplePenalty(int i) {
return m_pen[i];
}
public float getSampleDesiredVelocityPenalty(int i) {
return m_vpen[i];
}
public float getSampleCurrentVelocityPenalty(int i) {
return m_vcpen[i];
}
public float getSamplePreferredSidePenalty(int i) {
return m_spen[i];
}
public float getSampleCollisionTimePenalty(int i) {
return m_tpen[i];
}
}

View File

@ -0,0 +1,44 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using DotRecast.Detour.Dynamic.Colliders;
namespace DotRecast.Detour.Dynamic;
public class AddColliderQueueItem : UpdateQueueItem {
private readonly long colliderId;
private readonly Collider collider;
private readonly ICollection<DynamicTile> _affectedTiles;
public AddColliderQueueItem(long colliderId, Collider collider, ICollection<DynamicTile> affectedTiles) {
this.colliderId = colliderId;
this.collider = collider;
_affectedTiles = affectedTiles;
}
public ICollection<DynamicTile> affectedTiles() {
return _affectedTiles;
}
public void process(DynamicTile tile) {
tile.addCollider(colliderId, collider);
}
}

View File

@ -0,0 +1,44 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public abstract class AbstractCollider : Collider {
protected readonly int area;
protected readonly float flagMergeThreshold;
protected readonly float[] _bounds;
public AbstractCollider(int area, float flagMergeThreshold, float[] bounds) {
this.area = area;
this.flagMergeThreshold = flagMergeThreshold;
this._bounds = bounds;
}
public float[] bounds() {
return _bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry)
{
///?
}
}

View File

@ -0,0 +1,79 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public class BoxCollider : AbstractCollider {
private readonly float[] center;
private readonly float[][] halfEdges;
public BoxCollider(float[] center, float[][] halfEdges, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(center, halfEdges))
{
this.center = center;
this.halfEdges = halfEdges;
}
private static float[] bounds(float[] center, float[][] halfEdges) {
float[] bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
for (int i = 0; i < 8; ++i) {
float s0 = (i & 1) != 0 ? 1f : -1f;
float s1 = (i & 2) != 0 ? 1f : -1f;
float s2 = (i & 4) != 0 ? 1f : -1f;
float vx = center[0] + s0 * halfEdges[0][0] + s1 * halfEdges[1][0] + s2 * halfEdges[2][0];
float vy = center[1] + s0 * halfEdges[0][1] + s1 * halfEdges[1][1] + s2 * halfEdges[2][1];
float vz = center[2] + s0 * halfEdges[0][2] + s1 * halfEdges[1][2] + s2 * halfEdges[2][2];
bounds[0] = Math.Min(bounds[0], vx);
bounds[1] = Math.Min(bounds[1], vy);
bounds[2] = Math.Min(bounds[2], vz);
bounds[3] = Math.Max(bounds[3], vx);
bounds[4] = Math.Max(bounds[4], vy);
bounds[5] = Math.Max(bounds[5], vz);
}
return bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
RecastFilledVolumeRasterization.rasterizeBox(hf, center, halfEdges, area, (int) Math.Floor(flagMergeThreshold / hf.ch),
telemetry);
}
public static float[][] getHalfEdges(float[] up, float[] forward, float[] extent) {
float[][] halfEdges = new float[][] { new float[3], new float[] { up[0], up[1], up[2] }, new float[3] };
RecastVectors.normalize(halfEdges[1]);
RecastVectors.cross(halfEdges[0], up, forward);
RecastVectors.normalize(halfEdges[0]);
RecastVectors.cross(halfEdges[2], halfEdges[0], up);
RecastVectors.normalize(halfEdges[2]);
halfEdges[0][0] *= extent[0];
halfEdges[0][1] *= extent[0];
halfEdges[0][2] *= extent[0];
halfEdges[1][0] *= extent[1];
halfEdges[1][1] *= extent[1];
halfEdges[1][2] *= extent[1];
halfEdges[2][0] *= extent[2];
halfEdges[2][1] *= extent[2];
halfEdges[2][2] *= extent[2];
return halfEdges;
}
}

View File

@ -0,0 +1,49 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public class CapsuleCollider : AbstractCollider {
private readonly float[] start;
private readonly float[] end;
private readonly float radius;
public CapsuleCollider(float[] start, float[] end, float radius, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(start, end, radius))
{
this.start = start;
this.end = end;
this.radius = radius;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
RecastFilledVolumeRasterization.rasterizeCapsule(hf, start, end, radius, area, (int) Math.Floor(flagMergeThreshold / hf.ch),
telemetry);
}
private static float[] bounds(float[] start, float[] end, float radius) {
return new float[] { Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
Math.Min(start[2], end[2]) - radius, Math.Max(start[0], end[0]) + radius, Math.Max(start[1], end[1]) + radius,
Math.Max(start[2], end[2]) + radius };
}
}

View File

@ -0,0 +1,27 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public interface Collider {
float[] bounds();
void rasterize(Heightfield hf, Telemetry telemetry);
}

View File

@ -0,0 +1,65 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using System.Linq;
using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public class CompositeCollider : Collider {
private readonly List<Collider> colliders;
private readonly float[] _bounds;
public CompositeCollider(List<Collider> colliders) {
this.colliders = colliders;
_bounds = bounds(colliders);
}
public CompositeCollider(params Collider[] colliders) {
this.colliders = colliders.ToList();
_bounds = bounds(this.colliders);
}
public float[] bounds() {
return _bounds;
}
private static float[] bounds(List<Collider> colliders) {
float[] bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
foreach (Collider collider in colliders) {
float[] b = collider.bounds();
bounds[0] = Math.Min(bounds[0], b[0]);
bounds[1] = Math.Min(bounds[1], b[1]);
bounds[2] = Math.Min(bounds[2], b[2]);
bounds[3] = Math.Max(bounds[3], b[3]);
bounds[4] = Math.Max(bounds[4], b[4]);
bounds[5] = Math.Max(bounds[5], b[5]);
}
return bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
foreach (var c in colliders)
c.rasterize(hf, telemetry);
}
}

View File

@ -0,0 +1,46 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public class ConvexTrimeshCollider : AbstractCollider {
private readonly float[] vertices;
private readonly int[] triangles;
public ConvexTrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, TrimeshCollider.computeBounds(vertices)) {
this.vertices = vertices;
this.triangles = triangles;
}
public ConvexTrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds) {
this.vertices = vertices;
this.triangles = triangles;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
RecastFilledVolumeRasterization.rasterizeConvex(hf, vertices, triangles, area,
(int) Math.Floor(flagMergeThreshold / hf.ch), telemetry);
}
}

View File

@ -0,0 +1,48 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public class CylinderCollider : AbstractCollider {
private readonly float[] start;
private readonly float[] end;
private readonly float radius;
public CylinderCollider(float[] start, float[] end, float radius, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(start, end, radius)) {
this.start = start;
this.end = end;
this.radius = radius;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
RecastFilledVolumeRasterization.rasterizeCylinder(hf, start, end, radius, area, (int) Math.Floor(flagMergeThreshold / hf.ch),
telemetry);
}
private static float[] bounds(float[] start, float[] end, float radius) {
return new float[] { Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
Math.Min(start[2], end[2]) - radius, Math.Max(start[0], end[0]) + radius, Math.Max(start[1], end[1]) + radius,
Math.Max(start[2], end[2]) + radius };
}
}

View File

@ -0,0 +1,45 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public class SphereCollider : AbstractCollider {
private readonly float[] center;
private readonly float radius;
public SphereCollider(float[] center, float radius, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(center, radius)) {
this.center = center;
this.radius = radius;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
RecastFilledVolumeRasterization.rasterizeSphere(hf, center, radius, area, (int) Math.Floor(flagMergeThreshold / hf.ch),
telemetry);
}
private static float[] bounds(float[] center, float radius) {
return new float[] { center[0] - radius, center[1] - radius, center[2] - radius, center[0] + radius, center[1] + radius,
center[2] + radius };
}
}

View File

@ -0,0 +1,61 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders;
public class TrimeshCollider : AbstractCollider {
private readonly float[] vertices;
private readonly int[] triangles;
public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, computeBounds(vertices)) {
this.vertices = vertices;
this.triangles = triangles;
}
public TrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds) {
this.vertices = vertices;
this.triangles = triangles;
}
public static float[] computeBounds(float[] vertices) {
float[] bounds = new float[] { vertices[0], vertices[1], vertices[2], vertices[0], vertices[1], vertices[2] };
for (int i = 3; i < vertices.Length; i += 3) {
bounds[0] = Math.Min(bounds[0], vertices[i]);
bounds[1] = Math.Min(bounds[1], vertices[i + 1]);
bounds[2] = Math.Min(bounds[2], vertices[i + 2]);
bounds[3] = Math.Max(bounds[3], vertices[i]);
bounds[4] = Math.Max(bounds[4], vertices[i + 1]);
bounds[5] = Math.Max(bounds[5], vertices[i + 2]);
}
return bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
for (int i = 0; i < triangles.Length; i += 3) {
RecastRasterization.rasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area,
(int) Math.Floor(flagMergeThreshold / hf.ch), telemetry);
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,226 @@
/*
recast4j copyright (c) 2021 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using DotRecast.Core;
using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io;
using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic;
public class DynamicNavMesh {
public const int MAX_VERTS_PER_POLY = 6;
public readonly DynamicNavMeshConfig config;
private readonly RecastBuilder builder;
private readonly Dictionary<long, DynamicTile> _tiles = new();
private readonly Telemetry telemetry;
private readonly NavMeshParams navMeshParams;
private readonly BlockingCollection<UpdateQueueItem> updateQueue = new();
private readonly AtomicLong currentColliderId = new AtomicLong(0);
private NavMesh _navMesh;
private bool dirty = true;
public DynamicNavMesh(VoxelFile voxelFile) {
config = new DynamicNavMeshConfig(voxelFile.useTiles, voxelFile.tileSizeX, voxelFile.tileSizeZ, voxelFile.cellSize);
config.walkableHeight = voxelFile.walkableHeight;
config.walkableRadius = voxelFile.walkableRadius;
config.walkableClimb = voxelFile.walkableClimb;
config.walkableSlopeAngle = voxelFile.walkableSlopeAngle;
config.maxSimplificationError = voxelFile.maxSimplificationError;
config.maxEdgeLen = voxelFile.maxEdgeLen;
config.minRegionArea = voxelFile.minRegionArea;
config.regionMergeArea = voxelFile.regionMergeArea;
config.vertsPerPoly = voxelFile.vertsPerPoly;
config.buildDetailMesh = voxelFile.buildMeshDetail;
config.detailSampleDistance = voxelFile.detailSampleDistance;
config.detailSampleMaxError = voxelFile.detailSampleMaxError;
builder = new RecastBuilder();
navMeshParams = new NavMeshParams();
navMeshParams.orig[0] = voxelFile.bounds[0];
navMeshParams.orig[1] = voxelFile.bounds[1];
navMeshParams.orig[2] = voxelFile.bounds[2];
navMeshParams.tileWidth = voxelFile.cellSize * voxelFile.tileSizeX;
navMeshParams.tileHeight = voxelFile.cellSize * voxelFile.tileSizeZ;
navMeshParams.maxTiles = voxelFile.tiles.Count;
navMeshParams.maxPolys = 0x8000;
foreach (var t in voxelFile.tiles) {
_tiles.Add(lookupKey(t.tileX, t.tileZ), new DynamicTile(t));
};
telemetry = new Telemetry();
}
public NavMesh navMesh() {
return _navMesh;
}
/**
* Voxel queries require checkpoints to be enabled in {@link DynamicNavMeshConfig}
*/
public VoxelQuery voxelQuery() {
return new VoxelQuery(navMeshParams.orig, navMeshParams.tileWidth, navMeshParams.tileHeight, lookupHeightfield);
}
private Heightfield lookupHeightfield(int x, int z) {
return getTileAt(x, z)?.checkpoint.heightfield;
}
public long addCollider(Collider collider) {
long cid = currentColliderId.IncrementAndGet();
updateQueue.Add(new AddColliderQueueItem(cid, collider, getTiles(collider.bounds())));
return cid;
}
public void removeCollider(long colliderId) {
updateQueue.Add(new RemoveColliderQueueItem(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<DynamicTile> stream) {
foreach (var dynamicTile in stream)
rebuild(dynamicTile);
return updateNavMesh();
}
private HashSet<DynamicTile> processQueue() {
var items = consumeQueue();
foreach (var item in items) {
process(item);
}
return items.SelectMany(i => i.affectedTiles()).ToHashSet();
}
private List<UpdateQueueItem> consumeQueue() {
List<UpdateQueueItem> items = new();
while (updateQueue.TryTake(out var item)) {
items.Add(item);
}
return items;
}
private void process(UpdateQueueItem item) {
foreach (var tile in item.affectedTiles())
{
item.process(tile);
}
}
/**
* Perform full build concurrently using the given {@link ExecutorService}
*/
public Task<bool> build(TaskFactory executor) {
processQueue();
return rebuild(_tiles.Values, executor);
}
/**
* Perform incremental update concurrently using the given {@link ExecutorService}
*/
public Task<bool> update(TaskFactory executor) {
return rebuild(processQueue(), executor);
}
private Task<bool> rebuild(ICollection<DynamicTile> tiles, TaskFactory executor)
{
var tasks = tiles.Select(tile => executor.StartNew(() => rebuild(tile))).ToArray();
return Task.WhenAll(tasks).ContinueWith(k => updateNavMesh());
}
private ICollection<DynamicTile> getTiles(float[] bounds) {
if (bounds == null) {
return _tiles.Values;
}
int minx = (int) Math.Floor((bounds[0] - navMeshParams.orig[0]) / navMeshParams.tileWidth);
int minz = (int) Math.Floor((bounds[2] - navMeshParams.orig[2]) / navMeshParams.tileHeight);
int maxx = (int) Math.Floor((bounds[3] - navMeshParams.orig[0]) / navMeshParams.tileWidth);
int maxz = (int) Math.Floor((bounds[5] - navMeshParams.orig[2]) / navMeshParams.tileHeight);
List<DynamicTile> tiles = new();
for (int z = minz; z <= maxz; ++z) {
for (int x = minx; x <= maxx; ++x) {
DynamicTile tile = getTileAt(x, z);
if (tile != null) {
tiles.Add(tile);
}
}
}
return tiles;
}
private List<DynamicTile> getTilesByCollider(long cid) {
return _tiles.Values.Where(t => t.containsCollider(cid)).ToList();
}
private void rebuild(DynamicTile tile) {
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.walkableHeight = config.walkableHeight;
dirty = dirty | tile.build(builder, config, telemetry);
}
private bool updateNavMesh() {
if (dirty) {
NavMesh navMesh = new NavMesh(navMeshParams, MAX_VERTS_PER_POLY);
foreach (var t in _tiles.Values)
t.addTo(navMesh);
this._navMesh = navMesh;
dirty = false;
return true;
}
return false;
}
private DynamicTile getTileAt(int x, int z) {
return _tiles.TryGetValue(lookupKey(x, z), out var tile)
? tile
: null;
}
private long lookupKey(long x, long z) {
return (z << 32) | x;
}
public List<VoxelTile> voxelTiles() {
return _tiles.Values.Select(t => t.voxelTile).ToList();
}
public List<RecastBuilderResult> recastResults() {
return _tiles.Values.Select(t => t.recastResult).ToList();
}
}

View File

@ -0,0 +1,56 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic;
public class DynamicNavMeshConfig {
public readonly bool useTiles;
public readonly int tileSizeX;
public readonly int tileSizeZ;
public readonly float cellSize;
public PartitionType partitionType = PartitionType.WATERSHED;
public AreaModification walkableAreaModification = new AreaModification(1);
public float walkableHeight;
public float walkableSlopeAngle;
public float walkableRadius;
public float walkableClimb;
public float minRegionArea;
public float regionMergeArea;
public float maxEdgeLen;
public float maxSimplificationError;
public int vertsPerPoly;
public bool buildDetailMesh;
public float detailSampleDistance;
public float detailSampleMaxError;
public bool filterLowHangingObstacles = true;
public bool filterLedgeSpans = true;
public bool filterWalkableLowHeightSpans = true;
public bool enableCheckpoints = true;
public bool keepIntermediateResults = false;
public DynamicNavMeshConfig(bool useTiles, int tileSizeX, int tileSizeZ, float cellSize) {
this.useTiles = useTiles;
this.tileSizeX = tileSizeX;
this.tileSizeZ = tileSizeZ;
this.cellSize = cellSize;
}
}

View File

@ -0,0 +1,154 @@
/*
recast4j copyright (c) 2021 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io;
using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic;
public class DynamicTile {
public readonly VoxelTile voxelTile;
public DynamicTileCheckpoint checkpoint;
public RecastBuilderResult recastResult;
MeshData meshData;
private readonly ConcurrentDictionary<long, Collider> colliders = new();
private bool dirty = true;
private long id;
public DynamicTile(VoxelTile voxelTile) {
this.voxelTile = voxelTile;
}
public bool build(RecastBuilder builder, DynamicNavMeshConfig config, Telemetry telemetry) {
if (dirty) {
Heightfield heightfield = buildHeightfield(config, telemetry);
RecastBuilderResult r = buildRecast(builder, config, voxelTile, heightfield, telemetry);
NavMeshDataCreateParams option = navMeshCreateParams(voxelTile.tileX, voxelTile.tileZ, voxelTile.cellSize,
voxelTile.cellHeight, config, r);
meshData = NavMeshBuilder.createNavMeshData(option);
return true;
}
return false;
}
private Heightfield buildHeightfield(DynamicNavMeshConfig config, Telemetry telemetry) {
ICollection<long> rasterizedColliders = checkpoint != null ? checkpoint.colliders : ImmutableArray<long>.Empty;
Heightfield heightfield = checkpoint != null ? checkpoint.heightfield : voxelTile.heightfield();
foreach (var (cid, c) in colliders) {
if (!rasterizedColliders.Contains(cid)) {
heightfield.bmax[1] = Math.Max(heightfield.bmax[1], c.bounds()[4] + heightfield.ch * 2);
c.rasterize(heightfield, telemetry);
}
}
if (config.enableCheckpoints) {
checkpoint = new DynamicTileCheckpoint(heightfield, colliders.Keys.ToHashSet());
}
return heightfield;
}
private RecastBuilderResult buildRecast(RecastBuilder builder, DynamicNavMeshConfig config, VoxelTile vt,
Heightfield heightfield, Telemetry telemetry) {
RecastConfig rcConfig = new RecastConfig(config.useTiles, config.tileSizeX, config.tileSizeZ, vt.borderSize,
config.partitionType, vt.cellSize, vt.cellHeight, config.walkableSlopeAngle, true, true, true,
config.walkableHeight, config.walkableRadius, config.walkableClimb, config.minRegionArea, config.regionMergeArea,
config.maxEdgeLen, config.maxSimplificationError,
Math.Min(DynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly), true, config.detailSampleDistance,
config.detailSampleMaxError, null);
RecastBuilderResult r = builder.build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry);
if (config.keepIntermediateResults) {
recastResult = r;
}
return r;
}
public void addCollider(long cid, Collider collider) {
colliders[cid] = collider;
dirty = true;
}
public bool containsCollider(long cid) {
return colliders.ContainsKey(cid);
}
public void removeCollider(long colliderId) {
if (colliders.TryRemove(colliderId, out var collider)) {
dirty = true;
checkpoint = null;
}
}
private NavMeshDataCreateParams navMeshCreateParams(int tilex, int tileZ, float cellSize, float cellHeight,
DynamicNavMeshConfig config, RecastBuilderResult rcResult) {
PolyMesh m_pmesh = rcResult.getMesh();
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
for (int i = 0; i < m_pmesh.npolys; ++i) {
m_pmesh.flags[i] = 1;
}
option.tileX = tilex;
option.tileZ = tileZ;
option.verts = m_pmesh.verts;
option.vertCount = m_pmesh.nverts;
option.polys = m_pmesh.polys;
option.polyAreas = m_pmesh.areas;
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
if (m_dmesh != null) {
option.detailMeshes = m_dmesh.meshes;
option.detailVerts = m_dmesh.verts;
option.detailVertsCount = m_dmesh.nverts;
option.detailTris = m_dmesh.tris;
option.detailTriCount = m_dmesh.ntris;
}
option.walkableHeight = config.walkableHeight;
option.walkableRadius = config.walkableRadius;
option.walkableClimb = config.walkableClimb;
option.bmin = m_pmesh.bmin;
option.bmax = m_pmesh.bmax;
option.cs = cellSize;
option.ch = cellHeight;
option.buildBvTree = true;
option.offMeshConCount = 0;
option.offMeshConVerts = new float[0];
option.offMeshConRad = new float[0];
option.offMeshConDir = new int[0];
option.offMeshConAreas = new int[0];
option.offMeshConFlags = new int[0];
option.offMeshConUserID = new int[0];
return option;
}
public void addTo(NavMesh navMesh) {
if (meshData != null) {
id = navMesh.addTile(meshData, 0, 0);
} else {
navMesh.removeTile(id);
id = 0;
}
}
}

View File

@ -0,0 +1,61 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using DotRecast.Recast;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Dynamic;
public class DynamicTileCheckpoint {
public readonly Heightfield heightfield;
public readonly ISet<long> colliders;
public DynamicTileCheckpoint(Heightfield heightfield, ISet<long> colliders) {
this.colliders = colliders;
this.heightfield = clone(heightfield);
}
private Heightfield clone(Heightfield source) {
Heightfield clone = new Heightfield(source.width, source.height, vCopy(source.bmin), vCopy(source.bmax), source.cs,
source.ch, source.borderSize);
for (int z = 0, pz = 0; z < source.height; z++, pz += source.width) {
for (int x = 0; x < source.width; x++) {
Span span = source.spans[pz + x];
Span prevCopy = null;
while (span != null) {
Span copy = new Span();
copy.smin = span.smin;
copy.smax = span.smax;
copy.area = span.area;
if (prevCopy == null) {
clone.spans[pz + x] = copy;
} else {
prevCopy.next = copy;
}
prevCopy = copy;
span = span.next;
}
}
}
return clone;
}
}

View File

@ -0,0 +1,77 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Core;
namespace DotRecast.Detour.Dynamic.Io;
public static class ByteUtils {
public static int getInt(byte[] data, int position, ByteOrder order) {
return order == ByteOrder.BIG_ENDIAN ? getIntBE(data, position) : getIntLE(data, position);
}
public static int getIntBE(byte[] data, int position) {
return ((data[position] & 0xff) << 24) | ((data[position + 1] & 0xff) << 16) | ((data[position + 2] & 0xff) << 8)
| (data[position + 3] & 0xff);
}
public static int getIntLE(byte[] data, int position) {
return ((data[position + 3] & 0xff) << 24) | ((data[position + 2] & 0xff) << 16) | ((data[position + 1] & 0xff) << 8)
| (data[position] & 0xff);
}
public static int getShort(byte[] data, int position, ByteOrder order) {
return order == ByteOrder.BIG_ENDIAN ? getShortBE(data, position) : getShortLE(data, position);
}
public static int getShortBE(byte[] data, int position) {
return ((data[position] & 0xff) << 8) | (data[position + 1] & 0xff);
}
public static int getShortLE(byte[] data, int position) {
return ((data[position + 1] & 0xff) << 8) | (data[position] & 0xff);
}
public static int putInt(int value, byte[] data, int position, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
data[position] = (byte) (value >>> 24);
data[position + 1] = (byte) (value >>> 16);
data[position + 2] = (byte) (value >>> 8);
data[position + 3] = (byte) (value & 0xFF);
} else {
data[position] = (byte) (value & 0xFF);
data[position + 1] = (byte) (value >>> 8);
data[position + 2] = (byte) (value >>> 16);
data[position + 3] = (byte) (value >>> 24);
}
return position + 4;
}
public static int putShort(int value, byte[] data, int position, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
data[position] = (byte) (value >>> 8);
data[position + 1] = (byte) (value & 0xFF);
} else {
data[position] = (byte) (value & 0xFF);
data[position + 1] = (byte) (value >>> 8);
}
return position + 2;
}
}

View File

@ -0,0 +1,40 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Core;
using K4os.Compression.LZ4;
namespace DotRecast.Detour.Dynamic.Io;
public class LZ4VoxelTileCompressor {
public byte[] decompress(byte[] data) {
int compressedSize = ByteUtils.getIntBE(data, 0);
return LZ4Pickler.Unpickle(data.AsSpan(4, compressedSize));
}
public byte[] compress(byte[] data) {
byte[] compressed = LZ4Pickler.Pickle(data, LZ4Level.L12_MAX);
byte[] result = new byte[4 + compressed.Length];
ByteUtils.putInt(compressed.Length, result, 0, ByteOrder.BIG_ENDIAN);
Array.Copy(compressed, 0, result, 4, compressed.Length);
return result;
}
}

View File

@ -0,0 +1,148 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using DotRecast.Core;
using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Io;
public class VoxelFile {
public static readonly ByteOrder PREFERRED_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
public const int MAGIC = 'V' << 24 | 'O' << 16 | 'X' << 8 | 'L';
public const int VERSION_EXPORTER_MASK = 0xF000;
public const int VERSION_COMPRESSION_MASK = 0x0F00;
public const int VERSION_EXPORTER_RECAST4J = 0x1000;
public const int VERSION_COMPRESSION_LZ4 = 0x0100;
public int version;
public PartitionType partitionType = PartitionType.WATERSHED;
public bool filterLowHangingObstacles = true;
public bool filterLedgeSpans = true;
public bool filterWalkableLowHeightSpans = true;
public float walkableRadius;
public float walkableHeight;
public float walkableClimb;
public float walkableSlopeAngle;
public float cellSize;
public float maxSimplificationError;
public float maxEdgeLen;
public float minRegionArea;
public float regionMergeArea;
public int vertsPerPoly;
public bool buildMeshDetail;
public float detailSampleDistance;
public float detailSampleMaxError;
public bool useTiles;
public int tileSizeX;
public int tileSizeZ;
public float[] rotation = new float[3];
public float[] bounds = new float[6];
public readonly List<VoxelTile> tiles = new();
public void addTile(VoxelTile tile) {
tiles.Add(tile);
}
public RecastConfig getConfig(VoxelTile tile, PartitionType partitionType, int maxPolyVerts, int regionMergeSize,
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans,
AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError) {
return new RecastConfig(useTiles, tileSizeX, tileSizeZ, tile.borderSize, partitionType, cellSize, tile.cellHeight,
walkableSlopeAngle, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, walkableHeight,
walkableRadius, walkableClimb, minRegionArea, regionMergeArea, maxEdgeLen, maxSimplificationError, maxPolyVerts,
buildMeshDetail, detailSampleDist, detailSampleMaxError, walkbableAreaMod);
}
public static VoxelFile from(RecastConfig config, List<RecastBuilderResult> results) {
VoxelFile f = new VoxelFile();
f.version = 1;
f.partitionType = config.partitionType;
f.filterLowHangingObstacles = config.filterLowHangingObstacles;
f.filterLedgeSpans = config.filterLedgeSpans;
f.filterWalkableLowHeightSpans = config.filterWalkableLowHeightSpans;
f.walkableRadius = config.walkableRadiusWorld;
f.walkableHeight = config.walkableHeightWorld;
f.walkableClimb = config.walkableClimbWorld;
f.walkableSlopeAngle = config.walkableSlopeAngle;
f.cellSize = config.cs;
f.maxSimplificationError = config.maxSimplificationError;
f.maxEdgeLen = config.maxEdgeLenWorld;
f.minRegionArea = config.minRegionAreaWorld;
f.regionMergeArea = config.mergeRegionAreaWorld;
f.vertsPerPoly = config.maxVertsPerPoly;
f.buildMeshDetail = config.buildMeshDetail;
f.detailSampleDistance = config.detailSampleDist;
f.detailSampleMaxError = config.detailSampleMaxError;
f.useTiles = config.useTiles;
f.tileSizeX = config.tileSizeX;
f.tileSizeZ = config.tileSizeZ;
f.bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
foreach (RecastBuilderResult r in results) {
f.tiles.Add(new VoxelTile(r.tileX, r.tileZ, r.getSolidHeightfield()));
f.bounds[0] = Math.Min(f.bounds[0], r.getSolidHeightfield().bmin[0]);
f.bounds[1] = Math.Min(f.bounds[1], r.getSolidHeightfield().bmin[1]);
f.bounds[2] = Math.Min(f.bounds[2], r.getSolidHeightfield().bmin[2]);
f.bounds[3] = Math.Max(f.bounds[3], r.getSolidHeightfield().bmax[0]);
f.bounds[4] = Math.Max(f.bounds[4], r.getSolidHeightfield().bmax[1]);
f.bounds[5] = Math.Max(f.bounds[5], r.getSolidHeightfield().bmax[2]);
}
return f;
}
public static VoxelFile from(DynamicNavMesh mesh) {
VoxelFile f = new VoxelFile();
f.version = 1;
DynamicNavMeshConfig config = mesh.config;
f.partitionType = config.partitionType;
f.filterLowHangingObstacles = config.filterLowHangingObstacles;
f.filterLedgeSpans = config.filterLedgeSpans;
f.filterWalkableLowHeightSpans = config.filterWalkableLowHeightSpans;
f.walkableRadius = config.walkableRadius;
f.walkableHeight = config.walkableHeight;
f.walkableClimb = config.walkableClimb;
f.walkableSlopeAngle = config.walkableSlopeAngle;
f.cellSize = config.cellSize;
f.maxSimplificationError = config.maxSimplificationError;
f.maxEdgeLen = config.maxEdgeLen;
f.minRegionArea = config.minRegionArea;
f.regionMergeArea = config.regionMergeArea;
f.vertsPerPoly = config.vertsPerPoly;
f.buildMeshDetail = config.buildDetailMesh;
f.detailSampleDistance = config.detailSampleDistance;
f.detailSampleMaxError = config.detailSampleMaxError;
f.useTiles = config.useTiles;
f.tileSizeX = config.tileSizeX;
f.tileSizeZ = config.tileSizeZ;
f.bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
foreach (VoxelTile vt in mesh.voxelTiles()) {
Heightfield heightfield = vt.heightfield();
f.tiles.Add(new VoxelTile(vt.tileX, vt.tileZ, heightfield));
f.bounds[0] = Math.Min(f.bounds[0], vt.boundsMin[0]);
f.bounds[1] = Math.Min(f.bounds[1], vt.boundsMin[1]);
f.bounds[2] = Math.Min(f.bounds[2], vt.boundsMin[2]);
f.bounds[3] = Math.Max(f.bounds[3], vt.boundsMax[0]);
f.bounds[4] = Math.Max(f.bounds[4], vt.boundsMax[1]);
f.bounds[5] = Math.Max(f.bounds[5], vt.boundsMax[2]);
}
return f;
}
}

View File

@ -0,0 +1,125 @@
/*
recast4j copyright (c) 2021 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
namespace DotRecast.Detour.Dynamic.Io;
public class VoxelFileReader {
private readonly LZ4VoxelTileCompressor compressor = new LZ4VoxelTileCompressor();
public VoxelFile read(BinaryReader stream) {
ByteBuffer buf = IOUtils.toByteBuffer(stream);
VoxelFile file = new VoxelFile();
int magic = buf.getInt();
if (magic != VoxelFile.MAGIC) {
magic = IOUtils.swapEndianness(magic);
if (magic != VoxelFile.MAGIC) {
throw new IOException("Invalid magic");
}
buf.order(buf.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
file.version = buf.getInt();
bool isExportedFromAstar = (file.version & VoxelFile.VERSION_EXPORTER_MASK) == 0;
bool compression = (file.version & VoxelFile.VERSION_COMPRESSION_MASK) == VoxelFile.VERSION_COMPRESSION_LZ4;
file.walkableRadius = buf.getFloat();
file.walkableHeight = buf.getFloat();
file.walkableClimb = buf.getFloat();
file.walkableSlopeAngle = buf.getFloat();
file.cellSize = buf.getFloat();
file.maxSimplificationError = buf.getFloat();
file.maxEdgeLen = buf.getFloat();
file.minRegionArea = (int) buf.getFloat();
if (!isExportedFromAstar) {
file.regionMergeArea = buf.getFloat();
file.vertsPerPoly = buf.getInt();
file.buildMeshDetail = buf.get() != 0;
file.detailSampleDistance = buf.getFloat();
file.detailSampleMaxError = buf.getFloat();
} else {
file.regionMergeArea = 6 * file.minRegionArea;
file.vertsPerPoly = 6;
file.buildMeshDetail = true;
file.detailSampleDistance = file.maxEdgeLen * 0.5f;
file.detailSampleMaxError = file.maxSimplificationError * 0.8f;
}
file.useTiles = buf.get() != 0;
file.tileSizeX = buf.getInt();
file.tileSizeZ = buf.getInt();
file.rotation[0] = buf.getFloat();
file.rotation[1] = buf.getFloat();
file.rotation[2] = buf.getFloat();
file.bounds[0] = buf.getFloat();
file.bounds[1] = buf.getFloat();
file.bounds[2] = buf.getFloat();
file.bounds[3] = buf.getFloat();
file.bounds[4] = buf.getFloat();
file.bounds[5] = buf.getFloat();
if (isExportedFromAstar) {
// bounds are saved as center + size
file.bounds[0] -= 0.5f * file.bounds[3];
file.bounds[1] -= 0.5f * file.bounds[4];
file.bounds[2] -= 0.5f * file.bounds[5];
file.bounds[3] += file.bounds[0];
file.bounds[4] += file.bounds[1];
file.bounds[5] += file.bounds[2];
}
int tileCount = buf.getInt();
for (int tile = 0; tile < tileCount; tile++) {
int tileX = buf.getInt();
int tileZ = buf.getInt();
int width = buf.getInt();
int depth = buf.getInt();
int borderSize = buf.getInt();
float[] boundsMin = new float[3];
boundsMin[0] = buf.getFloat();
boundsMin[1] = buf.getFloat();
boundsMin[2] = buf.getFloat();
float[] boundsMax = new float[3];
boundsMax[0] = buf.getFloat();
boundsMax[1] = buf.getFloat();
boundsMax[2] = buf.getFloat();
if (isExportedFromAstar) {
// bounds are local
boundsMin[0] += file.bounds[0];
boundsMin[1] += file.bounds[1];
boundsMin[2] += file.bounds[2];
boundsMax[0] += file.bounds[0];
boundsMax[1] += file.bounds[1];
boundsMax[2] += file.bounds[2];
}
float cellSize = buf.getFloat();
float cellHeight = buf.getFloat();
int voxelSize = buf.getInt();
int position = buf.position();
byte[] bytes = buf.ReadBytes(voxelSize).ToArray();
if (compression) {
bytes = compressor.decompress(bytes);
}
ByteBuffer data = new ByteBuffer(bytes);
data.order(buf.order());
file.addTile(new VoxelTile(tileX, tileZ, width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize, data));
buf.position(position + voxelSize);
}
return file;
}
}

View File

@ -0,0 +1,89 @@
/*
recast4j copyright (c) 2021 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
namespace DotRecast.Detour.Dynamic.Io;
public class VoxelFileWriter : DetourWriter {
private readonly LZ4VoxelTileCompressor compressor = new LZ4VoxelTileCompressor();
public void write(BinaryWriter stream, VoxelFile f, bool compression) {
write(stream, f, VoxelFile.PREFERRED_BYTE_ORDER, compression);
}
public void write(BinaryWriter stream, VoxelFile f, ByteOrder byteOrder, bool compression) {
write(stream, VoxelFile.MAGIC, byteOrder);
write(stream, VoxelFile.VERSION_EXPORTER_RECAST4J | (compression ? VoxelFile.VERSION_COMPRESSION_LZ4 : 0), byteOrder);
write(stream, f.walkableRadius, byteOrder);
write(stream, f.walkableHeight, byteOrder);
write(stream, f.walkableClimb, byteOrder);
write(stream, f.walkableSlopeAngle, byteOrder);
write(stream, f.cellSize, byteOrder);
write(stream, f.maxSimplificationError, byteOrder);
write(stream, f.maxEdgeLen, byteOrder);
write(stream, f.minRegionArea, byteOrder);
write(stream, f.regionMergeArea, byteOrder);
write(stream, f.vertsPerPoly, byteOrder);
write(stream, f.buildMeshDetail);
write(stream, f.detailSampleDistance, byteOrder);
write(stream, f.detailSampleMaxError, byteOrder);
write(stream, f.useTiles);
write(stream, f.tileSizeX, byteOrder);
write(stream, f.tileSizeZ, byteOrder);
write(stream, f.rotation[0], byteOrder);
write(stream, f.rotation[1], byteOrder);
write(stream, f.rotation[2], byteOrder);
write(stream, f.bounds[0], byteOrder);
write(stream, f.bounds[1], byteOrder);
write(stream, f.bounds[2], byteOrder);
write(stream, f.bounds[3], byteOrder);
write(stream, f.bounds[4], byteOrder);
write(stream, f.bounds[5], byteOrder);
write(stream, f.tiles.Count, byteOrder);
foreach (VoxelTile t in f.tiles) {
writeTile(stream, t, byteOrder, compression);
}
}
public void writeTile(BinaryWriter stream, VoxelTile tile, ByteOrder byteOrder, bool compression) {
write(stream, tile.tileX, byteOrder);
write(stream, tile.tileZ, byteOrder);
write(stream, tile.width, byteOrder);
write(stream, tile.depth, byteOrder);
write(stream, tile.borderSize, byteOrder);
write(stream, tile.boundsMin[0], byteOrder);
write(stream, tile.boundsMin[1], byteOrder);
write(stream, tile.boundsMin[2], byteOrder);
write(stream, tile.boundsMax[0], byteOrder);
write(stream, tile.boundsMax[1], byteOrder);
write(stream, tile.boundsMax[2], byteOrder);
write(stream, tile.cellSize, byteOrder);
write(stream, tile.cellHeight, byteOrder);
byte[] bytes = tile.spanData;
if (compression) {
bytes = compressor.compress(bytes);
}
write(stream, bytes.Length, byteOrder);
stream.Write(bytes);
}
}

View File

@ -0,0 +1,181 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Core;
using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Io;
public class VoxelTile {
private const int SERIALIZED_SPAN_COUNT_BYTES = 2;
private const int SERIALIZED_SPAN_BYTES = 12;
public readonly int tileX;
public readonly int tileZ;
public readonly int borderSize;
public int width;
public int depth;
public readonly float[] boundsMin;
public float[] boundsMax;
public float cellSize;
public float cellHeight;
public readonly byte[] spanData;
public VoxelTile(int tileX, int tileZ, int width, int depth, float[] boundsMin, float[] boundsMax, float cellSize,
float cellHeight, int borderSize, ByteBuffer buffer) {
this.tileX = tileX;
this.tileZ = tileZ;
this.width = width;
this.depth = depth;
this.boundsMin = boundsMin;
this.boundsMax = boundsMax;
this.cellSize = cellSize;
this.cellHeight = cellHeight;
this.borderSize = borderSize;
spanData = toByteArray(buffer, width, depth, VoxelFile.PREFERRED_BYTE_ORDER);
}
public VoxelTile(int tileX, int tileZ, Heightfield heightfield) {
this.tileX = tileX;
this.tileZ = tileZ;
width = heightfield.width;
depth = heightfield.height;
boundsMin = heightfield.bmin;
boundsMax = heightfield.bmax;
cellSize = heightfield.cs;
cellHeight = heightfield.ch;
borderSize = heightfield.borderSize;
spanData = serializeSpans(heightfield, VoxelFile.PREFERRED_BYTE_ORDER);
}
public Heightfield heightfield() {
return VoxelFile.PREFERRED_BYTE_ORDER == ByteOrder.BIG_ENDIAN ? heightfieldBE() : heightfieldLE();
}
private Heightfield heightfieldBE() {
Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize);
int position = 0;
for (int z = 0, pz = 0; z < depth; z++, pz += width) {
for (int x = 0; x < width; x++) {
Span prev = null;
int spanCount = ByteUtils.getShortBE(spanData, position);
position += 2;
for (int s = 0; s < spanCount; s++) {
Span span = new Span();
span.smin = ByteUtils.getIntBE(spanData, position);
position += 4;
span.smax = ByteUtils.getIntBE(spanData, position);
position += 4;
span.area = ByteUtils.getIntBE(spanData, position);
position += 4;
if (prev == null) {
hf.spans[pz + x] = span;
} else {
prev.next = span;
}
prev = span;
}
}
}
return hf;
}
private Heightfield heightfieldLE() {
Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize);
int position = 0;
for (int z = 0, pz = 0; z < depth; z++, pz += width) {
for (int x = 0; x < width; x++) {
Span prev = null;
int spanCount = ByteUtils.getShortLE(spanData, position);
position += 2;
for (int s = 0; s < spanCount; s++) {
Span span = new Span();
span.smin = ByteUtils.getIntLE(spanData, position);
position += 4;
span.smax = ByteUtils.getIntLE(spanData, position);
position += 4;
span.area = ByteUtils.getIntLE(spanData, position);
position += 4;
if (prev == null) {
hf.spans[pz + x] = span;
} else {
prev.next = span;
}
prev = span;
}
}
}
return hf;
}
private byte[] serializeSpans(Heightfield heightfield, ByteOrder order) {
int[] counts = new int[heightfield.width * heightfield.height];
int totalCount = 0;
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width) {
for (int x = 0; x < heightfield.width; x++) {
Span span = heightfield.spans[pz + x];
while (span != null) {
counts[pz + x]++;
totalCount++;
span = span.next;
}
}
}
byte[] data = new byte[totalCount * SERIALIZED_SPAN_BYTES + counts.Length * SERIALIZED_SPAN_COUNT_BYTES];
int position = 0;
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width) {
for (int x = 0; x < heightfield.width; x++) {
position = ByteUtils.putShort(counts[pz + x], data, position, order);
Span span = heightfield.spans[pz + x];
while (span != null) {
position = ByteUtils.putInt(span.smin, data, position, order);
position = ByteUtils.putInt(span.smax, data, position, order);
position = ByteUtils.putInt(span.area, data, position, order);
span = span.next;
}
}
}
return data;
}
private byte[] toByteArray(ByteBuffer buf, int width, int height, ByteOrder order) {
byte[] data;
if (buf.order() == order) {
data = buf.ReadBytes(buf.limit()).ToArray();
} else {
data = new byte[buf.limit()];
int l = width * height;
int position = 0;
for (int i = 0; i < l; i++) {
int count = buf.getShort();
ByteUtils.putShort(count, data, position, order);
position += 2;
for (int j = 0; j < count; j++) {
ByteUtils.putInt(buf.getInt(), data, position, order);
position += 4;
ByteUtils.putInt(buf.getInt(), data, position, order);
position += 4;
ByteUtils.putInt(buf.getInt(), data, position, order);
position += 4;
}
}
}
return data;
}
}

View File

@ -0,0 +1,41 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
namespace DotRecast.Detour.Dynamic;
public class RemoveColliderQueueItem : UpdateQueueItem {
private readonly long colliderId;
private readonly ICollection<DynamicTile> _affectedTiles;
public RemoveColliderQueueItem(long colliderId, ICollection<DynamicTile> affectedTiles) {
this.colliderId = colliderId;
this._affectedTiles = affectedTiles;
}
public ICollection<DynamicTile> affectedTiles() {
return _affectedTiles;
}
public void process(DynamicTile tile) {
tile.removeCollider(colliderId);
}
}

View File

@ -0,0 +1,29 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
namespace DotRecast.Detour.Dynamic;
public interface UpdateQueueItem {
ICollection<DynamicTile> affectedTiles();
void process(DynamicTile tile);
}

View File

@ -0,0 +1,160 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic;
/**
* Voxel raycast based on the algorithm described in
*
* "A Fast Voxel Traversal Algorithm for Ray Tracing" by John Amanatides and Andrew Woo
*/
public class VoxelQuery {
private readonly float[] origin;
private readonly float tileWidth;
private readonly float tileDepth;
private readonly Func<int, int, Heightfield> heightfieldProvider;
public VoxelQuery(float[] origin, float tileWidth, float tileDepth, Func<int, int, Heightfield> heightfieldProvider) {
this.origin = origin;
this.tileWidth = tileWidth;
this.tileDepth = tileDepth;
this.heightfieldProvider = heightfieldProvider;
}
/**
* Perform raycast using voxels heightfields.
*
* @return Optional with hit parameter (t) or empty if no hit found
*/
public float? raycast(float[] start, float[] end) {
return traverseTiles(start, end);
}
private float? traverseTiles(float[] start, float[] end) {
float relStartX = start[0] - origin[0];
float relStartZ = start[2] - origin[2];
int sx = (int) Math.Floor(relStartX / tileWidth);
int sz = (int) Math.Floor(relStartZ / tileDepth);
int ex = (int) Math.Floor((end[0] - origin[0]) / tileWidth);
int ez = (int) Math.Floor((end[2] - origin[2]) / tileDepth);
int dx = ex - sx;
int dz = ez - sz;
int stepX = dx < 0 ? -1 : 1;
int stepZ = dz < 0 ? -1 : 1;
float xRem = (tileWidth + (relStartX % tileWidth)) % tileWidth;
float zRem = (tileDepth + (relStartZ % tileDepth)) % tileDepth;
float tx = end[0] - start[0];
float tz = end[2] - start[2];
float xOffest = Math.Abs(tx < 0 ? xRem : tileWidth - xRem);
float zOffest = Math.Abs(tz < 0 ? zRem : tileDepth - zRem);
tx = Math.Abs(tx);
tz = Math.Abs(tz);
float tMaxX = xOffest / tx;
float tMaxZ = zOffest / tz;
float tDeltaX = tileWidth / tx;
float tDeltaZ = tileDepth / tz;
float t = 0;
while (true) {
float? hit = traversHeightfield(sx, sz, start, end, t, Math.Min(1, Math.Min(tMaxX, tMaxZ)));
if (hit.HasValue) {
return hit;
}
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez)) {
break;
}
if (tMaxX < tMaxZ) {
t = tMaxX;
tMaxX += tDeltaX;
sx += stepX;
} else {
t = tMaxZ;
tMaxZ += tDeltaZ;
sz += stepZ;
}
}
return null;
}
private float? traversHeightfield(int x, int z, float[] start, float[] end, float tMin, float tMax) {
Heightfield hf = heightfieldProvider.Invoke(x, z);
if (null != hf) {
float tx = end[0] - start[0];
float ty = end[1] - start[1];
float tz = end[2] - start[2];
float[] entry = { start[0] + tMin * tx, start[1] + tMin * ty, start[2] + tMin * tz };
float[] exit = { start[0] + tMax * tx, start[1] + tMax * ty, start[2] + tMax * tz };
float relStartX = entry[0] - hf.bmin[0];
float relStartZ = entry[2] - hf.bmin[2];
int sx = (int) Math.Floor(relStartX / hf.cs);
int sz = (int) Math.Floor(relStartZ / hf.cs);
int ex = (int) Math.Floor((exit[0] - hf.bmin[0]) / hf.cs);
int ez = (int) Math.Floor((exit[2] - hf.bmin[2]) / hf.cs);
int dx = ex - sx;
int dz = ez - sz;
int stepX = dx < 0 ? -1 : 1;
int stepZ = dz < 0 ? -1 : 1;
float xRem = (hf.cs + (relStartX % hf.cs)) % hf.cs;
float zRem = (hf.cs + (relStartZ % hf.cs)) % hf.cs;
float xOffest = Math.Abs(tx < 0 ? xRem : hf.cs - xRem);
float zOffest = Math.Abs(tz < 0 ? zRem : hf.cs - zRem);
tx = Math.Abs(tx);
tz = Math.Abs(tz);
float tMaxX = xOffest / tx;
float tMaxZ = zOffest / tz;
float tDeltaX = hf.cs / tx;
float tDeltaZ = hf.cs / tz;
float t = 0;
while (true) {
if (sx >= 0 && sx < hf.width && sz >= 0 && sz < hf.height) {
float y1 = start[1] + ty * (tMin + t) - hf.bmin[1];
float y2 = start[1] + ty * (tMin + Math.Min(tMaxX, tMaxZ)) - hf.bmin[1];
float ymin = Math.Min(y1, y2) / hf.ch;
float ymax = Math.Max(y1, y2) / hf.ch;
Span span = hf.spans[sx + sz * hf.width];
while (span != null) {
if (span.smin <= ymin && span.smax >= ymax) {
return Math.Min(1, tMin + t);
}
span = span.next;
}
}
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez)) {
break;
}
if (tMaxX < tMaxZ) {
t = tMaxX;
tMaxX += tDeltaX;
sx += stepX;
} else {
t = tMaxZ;
tMaxZ += tDeltaZ;
sz += stepZ;
}
}
}
return null;
}
}

View File

@ -0,0 +1,55 @@
/*
recast4j copyright (c) 2021 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 static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras;
public class BVTreeBuilder {
public void build(MeshData data) {
data.bvTree = new BVNode[data.header.polyCount * 2];
data.header.bvNodeCount = data.bvTree.Length == 0 ? 0
: createBVTree(data, data.bvTree, data.header.bvQuantFactor);
}
private static int createBVTree(MeshData data, BVNode[] nodes, float quantFactor) {
NavMeshBuilder.BVItem[] items = new NavMeshBuilder.BVItem[data.header.polyCount];
for (int i = 0; i < data.header.polyCount; i++) {
NavMeshBuilder.BVItem it = new NavMeshBuilder.BVItem();
items[i] = it;
it.i = i;
float[] bmin = new float[3];
float[] bmax = new float[3];
vCopy(bmin, data.verts, data.polys[i].verts[0] * 3);
vCopy(bmax, data.verts, data.polys[i].verts[0] * 3);
for (int j = 1; j < data.polys[i].vertCount; j++) {
vMin(bmin, data.verts, data.polys[i].verts[j] * 3);
vMax(bmax, data.verts, data.polys[i].verts[j] * 3);
}
it.bmin[0] = clamp((int) ((bmin[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff);
it.bmin[1] = clamp((int) ((bmin[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
it.bmin[2] = clamp((int) ((bmin[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
it.bmax[0] = clamp((int) ((bmax[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff);
it.bmax[1] = clamp((int) ((bmax[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
it.bmax[2] = clamp((int) ((bmax[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
}
return NavMeshBuilder.subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes);
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" />
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,48 @@
using System;
using DotRecast.Recast;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink;
public abstract class AbstractGroundSampler : GroundSampler {
protected void sampleGround(JumpLinkBuilderConfig acfg, EdgeSampler es,
Func<float[], float, Tuple<bool, float>> heightFunc) {
float cs = acfg.cellSize;
float dist = (float) Math.Sqrt(vDist2DSqr(es.start.p, es.start.q));
int ngsamples = Math.Max(2, (int) Math.Ceiling(dist / cs));
sampleGroundSegment(heightFunc, es.start, ngsamples);
foreach (GroundSegment end in es.end) {
sampleGroundSegment(heightFunc, end, ngsamples);
}
}
public void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es)
{
throw new NotImplementedException();
}
protected void sampleGroundSegment(Func<float[], float, Tuple<bool, float>> heightFunc, GroundSegment seg,
int nsamples) {
seg.gsamples = new GroundSample[nsamples];
for (int i = 0; i < nsamples; ++i) {
float u = i / (float) (nsamples - 1);
GroundSample s = new GroundSample();
seg.gsamples[i] = s;
float[] pt = vLerp(seg.p, seg.q, u);
Tuple<bool, float> height = heightFunc.Invoke(pt, seg.height);
s.p[0] = pt[0];
s.p[1] = height.Item2;
s.p[2] = pt[2];
if (!height.Item1) {
continue;
}
s.validHeight = true;
}
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace DotRecast.Detour.Extras.Jumplink;
public class ClimbTrajectory : Trajectory {
public override float[] apply(float[] start, float[] end, float u) {
return new float[] { lerp(start[0], end[0], Math.Min(2f * u, 1f)),
lerp(start[1], end[1], Math.Max(0f, 2f * u - 1f)),
lerp(start[2], end[2], Math.Min(2f * u, 1f)) };
}
}

View File

@ -0,0 +1,6 @@
namespace DotRecast.Detour.Extras.Jumplink;
public class Edge {
public readonly float[] sp = new float[3];
public readonly float[] sq = new float[3];
}

View File

@ -0,0 +1,58 @@
using System.Collections.Generic;
using DotRecast.Recast;
using static DotRecast.Recast.RecastConstants;
namespace DotRecast.Detour.Extras.Jumplink;
public class EdgeExtractor {
public Edge[] extractEdges(PolyMesh mesh) {
List<Edge> edges = new();
if (mesh != null) {
float[] orig = mesh.bmin;
float cs = mesh.cs;
float ch = mesh.ch;
for (int i = 0; i < mesh.npolys; i++) {
if (i > 41 || i < 41) {
// continue;
}
int nvp = mesh.nvp;
int p = i * 2 * nvp;
for (int j = 0; j < nvp; ++j) {
if (j != 1) {
// continue;
}
if (mesh.polys[p + j] == RC_MESH_NULL_IDX) {
break;
}
// Skip connected edges.
if ((mesh.polys[p + nvp + j] & 0x8000) != 0) {
int dir = mesh.polys[p + nvp + j] & 0xf;
if (dir == 0xf) {// Border
if (mesh.polys[p + nvp + j] != RC_MESH_NULL_IDX) {
continue;
}
int nj = j + 1;
if (nj >= nvp || mesh.polys[p + nj] == RC_MESH_NULL_IDX) {
nj = 0;
}
int va = mesh.polys[p + j] * 3;
int vb = mesh.polys[p + nj] * 3;
Edge e = new Edge();
e.sp[0] = orig[0] + mesh.verts[vb] * cs;
e.sp[1] = orig[1] + mesh.verts[vb + 1] * ch;
e.sp[2] = orig[2] + mesh.verts[vb + 2] * cs;
e.sq[0] = orig[0] + mesh.verts[va] * cs;
e.sq[1] = orig[1] + mesh.verts[va + 1] * ch;
e.sq[2] = orig[2] + mesh.verts[va + 2] * cs;
edges.Add(e);
}
}
}
}
}
return edges.ToArray();
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink;
public class EdgeSampler {
public readonly GroundSegment start = new GroundSegment();
public readonly List<GroundSegment> end = new();
public readonly Trajectory trajectory;
public readonly float[] ax = new float[3];
public readonly float[] ay = new float[3];
public readonly float[] az = new float[3];
public EdgeSampler(Edge edge, Trajectory trajectory) {
this.trajectory = trajectory;
vCopy(ax, vSub(edge.sq, edge.sp));
vNormalize(ax);
vSet(az, ax[2], 0, -ax[0]);
vNormalize(az);
vSet(ay, 0, 1, 0);
}
}

View File

@ -0,0 +1,79 @@
using System;
namespace DotRecast.Detour.Extras.Jumplink;
class EdgeSamplerFactory {
public EdgeSampler get(JumpLinkBuilderConfig acfg, JumpLinkType type, Edge edge) {
EdgeSampler es = null;
switch (type) {
case JumpLinkType.EDGE_JUMP:
es = initEdgeJumpSampler(acfg, edge);
break;
case JumpLinkType.EDGE_CLIMB_DOWN:
es = initClimbDownSampler(acfg, edge);
break;
case JumpLinkType.EDGE_JUMP_OVER:
default:
throw new ArgumentException("Unsupported jump type " + type);
}
return es;
}
private EdgeSampler initEdgeJumpSampler(JumpLinkBuilderConfig acfg, Edge edge) {
EdgeSampler es = new EdgeSampler(edge, new JumpTrajectory(acfg.jumpHeight));
es.start.height = acfg.agentClimb * 2;
float[] offset = new float[3];
trans2d(offset, es.az, es.ay, new float[] { acfg.startDistance, -acfg.agentClimb });
vadd(es.start.p, edge.sp, offset);
vadd(es.start.q, edge.sq, offset);
float dx = acfg.endDistance - 2 * acfg.agentRadius;
float cs = acfg.cellSize;
int nsamples = Math.Max(2, (int) Math.Ceiling(dx / cs));
for (int j = 0; j < nsamples; ++j) {
float v = (float) j / (float) (nsamples - 1);
float ox = 2 * acfg.agentRadius + dx * v;
trans2d(offset, es.az, es.ay, new float[] { ox, acfg.minHeight });
GroundSegment end = new GroundSegment();
end.height = acfg.heightRange;
vadd(end.p, edge.sp, offset);
vadd(end.q, edge.sq, offset);
es.end.Add(end);
}
return es;
}
private EdgeSampler initClimbDownSampler(JumpLinkBuilderConfig acfg, Edge edge) {
EdgeSampler es = new EdgeSampler(edge, new ClimbTrajectory());
es.start.height = acfg.agentClimb * 2;
float[] offset = new float[3];
trans2d(offset, es.az, es.ay, new float[] { acfg.startDistance, -acfg.agentClimb });
vadd(es.start.p, edge.sp, offset);
vadd(es.start.q, edge.sq, offset);
trans2d(offset, es.az, es.ay, new float[] { acfg.endDistance, acfg.minHeight });
GroundSegment end = new GroundSegment();
end.height = acfg.heightRange;
vadd(end.p, edge.sp, offset);
vadd(end.q, edge.sq, offset);
es.end.Add(end);
return es;
}
private void vadd(float[] dest, float[] v1, float[] v2) {
dest[0] = v1[0] + v2[0];
dest[1] = v1[1] + v2[1];
dest[2] = v1[2] + v2[2];
}
private void trans2d(float[] dst, float[] ax, float[] ay, float[] pt) {
dst[0] = ax[0] * pt[0] + ay[0] * pt[1];
dst[1] = ax[1] * pt[0] + ay[1] * pt[1];
dst[2] = ax[2] * pt[0] + ay[2] * pt[1];
}
}

View File

@ -0,0 +1,7 @@
namespace DotRecast.Detour.Extras.Jumplink;
public class GroundSample {
public readonly float[] p = new float[3];
public bool validTrajectory;
public bool validHeight;
}

View File

@ -0,0 +1,9 @@
using DotRecast.Recast;
namespace DotRecast.Detour.Extras.Jumplink;
public interface GroundSampler {
void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es);
}

View File

@ -0,0 +1,9 @@
namespace DotRecast.Detour.Extras.Jumplink;
public class GroundSegment {
public readonly float[] p = new float[3];
public readonly float[] q = new float[3];
public GroundSample[] gsamples;
public float height;
}

View File

@ -0,0 +1,15 @@
namespace DotRecast.Detour.Extras.Jumplink;
public class JumpLink {
public const int MAX_SPINE = 8;
public readonly int nspine = MAX_SPINE;
public readonly float[] spine0 = new float[MAX_SPINE * 3];
public readonly float[] spine1 = new float[MAX_SPINE * 3];
public GroundSample[] startSamples;
public GroundSample[] endSamples;
public GroundSegment start;
public GroundSegment end;
public Trajectory trajectory;
}

View File

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using DotRecast.Core;
using DotRecast.Recast;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink;
public class JumpLinkBuilder {
private readonly EdgeExtractor edgeExtractor = new EdgeExtractor();
private readonly EdgeSamplerFactory edgeSamplerFactory = new EdgeSamplerFactory();
private readonly GroundSampler groundSampler = new NavMeshGroundSampler();
private readonly TrajectorySampler trajectorySampler = new TrajectorySampler();
private readonly JumpSegmentBuilder jumpSegmentBuilder = new JumpSegmentBuilder();
private readonly List<Edge[]> edges;
private readonly List<RecastBuilderResult> results;
public JumpLinkBuilder(List<RecastBuilderResult> results) {
this.results = results;
edges = results.Select(r => edgeExtractor.extractEdges(r.getMesh())).ToList();
}
public List<JumpLink> build(JumpLinkBuilderConfig acfg, JumpLinkType type) {
List<JumpLink> links = new();
for (int tile = 0; tile < results.Count; tile++) {
Edge[] edges = this.edges[tile];
foreach (Edge edge in edges) {
links.AddRange(processEdge(acfg, results[tile], type, edge));
}
}
return links;
}
private List<JumpLink> processEdge(JumpLinkBuilderConfig acfg, RecastBuilderResult result, JumpLinkType type, Edge edge) {
EdgeSampler es = edgeSamplerFactory.get(acfg, type, edge);
groundSampler.sample(acfg, result, es);
trajectorySampler.sample(acfg, result.getSolidHeightfield(), es);
JumpSegment[] jumpSegments = jumpSegmentBuilder.build(acfg, es);
return buildJumpLinks(acfg, es, jumpSegments);
}
private List<JumpLink> buildJumpLinks(JumpLinkBuilderConfig acfg, EdgeSampler es, JumpSegment[] jumpSegments) {
List<JumpLink> links = new();
foreach (JumpSegment js in jumpSegments) {
float[] sp = es.start.gsamples[js.startSample].p;
float[] sq = es.start.gsamples[js.startSample + js.samples - 1].p;
GroundSegment end = es.end[js.groundSegment];
float[] ep = end.gsamples[js.startSample].p;
float[] eq = end.gsamples[js.startSample + js.samples - 1].p;
float d = Math.Min(vDist2DSqr(sp, sq), vDist2DSqr(ep, eq));
if (d >= 4 * acfg.agentRadius * acfg.agentRadius) {
JumpLink link = new JumpLink();
links.Add(link);
link.startSamples = ArrayUtils.CopyOf(es.start.gsamples, js.startSample, js.samples - js.startSample);
link.endSamples = ArrayUtils.CopyOf(end.gsamples, js.startSample, js.samples - js.startSample);
link.start = es.start;
link.end = end;
link.trajectory = es.trajectory;
for (int j = 0; j < link.nspine; ++j) {
float u = ((float) j) / (link.nspine - 1);
float[] p = es.trajectory.apply(sp, ep, u);
link.spine0[j * 3] = p[0];
link.spine0[j * 3 + 1] = p[1];
link.spine0[j * 3 + 2] = p[2];
p = es.trajectory.apply(sq, eq, u);
link.spine1[j * 3] = p[0];
link.spine1[j * 3 + 1] = p[1];
link.spine1[j * 3 + 2] = p[2];
}
}
}
return links;
}
public List<Edge[]> getEdges() {
return edges;
}
}

View File

@ -0,0 +1,33 @@
namespace DotRecast.Detour.Extras.Jumplink;
public class JumpLinkBuilderConfig {
public readonly float cellSize;
public readonly float cellHeight;
public readonly float agentClimb;
public readonly float agentRadius;
public readonly float groundTolerance;
public readonly float agentHeight;
public readonly float startDistance;
public readonly float endDistance;
public readonly float jumpHeight;
public readonly float minHeight;
public readonly float heightRange;
public JumpLinkBuilderConfig(float cellSize, float cellHeight, float agentRadius, float agentHeight,
float agentClimb, float groundTolerance, float startDistance, float endDistance, float minHeight,
float maxHeight, float jumpHeight) {
this.cellSize = cellSize;
this.cellHeight = cellHeight;
this.agentRadius = agentRadius;
this.agentClimb = agentClimb;
this.groundTolerance = groundTolerance;
this.agentHeight = agentHeight;
this.startDistance = startDistance;
this.endDistance = endDistance;
this.minHeight = minHeight;
heightRange = maxHeight - minHeight;
this.jumpHeight = jumpHeight;
}
}

View File

@ -0,0 +1,5 @@
namespace DotRecast.Detour.Extras.Jumplink;
public enum JumpLinkType {
EDGE_JUMP, EDGE_CLIMB_DOWN, EDGE_JUMP_OVER
}

View File

@ -0,0 +1,7 @@
namespace DotRecast.Detour.Extras.Jumplink;
public class JumpSegment {
public int groundSegment;
public int startSample;
public int samples;
}

View File

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using DotRecast.Core;
namespace DotRecast.Detour.Extras.Jumplink;
class JumpSegmentBuilder {
public JumpSegment[] build(JumpLinkBuilderConfig acfg, EdgeSampler es) {
int n = es.end[0].gsamples.Length;
int[][] sampleGrid = ArrayUtils.Of<int>(n, es.end.Count);
for (int j = 0; j < es.end.Count; j++) {
for (int i = 0; i < n; i++) {
sampleGrid[i][j] = -1;
}
}
// Fill connected regions
int region = 0;
for (int j = 0; j < es.end.Count; j++) {
for (int i = 0; i < n; i++) {
if (sampleGrid[i][j] == -1) {
GroundSample p = es.end[j].gsamples[i];
if (!p.validTrajectory) {
sampleGrid[i][j] = -2;
} else {
var queue = new Queue<int[]>();
queue.Enqueue(new int[] { i, j });
fill(es, sampleGrid, queue, acfg.agentClimb, region);
region++;
}
}
}
}
JumpSegment[] jumpSegments = new JumpSegment[region];
for (int i = 0; i < jumpSegments.Length; i++) {
jumpSegments[i] = new JumpSegment();
}
// Find longest segments per region
for (int j = 0; j < es.end.Count; j++) {
int l = 0;
int r = -2;
for (int i = 0; i < n + 1; i++) {
if (i == n || sampleGrid[i][j] != r) {
if (r >= 0) {
if (jumpSegments[r].samples < l) {
jumpSegments[r].samples = l;
jumpSegments[r].startSample = i - l;
jumpSegments[r].groundSegment = j;
}
}
if (i < n) {
r = sampleGrid[i][j];
}
l = 1;
} else {
l++;
}
}
}
return jumpSegments;
}
private void fill(EdgeSampler es, int[][] sampleGrid, Queue<int[]> queue, float agentClimb, int region) {
while (queue.TryDequeue(out var ij)) {
int i = ij[0];
int j = ij[1];
if (sampleGrid[i][j] == -1) {
GroundSample p = es.end[j].gsamples[i];
sampleGrid[i][j] = region;
float h = p.p[1];
if (i < sampleGrid.Length - 1) {
addNeighbour(es, queue, agentClimb, h, i + 1, j);
}
if (i > 0) {
addNeighbour(es, queue, agentClimb, h, i - 1, j);
}
if (j < sampleGrid[0].Length - 1) {
addNeighbour(es, queue, agentClimb, h, i, j + 1);
}
if (j > 0) {
addNeighbour(es, queue, agentClimb, h, i, j - 1);
}
}
}
}
private void addNeighbour(EdgeSampler es, Queue<int[]> queue, float agentClimb, float h, int i, int j) {
GroundSample q = es.end[j].gsamples[i];
if (q.validTrajectory && Math.Abs(q.p[1] - h) < agentClimb) {
queue.Enqueue(new int[] { i, j });
}
}
}

View File

@ -0,0 +1,41 @@
using System;
namespace DotRecast.Detour.Extras.Jumplink;
public class JumpTrajectory : Trajectory {
private readonly float jumpHeight;
public JumpTrajectory(float jumpHeight) {
this.jumpHeight = jumpHeight;
}
public override float[] apply(float[] start, float[] end, float u) {
return new float[] { lerp(start[0], end[0], u), interpolateHeight(start[1], end[1], u),
lerp(start[2], end[2], u) };
}
private float interpolateHeight(float ys, float ye, float u) {
if (u == 0f) {
return ys;
} else if (u == 1.0f) {
return ye;
}
float h1, h2;
if (ys >= ye) { // jump down
h1 = jumpHeight;
h2 = jumpHeight + ys - ye;
} else { // jump up
h1 = jumpHeight + ys - ye;
h2 = jumpHeight;
}
float t = (float) (Math.Sqrt(h1) / (Math.Sqrt(h2) + Math.Sqrt(h1)));
if (u <= t) {
float v1 = 1.0f - (u / t);
return ys + h1 - h1 * v1 * v1;
}
float v = (u - t) / (1.0f - t);
return ys + h1 - h2 * v * v;
}
}

View File

@ -0,0 +1,91 @@
using System;
using DotRecast.Core;
using DotRecast.Recast;
namespace DotRecast.Detour.Extras.Jumplink;
class NavMeshGroundSampler : AbstractGroundSampler {
private readonly QueryFilter filter = new NoOpFilter();
private class NoOpFilter : QueryFilter {
public bool passFilter(long refs, MeshTile tile, Poly poly) {
return true;
}
public float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef,
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly) {
return 0;
}
}
public void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es) {
NavMeshQuery navMeshQuery = createNavMesh(result, acfg.agentRadius, acfg.agentHeight, acfg.agentClimb);
sampleGround(acfg, es, (pt, h) => getNavMeshHeight(navMeshQuery, pt, acfg.cellSize, h));
}
private NavMeshQuery createNavMesh(RecastBuilderResult r, float agentRadius, float agentHeight, float agentClimb) {
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = r.getMesh().verts;
option.vertCount = r.getMesh().nverts;
option.polys = r.getMesh().polys;
option.polyAreas = r.getMesh().areas;
option.polyFlags = r.getMesh().flags;
option.polyCount = r.getMesh().npolys;
option.nvp = r.getMesh().nvp;
option.detailMeshes = r.getMeshDetail().meshes;
option.detailVerts = r.getMeshDetail().verts;
option.detailVertsCount = r.getMeshDetail().nverts;
option.detailTris = r.getMeshDetail().tris;
option.detailTriCount = r.getMeshDetail().ntris;
option.walkableRadius = agentRadius;
option.walkableHeight = agentHeight;
option.walkableClimb = agentClimb;
option.bmin = r.getMesh().bmin;
option.bmax = r.getMesh().bmax;
option.cs = r.getMesh().cs;
option.ch = r.getMesh().ch;
option.buildBvTree = true;
return new NavMeshQuery(new NavMesh(NavMeshBuilder.createNavMeshData(option), option.nvp, 0));
}
public class PolyQueryInvoker : PolyQuery
{
public readonly Action<MeshTile, Poly, long> _callback;
public PolyQueryInvoker(Action<MeshTile, Poly, long> callback)
{
_callback = callback;
}
public void process(MeshTile tile, Poly poly, long refs)
{
_callback?.Invoke(tile, poly, refs);
}
}
private Tuple<bool, float> getNavMeshHeight(NavMeshQuery navMeshQuery, float[] pt, float cs,
float heightRange) {
float[] halfExtents = new float[] { cs, heightRange, cs };
float maxHeight = pt[1] + heightRange;
AtomicBoolean found = new AtomicBoolean();
AtomicFloat minHeight = new AtomicFloat(pt[1]);
navMeshQuery.queryPolygons(pt, halfExtents, filter, new PolyQueryInvoker((tile, poly, refs) => {
Result<float> h = navMeshQuery.getPolyHeight(refs, pt);
if (h.succeeded()) {
float y = h.result;
if (y > minHeight.Get() && y < maxHeight) {
minHeight.Exchange(y);
found.set(true);
}
}
}));
if (found.get()) {
return Tuple.Create(true, minHeight.Get());
}
return Tuple.Create(false, pt[1]);
}
}

View File

@ -0,0 +1,16 @@
using System;
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 float[] apply(float[] start, float[] end, float u)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,76 @@
using System;
using DotRecast.Recast;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink;
class TrajectorySampler {
public void sample(JumpLinkBuilderConfig acfg, Heightfield heightfield, EdgeSampler es) {
int nsamples = es.start.gsamples.Length;
for (int i = 0; i < nsamples; ++i) {
GroundSample ssmp = es.start.gsamples[i];
foreach (GroundSegment end in es.end) {
GroundSample esmp = end.gsamples[i];
if (!ssmp.validHeight || !esmp.validHeight) {
continue;
}
if (!sampleTrajectory(acfg, heightfield, ssmp.p, esmp.p, es.trajectory)) {
continue;
}
ssmp.validTrajectory = true;
esmp.validTrajectory = true;
}
}
}
private bool sampleTrajectory(JumpLinkBuilderConfig acfg, Heightfield solid, float[] pa, float[] pb, Trajectory tra) {
float cs = Math.Min(acfg.cellSize, acfg.cellHeight);
float d = vDist2D(pa, pb) + Math.Abs(pa[1] - pb[1]);
int nsamples = Math.Max(2, (int) Math.Ceiling(d / cs));
for (int i = 0; i < nsamples; ++i) {
float u = (float) i / (float) (nsamples - 1);
float[] p = tra.apply(pa, pb, u);
if (checkHeightfieldCollision(solid, p[0], p[1] + acfg.groundTolerance, p[1] + acfg.agentHeight, p[2])) {
return false;
}
}
return true;
}
private bool checkHeightfieldCollision(Heightfield solid, float x, float ymin, float ymax, float z) {
int w = solid.width;
int h = solid.height;
float cs = solid.cs;
float ch = solid.ch;
float[] orig = solid.bmin;
int ix = (int) Math.Floor((x - orig[0]) / cs);
int iz = (int) Math.Floor((z - orig[2]) / cs);
if (ix < 0 || iz < 0 || ix > w || iz > h) {
return false;
}
Span s = solid.spans[ix + iz * w];
if (s == null) {
return false;
}
while (s != null) {
float symin = orig[1] + s.smin * ch;
float symax = orig[1] + s.smax * ch;
if (overlapRange(ymin, ymax, symin, symax)) {
return true;
}
s = s.next;
}
return false;
}
private bool overlapRange(float amin, float amax, float bmin, float bmax) {
return (amin > bmax || amax < bmin) ? false : true;
}
}

View File

@ -0,0 +1,64 @@
/*
recast4j copyright (c) 2015-2019 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.IO;
namespace DotRecast.Detour.Extras;
public class ObjExporter {
public void export(NavMesh mesh) {
string filename = Path.Combine(Directory.GetCurrentDirectory(), "Demo", "astar.obj");
using var fs = new FileStream(filename, FileMode.CreateNew);
using var fw = new StreamWriter(fs);
for (int i = 0; i < mesh.getTileCount(); i++) {
MeshTile tile = mesh.getTile(i);
if (tile != null) {
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");
}
}
}
int vertexOffset = 1;
for (int i = 0; i < mesh.getTileCount(); i++) {
MeshTile tile = mesh.getTile(i);
if (tile != null) {
for (int p = 0; p < tile.data.header.polyCount; p++) {
fw.Write("f ");
Poly poly = tile.data.polys[p];
for (int v = 0; v < poly.vertCount; v++) {
fw.Write(poly.verts[v] + vertexOffset + " ");
}
fw.Write("\n");
}
vertexOffset += tile.data.header.vertCount;
}
}
}
/*
*
MeshSetReader reader = new MeshSetReader();
ObjExporter exporter = new ObjExporter();
exporter.export(mesh);
reader.read(new FileInputStream("/home/piotr/Downloads/graph/all_tiles_navmesh.bin"), 3);
*/
}

View File

@ -0,0 +1,80 @@
/*
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Extras;
public class PolyUtils {
/**
* Find edge shared by 2 polygons within the same tile
*/
public static int findEdge(Poly node, Poly neighbour, MeshData tile, MeshData neighbourTile) {
// Compare indices first assuming there are no duplicate vertices
for (int i = 0; i < node.vertCount; i++) {
int j = (i + 1) % node.vertCount;
for (int k = 0; k < neighbour.vertCount; k++) {
int l = (k + 1) % neighbour.vertCount;
if ((node.verts[i] == neighbour.verts[l] && node.verts[j] == neighbour.verts[k])
|| (node.verts[i] == neighbour.verts[k] && node.verts[j] == neighbour.verts[l])) {
return i;
}
}
}
// Fall back to comparing actual positions in case of duplicate vertices
for (int i = 0; i < node.vertCount; i++) {
int j = (i + 1) % node.vertCount;
for (int k = 0; k < neighbour.vertCount; k++) {
int l = (k + 1) % neighbour.vertCount;
if ((samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[l])
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[k]))
|| (samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[k])
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[l]))) {
return i;
}
}
}
return -1;
}
private static bool samePosition(float[] verts, int v, float[] verts2, int v2) {
for (int i = 0; i < 3; i++) {
if (verts[3 * v + i] != verts2[3 * v2 + 1]) {
return false;
}
}
return true;
}
/**
* Find edge closest to the given coordinate
*/
public static int findEdge(Poly node, MeshData tile, float value, int comp) {
float error = float.MaxValue;
int edge = 0;
for (int i = 0; i < node.vertCount; i++) {
int j = (i + 1) % node.vertCount;
float v1 = tile.verts[3 * node.verts[i] + comp] - value;
float v2 = tile.verts[3 * node.verts[j] + comp] - value;
float d = v1 * v1 + v2 * v2;
if (d < error) {
error = d;
edge = i;
}
}
return edge;
}
}

View File

@ -0,0 +1,30 @@
/*
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Extras.Unity.Astar;
public class BVTreeCreator {
private readonly BVTreeBuilder builder = new BVTreeBuilder();
public void build(GraphMeshData graphData) {
foreach (MeshData d in graphData.tiles) {
builder.build(d);
}
}
}

View File

@ -0,0 +1,47 @@
/*
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.IO.Compression;
using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar;
class GraphConnectionReader : ZipBinaryReader {
public List<int[]> read(ZipArchive file, string filename, Meta meta, int[] indexToNode) {
List<int[]> connections = new();
ByteBuffer buffer = toByteBuffer(file, filename);
while (buffer.remaining() > 0) {
int count = buffer.getInt();
int[] nodeConnections = new int[count];
connections.Add(nodeConnections);
for (int i = 0; i < count; i++) {
int nodeIndex = buffer.getInt();
nodeConnections[i] = indexToNode[nodeIndex];
// XXX: Is there anything we can do with the cost?
int cost = buffer.getInt();
if (meta.isVersionAtLeast(Meta.UPDATED_STRUCT_VERSION)) {
byte shapeEdge = buffer.get();
}
}
}
return connections;
}
}

View File

@ -0,0 +1,44 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class GraphData {
public readonly Meta meta;
public readonly int[] indexToNode;
public readonly NodeLink2[] nodeLinks2;
public readonly List<GraphMeta> graphMeta;
public readonly List<GraphMeshData> graphMeshData;
public readonly List<List<int[]>> graphConnections;
public GraphData(Meta meta, int[] indexToNode, NodeLink2[] nodeLinks2, List<GraphMeta> graphMeta,
List<GraphMeshData> graphMeshData, List<List<int[]>> graphConnections) {
this.meta = meta;
this.indexToNode = indexToNode;
this.nodeLinks2 = nodeLinks2;
this.graphMeta = graphMeta;
this.graphMeshData = graphMeshData;
this.graphConnections = graphConnections;
}
}

View File

@ -0,0 +1,62 @@
/*
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Extras.Unity.Astar;
public class GraphMeshData {
public readonly int tileXCount;
public readonly int tileZCount;
public readonly MeshData[] tiles;
public GraphMeshData(int tileXCount, int tileZCount, MeshData[] tiles) {
this.tileXCount = tileXCount;
this.tileZCount = tileZCount;
this.tiles = tiles;
}
public int countNodes() {
int polyCount = 0;
foreach (MeshData t in tiles) {
polyCount += t.header.polyCount;
}
return polyCount;
}
public Poly getNode(int node) {
int index = 0;
foreach (MeshData t in tiles) {
if (node - index >= 0 && node - index < t.header.polyCount) {
return t.polys[node - index];
}
index += t.header.polyCount;
}
return null;
}
public MeshData getTile(int node) {
int index = 0;
foreach (MeshData t in tiles) {
if (node - index >= 0 && node - index < t.header.polyCount) {
return t;
}
index += t.header.polyCount;
}
return null;
}
}

View File

@ -0,0 +1,151 @@
/*
recast4j copyright (c) 2015-2019 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.Compression;
using System.Numerics;
using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class GraphMeshDataReader : ZipBinaryReader {
public const float INT_PRECISION_FACTOR = 1000f;
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly) {
ByteBuffer buffer = toByteBuffer(file, filename);
int tileXCount = buffer.getInt();
if (tileXCount < 0) {
return null;
}
int tileZCount = buffer.getInt();
MeshData[] tiles = new MeshData[tileXCount * tileZCount];
for (int z = 0; z < tileZCount; z++) {
for (int x = 0; x < tileXCount; x++) {
int tileIndex = x + z * tileXCount;
int tx = buffer.getInt();
int tz = buffer.getInt();
if (tx != x || tz != z) {
throw new ArgumentException("Inconsistent tile positions");
}
tiles[tileIndex] = new MeshData();
int width = buffer.getInt();
int depth = buffer.getInt();
int trisCount = buffer.getInt();
int[] tris = new int[trisCount];
for (int i = 0; i < tris.Length; i++) {
tris[i] = buffer.getInt();
}
int vertsCount = buffer.getInt();
float[] verts = new float[3 * vertsCount];
for (int i = 0; i < verts.Length; i++) {
verts[i] = buffer.getInt() / INT_PRECISION_FACTOR;
}
int[] vertsInGraphSpace = new int[3 * buffer.getInt()];
for (int i = 0; i < vertsInGraphSpace.Length; i++) {
vertsInGraphSpace[i] = buffer.getInt();
}
int nodeCount = buffer.getInt();
Poly[] nodes = new Poly[nodeCount];
PolyDetail[] detailNodes = new PolyDetail[nodeCount];
float[] detailVerts = new float[0];
int[] detailTris = new int[4 * nodeCount];
int vertMask = getVertMask(vertsCount);
float ymin = float.PositiveInfinity;
float ymax = float.NegativeInfinity;
for (int i = 0; i < nodes.Length; i++) {
nodes[i] = new Poly(i, maxVertPerPoly);
nodes[i].vertCount = 3;
// XXX: What can we do with the penalty?
int penalty = buffer.getInt();
nodes[i].flags = buffer.getInt();
nodes[i].verts[0] = buffer.getInt() & vertMask;
nodes[i].verts[1] = buffer.getInt() & vertMask;
nodes[i].verts[2] = buffer.getInt() & vertMask;
ymin = Math.Min(ymin, verts[nodes[i].verts[0] * 3 + 1]);
ymin = Math.Min(ymin, verts[nodes[i].verts[1] * 3 + 1]);
ymin = Math.Min(ymin, verts[nodes[i].verts[2] * 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[2] * 3 + 1]);
detailNodes[i] = new PolyDetail();
detailNodes[i].vertBase = 0;
detailNodes[i].vertCount = 0;
detailNodes[i].triBase = i;
detailNodes[i].triCount = 1;
detailTris[4 * i] = 0;
detailTris[4 * i + 1] = 1;
detailTris[4 * i + 2] = 2;
// Bit for each edge that belongs to poly boundary, basically all edges marked as boundary as it is
// a triangle
detailTris[4 * i + 3] = (1 << 4) | (1 << 2) | 1;
}
tiles[tileIndex].verts = verts;
tiles[tileIndex].polys = nodes;
tiles[tileIndex].detailMeshes = detailNodes;
tiles[tileIndex].detailVerts = detailVerts;
tiles[tileIndex].detailTris = detailTris;
MeshHeader header = new MeshHeader();
header.magic = MeshHeader.DT_NAVMESH_MAGIC;
header.version = MeshHeader.DT_NAVMESH_VERSION;
header.x = x;
header.y = z;
header.polyCount = nodeCount;
header.vertCount = vertsCount;
header.detailMeshCount = nodeCount;
header.detailTriCount = nodeCount;
header.maxLinkCount = nodeCount * 3 * 2; // XXX: Needed by Recast, not needed by recast4j
header.bmin[0] = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
+ meta.cellSize * meta.tileSizeX * x;
header.bmin[1] = ymin;
header.bmin[2] = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
+ meta.cellSize * meta.tileSizeZ * z;
header.bmax[0] = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
+ meta.cellSize * meta.tileSizeX * (x + 1);
header.bmax[1] = ymax;
header.bmax[2] = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
+ meta.cellSize * meta.tileSizeZ * (z + 1);
header.bvQuantFactor = 1.0f / meta.cellSize;
header.offMeshBase = nodeCount;
header.walkableClimb = meta.walkableClimb;
header.walkableHeight = meta.walkableHeight;
header.walkableRadius = meta.characterRadius;
tiles[tileIndex].header = header;
}
}
return new GraphMeshData(tileXCount, tileZCount, tiles);
}
// See NavmeshBase.cs: ASTAR_RECAST_LARGER_TILES
private int getVertMask(int vertsCount)
{
int vertMask = 1 << (BitOperations.Log2((uint)vertsCount) + 1);
if (vertMask != vertsCount) {
vertMask *= 2;
}
vertMask--;
return vertMask;
}
}

View File

@ -0,0 +1,39 @@
/*
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Extras.Unity.Astar;
public class GraphMeta {
public float characterRadius {get;set;}
public float contourMaxError {get;set;}
public float cellSize {get;set;}
public float walkableHeight {get;set;}
public float walkableClimb {get;set;}
public float maxSlope {get;set;}
public float maxEdgeLength {get;set;}
public float minRegionSize {get;set;}
/** Size of tile along X axis in voxels */
public float tileSizeX { get; set; }
/** Size of tile along Z axis in voxels */
public float tileSizeZ { get; set; }
public bool useTiles { get; set; }
public Vector3f rotation { get; set; }
public Vector3f forcedBoundsCenter { get; set; }
public Vector3f forcedBoundsSize { get; set; }
}

View File

@ -0,0 +1,38 @@
/*
recast4j copyright (c) 2015-2019 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.IO;
using System.IO.Compression;
using System.Text.Json;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class GraphMetaReader {
public GraphMeta read(ZipArchive file, string filename) {
ZipArchiveEntry entry = file.GetEntry(filename);
using StreamReader reader = new StreamReader(entry.Open());
JsonSerializerOptions options = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = reader.ReadToEnd();
return JsonSerializer.Deserialize<GraphMeta>(json, options);
}
}

View File

@ -0,0 +1,66 @@
/*
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class LinkBuilder {
// Process connections and transform them into recast neighbour flags
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections) {
for (int n = 0; n < connections.Count; n++) {
int[] nodeConnections = connections[n];
MeshData tile = graphData.getTile(n);
Poly node = graphData.getNode(n);
foreach (int connection in nodeConnections) {
MeshData neighbourTile = graphData.getTile(connection - nodeOffset);
if (neighbourTile != tile) {
buildExternalLink(tile, node, neighbourTile);
} else {
Poly neighbour = graphData.getNode(connection - nodeOffset);
buildInternalLink(tile, node, neighbourTile, neighbour);
}
}
}
}
private void buildInternalLink(MeshData tile, Poly node, MeshData neighbourTile, Poly neighbour) {
int edge = PolyUtils.findEdge(node, neighbour, tile, neighbourTile);
if (edge >= 0) {
node.neis[edge] = neighbour.index + 1;
} else {
throw new ArgumentException();
}
}
// In case of external link to other tiles we must find the direction
private void buildExternalLink(MeshData tile, Poly node, MeshData neighbourTile) {
if (neighbourTile.header.bmin[0] > tile.header.bmin[0]) {
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[0], 0)] = NavMesh.DT_EXT_LINK;
} else if (neighbourTile.header.bmin[0] < tile.header.bmin[0]) {
node.neis[PolyUtils.findEdge(node, tile, tile.header.bmin[0], 0)] = NavMesh.DT_EXT_LINK | 4;
} else if (neighbourTile.header.bmin[2] > tile.header.bmin[2]) {
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 2;
} else {
node.neis[PolyUtils.findEdge(node, tile, tile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 6;
}
}
}

View File

@ -0,0 +1,73 @@
/*
recast4j copyright (c) 2015-2019 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.Text.RegularExpressions;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class Meta {
public const string TYPENAME_RECAST_GRAPH = "Pathfinding.RecastGraph";
public const string MIN_SUPPORTED_VERSION = "4.0.6";
public const string UPDATED_STRUCT_VERSION = "4.1.16";
public static readonly Regex VERSION_PATTERN = new Regex(@"(\d+)\.(\d+)\.(\d+)");
public string version { get; set; }
public int graphs { get; set; }
public string[] guids { get; set; }
public string[] typeNames { get; set; }
public bool isSupportedVersion() {
return isVersionAtLeast(MIN_SUPPORTED_VERSION);
}
public bool isVersionAtLeast(string minVersion) {
int[] actual = parseVersion(version);
int[] minSupported = parseVersion(minVersion);
for (int i = 0; i < Math.Min(actual.Length, minSupported.Length); i++) {
if (actual[i] > minSupported[i]) {
return true;
} else if (minSupported[i] > actual[i]) {
return false;
}
}
return true;
}
private int[] parseVersion(string version) {
Match m = VERSION_PATTERN.Match(version);
if (m.Success) {
int[] v = new int[m.Groups.Count - 1];
for (int i = 0; i < v.Length; i++) {
v[i] = int.Parse(m.Groups[i + 1].Value);
}
return v;
}
throw new ArgumentException("Invalid version format: " + version);
}
public bool isSupportedType() {
foreach (string t in typeNames) {
if (t == TYPENAME_RECAST_GRAPH) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,54 @@
/*
recast4j copyright (c) 2015-2019 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 System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class MetaReader {
public Meta read(ZipArchive file, string filename) {
ZipArchiveEntry entry = file.GetEntry(filename);
using StreamReader reader = new StreamReader(entry.Open());
var json = reader.ReadToEnd();
// fixed : version 표기는 문자열이여야 한다
string pattern = @"(\d+\.\d+\.\d+),";
string replacement = "\"$1\",";
var regex = new Regex(pattern);
json = regex.Replace(json, replacement);
var meta = JsonSerializer.Deserialize<Meta>(json);
if (!meta.isSupportedType()) {
throw new ArgumentException("Unsupported graph type " + string.Join(", ", meta.typeNames));
}
if (!meta.isSupportedVersion()) {
throw new ArgumentException("Unsupported version " + meta.version);
}
return meta;
}
}

View File

@ -0,0 +1,38 @@
/*
recast4j copyright (c) 2015-2019 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.IO.Compression;
using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar;
class NodeIndexReader : ZipBinaryReader {
public int[] read(ZipArchive file, string filename) {
ByteBuffer buffer = toByteBuffer(file, filename);
int maxNodeIndex = buffer.getInt();
int[] int2Node = new int[maxNodeIndex + 1];
int node = 0;
while (buffer.remaining() > 0) {
int index = buffer.getInt();
int2Node[index] = node++;
}
return int2Node;
}
}

View File

@ -0,0 +1,35 @@
/*
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Extras.Unity.Astar;
public class NodeLink2 {
public readonly long linkID;
public readonly int startNode;
public readonly int endNode;
public readonly Vector3f clamped1;
public readonly Vector3f clamped2;
public NodeLink2(long linkID, int startNode, int endNode, Vector3f clamped1, Vector3f clamped2) : base() {
this.linkID = linkID;
this.startNode = startNode;
this.endNode = endNode;
this.clamped1 = clamped1;
this.clamped2 = clamped2;
}
}

View File

@ -0,0 +1,49 @@
/*
recast4j copyright (c) 2015-2019 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.IO.Compression;
using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class NodeLink2Reader : ZipBinaryReader {
public NodeLink2[] read(ZipArchive file, string filename, int[] indexToNode) {
ByteBuffer buffer = toByteBuffer(file, filename);
int linkCount = buffer.getInt();
NodeLink2[] links = new NodeLink2[linkCount];
for (int i = 0; i < linkCount; i++) {
long linkID = buffer.getLong();
int startNode = indexToNode[buffer.getInt()];
int endNode = indexToNode[buffer.getInt()];
int connectedNode1 = buffer.getInt();
int connectedNode2 = buffer.getInt();
Vector3f clamped1 = new Vector3f();
clamped1.x = buffer.getFloat();
clamped1.y = buffer.getFloat();
clamped1.z = buffer.getFloat();
Vector3f clamped2 = new Vector3f();
clamped2.x = buffer.getFloat();
clamped2.y = buffer.getFloat();
clamped2.z = buffer.getFloat();
bool postScanCalled = buffer.get() != 0;
links[i] = new NodeLink2(linkID, startNode, endNode, clamped1, clamped2);
}
return links;
}
}

View File

@ -0,0 +1,63 @@
/*
recast4j copyright (c) 2015-2019 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 DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class OffMeshLinkCreator {
public void build(GraphMeshData graphData, NodeLink2[] links, int nodeOffset) {
if (links.Length > 0) {
foreach (NodeLink2 l in links) {
MeshData startTile = graphData.getTile(l.startNode - nodeOffset);
Poly startNode = graphData.getNode(l.startNode - nodeOffset);
MeshData endTile = graphData.getTile(l.endNode - nodeOffset);
Poly endNode = graphData.getNode(l.endNode - nodeOffset);
if (startNode != null && endNode != null) {
// FIXME: Optimise
startTile.polys = ArrayUtils.CopyOf(startTile.polys, startTile.polys.Length + 1);
int poly = startTile.header.polyCount;
startTile.polys[poly] = new Poly(poly, 2);
startTile.polys[poly].verts[0] = startTile.header.vertCount;
startTile.polys[poly].verts[1] = startTile.header.vertCount + 1;
startTile.polys[poly].setType(Poly.DT_POLYTYPE_OFFMESH_CONNECTION);
startTile.verts = ArrayUtils.CopyOf(startTile.verts, startTile.verts.Length + 6);
startTile.header.polyCount++;
startTile.header.vertCount += 2;
OffMeshConnection connection = new OffMeshConnection();
connection.poly = poly;
connection.pos = new float[] { l.clamped1.x, l.clamped1.y, l.clamped1.z, l.clamped2.x, l.clamped2.y,
l.clamped2.z };
connection.rad = 0.1f;
connection.side = startTile == endTile ? 0xFF
: NavMeshBuilder.classifyOffMeshPoint(new VectorPtr(connection.pos, 3),
startTile.header.bmin, startTile.header.bmax);
connection.userId = (int) l.linkID;
if (startTile.offMeshCons == null) {
startTile.offMeshCons = new OffMeshConnection[1];
} else {
startTile.offMeshCons = ArrayUtils.CopyOf(startTile.offMeshCons, startTile.offMeshCons.Length + 1);
}
startTile.offMeshCons[startTile.offMeshCons.Length - 1] = connection;
startTile.header.offMeshConCount++;
}
}
}
}
}

View File

@ -0,0 +1,75 @@
/*
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.IO;
namespace DotRecast.Detour.Extras.Unity.Astar;
/**
* Import navmeshes created with A* Pathfinding Project Unity plugin (https://arongranberg.com/astar/). Graph data is
* loaded from a zip archive and converted to Recast navmesh objects.
*/
public class UnityAStarPathfindingImporter {
private readonly UnityAStarPathfindingReader reader = new UnityAStarPathfindingReader();
private readonly BVTreeCreator bvTreeCreator = new BVTreeCreator();
private readonly LinkBuilder linkCreator = new LinkBuilder();
private readonly OffMeshLinkCreator offMeshLinkCreator = new OffMeshLinkCreator();
public NavMesh[] load(FileStream zipFile) {
GraphData graphData = reader.read(zipFile);
Meta meta = graphData.meta;
NodeLink2[] nodeLinks2 = graphData.nodeLinks2;
NavMesh[] meshes = new NavMesh[meta.graphs];
int nodeOffset = 0;
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++) {
GraphMeta graphMeta = graphData.graphMeta[graphIndex];
GraphMeshData graphMeshData = graphData.graphMeshData[graphIndex];
List<int[]> connections = graphData.graphConnections[graphIndex];
int nodeCount = graphMeshData.countNodes();
if (connections.Count != nodeCount) {
throw new ArgumentException("Inconsistent number of nodes in data file: " + nodeCount
+ " and connecton files: " + connections.Count);
}
// Build BV tree
bvTreeCreator.build(graphMeshData);
// Create links between nodes (both internal and portals between tiles)
linkCreator.build(nodeOffset, graphMeshData, connections);
// Finally, process all the off-mesh links that can be actually converted to detour data
offMeshLinkCreator.build(graphMeshData, nodeLinks2, nodeOffset);
NavMeshParams option = new NavMeshParams();
option.maxTiles = graphMeshData.tiles.Length;
option.maxPolys = 32768;
option.tileWidth = graphMeta.tileSizeX * graphMeta.cellSize;
option.tileHeight = graphMeta.tileSizeZ * graphMeta.cellSize;
option.orig[0] = -0.5f * graphMeta.forcedBoundsSize.x + graphMeta.forcedBoundsCenter.x;
option.orig[1] = -0.5f * graphMeta.forcedBoundsSize.y + graphMeta.forcedBoundsCenter.y;
option.orig[2] = -0.5f * graphMeta.forcedBoundsSize.z + graphMeta.forcedBoundsCenter.z;
NavMesh mesh = new NavMesh(option, 3);
foreach (MeshData t in graphMeshData.tiles) {
mesh.addTile(t, 0, 0);
}
meshes[graphIndex] = mesh;
nodeOffset += graphMeshData.countNodes();
}
return meshes;
}
}

View File

@ -0,0 +1,67 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using System.IO;
using System.IO.Compression;
namespace DotRecast.Detour.Extras.Unity.Astar;
public class UnityAStarPathfindingReader {
private const string META_FILE_NAME = "meta.json";
private const string NODE_INDEX_FILE_NAME = "graph_references.binary";
private const string NODE_LINK_2_FILE_NAME = "node_link2.binary";
private const string GRAPH_META_FILE_NAME_PATTERN = "graph{0}.json";
private const string GRAPH_DATA_FILE_NAME_PATTERN = "graph{0}_extra.binary";
private const string GRAPH_CONNECTION_FILE_NAME_PATTERN = "graph{0}_references.binary";
private const int MAX_VERTS_PER_POLY = 3;
private readonly MetaReader metaReader = new MetaReader();
private readonly NodeIndexReader nodeIndexReader = new NodeIndexReader();
private readonly GraphMetaReader graphMetaReader = new GraphMetaReader();
private readonly GraphMeshDataReader graphDataReader = new GraphMeshDataReader();
private readonly GraphConnectionReader graphConnectionReader = new GraphConnectionReader();
private readonly NodeLink2Reader nodeLink2Reader = new NodeLink2Reader();
public GraphData read(FileStream zipFile) {
using ZipArchive file = new ZipArchive(zipFile);
// Read meta file and check version and graph type
Meta meta = metaReader.read(file, META_FILE_NAME);
// Read index to node mapping
int[] indexToNode = nodeIndexReader.read(file, NODE_INDEX_FILE_NAME);
// Read NodeLink2 data (off-mesh links)
NodeLink2[] nodeLinks2 = nodeLink2Reader.read(file, NODE_LINK_2_FILE_NAME, indexToNode);
// Read graph by graph
List<GraphMeta> metaList = new();
List<GraphMeshData> meshDataList = new();
List<List<int[]>> connectionsList = new();
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++) {
GraphMeta graphMeta = graphMetaReader.read(file, string.Format(GRAPH_META_FILE_NAME_PATTERN, graphIndex));
// First graph mesh data - vertices and polygons
GraphMeshData graphData = graphDataReader.read(file,
string.Format(GRAPH_DATA_FILE_NAME_PATTERN, graphIndex), graphMeta, MAX_VERTS_PER_POLY);
// Then graph connection data - links between nodes located in both the same tile and other tiles
List<int[]> connections = graphConnectionReader.read(file,
string.Format(GRAPH_CONNECTION_FILE_NAME_PATTERN, graphIndex), meta, indexToNode);
metaList.Add(graphMeta);
meshDataList.Add(graphData);
connectionsList.Add(connections);
}
return new GraphData(meta, indexToNode, nodeLinks2, metaList, meshDataList, connectionsList);
}
}

View File

@ -0,0 +1,37 @@
/*
recast4j copyright (c) 2015-2019 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.IO;
using System.IO.Compression;
using DotRecast.Core;
using DotRecast.Detour.Io;
namespace DotRecast.Detour.Extras.Unity.Astar;
public abstract class ZipBinaryReader {
protected ByteBuffer toByteBuffer(ZipArchive file, string filename) {
ZipArchiveEntry graphReferences = file.GetEntry(filename);
using var entryStream = graphReferences.Open();
using var bis = new BinaryReader(entryStream);
ByteBuffer buffer = IOUtils.toByteBuffer(bis);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer;
}
}

View File

@ -0,0 +1,35 @@
/*
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.Extras;
public class Vector3f {
public float x { get; set; }
public float y { get; set; }
public float z { get; set; }
public Vector3f() {
}
public Vector3f(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
}

View File

@ -0,0 +1,77 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DotRecast.Core;
namespace DotRecast.Detour.TileCache;
public abstract class AbstractTileLayersBuilder {
protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th) {
if (threads == 1) {
return buildSingleThread(order, cCompatibility, tw, th);
}
return buildMultiThread(order, cCompatibility, tw, th, threads);
}
private List<byte[]> buildSingleThread(ByteOrder order, bool cCompatibility, int tw, int th) {
List<byte[]> layers = new();
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
layers.AddRange(build(x, y, order, cCompatibility));
}
}
return layers;
}
private List<byte[]> buildMultiThread(ByteOrder order, bool cCompatibility, int tw, int th, int threads) {
var tasks = new ConcurrentQueue<Task<Tuple<int, int, List<byte[]>>>>();
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
int tx = x;
int ty = y;
var task = Task.Run(() => {
var partial= build(tx, ty, order, cCompatibility);
return Tuple.Create(tx, ty, partial);
});
tasks.Enqueue(task);
}
}
var partialResults = tasks
.Select(x => x.Result)
.ToDictionary(x => Tuple.Create(x.Item1, x.Item2), x => x.Item3);
List<byte[]> layers = new();
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
var key = Tuple.Create(x, y);
layers.AddRange(partialResults[key]);
}
}
return layers;
}
protected abstract List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility);
}

View File

@ -0,0 +1,34 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public class CompressedTile {
public readonly int index;
public int salt; /// < Counter describing modifications to the tile.
public TileCacheLayerHeader header;
public byte[] data;
public int compressed; // offset of compressed data
public int flags;
public CompressedTile next;
public CompressedTile(int index) {
this.index = index;
salt = 1;
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,576 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
using System;
namespace DotRecast.Detour.TileCache.Io.Compress;
/**
* Core of FastLZ compression algorithm.
*
* This class provides methods for compression and decompression of buffers and saves
* constants which use by FastLzFrameEncoder and FastLzFrameDecoder.
*
* This is refactored code of <a href="https://code.google.com/p/jfastlz/">jfastlz</a>
* library written by William Kinney.
*/
public class FastLz {
private static readonly int MAX_DISTANCE = 8191;
private static readonly int MAX_FARDISTANCE = 65535 + MAX_DISTANCE - 1;
private static readonly int HASH_LOG = 13;
private static readonly int HASH_SIZE = 1 << HASH_LOG; // 8192
private static readonly int HASH_MASK = HASH_SIZE - 1;
private static readonly int MAX_COPY = 32;
private static readonly int MAX_LEN = 256 + 8;
private static readonly int MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 = 1024 * 64;
static readonly int MAGIC_NUMBER = 'F' << 16 | 'L' << 8 | 'Z';
static readonly byte BLOCK_TYPE_NON_COMPRESSED = 0x00;
static readonly byte BLOCK_TYPE_COMPRESSED = 0x01;
static readonly byte BLOCK_WITHOUT_CHECKSUM = 0x00;
static readonly byte BLOCK_WITH_CHECKSUM = 0x10;
static readonly int OPTIONS_OFFSET = 3;
static readonly int CHECKSUM_OFFSET = 4;
static readonly int MAX_CHUNK_LENGTH = 0xFFFF;
/**
* Do not call {@link #compress(byte[], int, int, byte[], int, int)} for input buffers
* which length less than this value.
*/
static readonly int MIN_LENGTH_TO_COMPRESSION = 32;
/**
* In this case {@link #compress(byte[], int, int, byte[], int, int)} will choose level
* automatically depending on the length of the input buffer. If length less than
* {@link #MIN_RECOMENDED_LENGTH_FOR_LEVEL_2} {@link #LEVEL_1} will be choosen,
* otherwise {@link #LEVEL_2}.
*/
static readonly int LEVEL_AUTO = 0;
/**
* Level 1 is the fastest compression and generally useful for short data.
*/
static readonly int LEVEL_1 = 1;
/**
* Level 2 is slightly slower but it gives better compression ratio.
*/
static readonly int LEVEL_2 = 2;
/**
* The output buffer must be at least 6% larger than the input buffer and can not be smaller than 66 bytes.
* @param inputLength length of input buffer
* @return Maximum output buffer length
*/
public static int calculateOutputBufferLength(int inputLength) {
int tempOutputLength = (int) (inputLength * 1.06);
return Math.Max(tempOutputLength, 66);
}
/**
* Compress a block of data in the input buffer and returns the size of compressed block.
* The size of input buffer is specified by length. The minimum input buffer size is 32.
*
* If the input is not compressible, the return value might be larger than length (input buffer size).
*/
public static int compress(byte[] input, int inOffset, int inLength,
byte[] output, int outOffset, int proposedLevel) {
int level;
if (proposedLevel == LEVEL_AUTO) {
level = inLength < MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 ? LEVEL_1 : LEVEL_2;
} else {
level = proposedLevel;
}
int ip = 0;
int ipBound = ip + inLength - 2;
int ipLimit = ip + inLength - 12;
int op = 0;
// const flzuint8* htab[HASH_SIZE];
int[] htab = new int[HASH_SIZE];
// const flzuint8** hslot;
int hslot;
// flzuint32 hval;
// int OK b/c address starting from 0
int hval;
// flzuint32 copy;
// int OK b/c address starting from 0
int copy;
/* sanity check */
if (inLength < 4) {
if (inLength != 0) {
// *op++ = length-1;
output[outOffset + op++] = (byte) (inLength - 1);
ipBound++;
while (ip <= ipBound) {
output[outOffset + op++] = input[inOffset + ip++];
}
return inLength + 1;
}
// else
return 0;
}
/* initializes hash table */
// for (hslot = htab; hslot < htab + HASH_SIZE; hslot++)
for (hslot = 0; hslot < HASH_SIZE; hslot++) {
//*hslot = ip;
htab[hslot] = ip;
}
/* we start with literal copy */
copy = 2;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
output[outOffset + op++] = input[inOffset + ip++];
output[outOffset + op++] = input[inOffset + ip++];
/* main loop */
while (ip < ipLimit) {
int refs = 0;
long distance = 0;
/* minimum match length */
// flzuint32 len = 3;
// int OK b/c len is 0 and octal based
int len = 3;
/* comparison starting-point */
int anchor = ip;
bool matchLabel = false;
/* check for a run */
if (level == LEVEL_2) {
//if(ip[0] == ip[-1] && FASTLZ_READU16(ip-1)==FASTLZ_READU16(ip+1))
if (input[inOffset + ip] == input[inOffset + ip - 1] &&
readU16(input, inOffset + ip - 1) == readU16(input, inOffset + ip + 1)) {
distance = 1;
ip += 3;
refs = anchor - 1 + 3;
/*
* goto match;
*/
matchLabel = true;
}
}
if (!matchLabel) {
/* find potential match */
// HASH_FUNCTION(hval,ip);
hval = hashFunction(input, inOffset + ip);
// hslot = htab + hval;
hslot = hval;
// refs = htab[hval];
refs = htab[hval];
/* calculate distance to the match */
distance = anchor - refs;
/* update hash table */
//*hslot = anchor;
htab[hslot] = anchor;
/* is this a match? check the first 3 bytes */
if (distance == 0
|| (level == LEVEL_1 ? distance >= MAX_DISTANCE : distance >= MAX_FARDISTANCE)
|| input[inOffset + refs++] != input[inOffset + ip++]
|| input[inOffset + refs++] != input[inOffset + ip++]
|| input[inOffset + refs++] != input[inOffset + ip++]) {
/*
* goto literal;
*/
output[outOffset + op++] = input[inOffset + anchor++];
ip = anchor;
copy++;
if (copy == MAX_COPY) {
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
continue;
}
if (level == LEVEL_2) {
/* far, needs at least 5-byte match */
if (distance >= MAX_DISTANCE) {
if (input[inOffset + ip++] != input[inOffset + refs++]
|| input[inOffset + ip++] != input[inOffset + refs++]) {
/*
* goto literal;
*/
output[outOffset + op++] = input[inOffset + anchor++];
ip = anchor;
copy++;
if (copy == MAX_COPY) {
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
continue;
}
len += 2;
}
}
} // end if(!matchLabel)
/*
* match:
*/
/* last matched byte */
ip = anchor + len;
/* distance is biased */
distance--;
if (distance == 0) {
/* zero distance means a run */
//flzuint8 x = ip[-1];
byte x = input[inOffset + ip - 1];
while (ip < ipBound) {
if (input[inOffset + refs++] != x) {
break;
} else {
ip++;
}
}
} else {
for (;;) {
/* safe because the outer check against ip limit */
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
while (ip < ipBound) {
if (input[inOffset + refs++] != input[inOffset + ip++]) {
break;
}
}
break;
}
}
/* if we have copied something, adjust the copy count */
if (copy != 0) {
/* copy is biased, '0' means 1 byte copy */
// *(op-copy-1) = copy-1;
output[outOffset + op - copy - 1] = (byte) (copy - 1);
} else {
/* back, to overwrite the copy count */
op--;
}
/* reset literal counter */
copy = 0;
/* length is biased, '1' means a match of 3 bytes */
ip -= 3;
len = ip - anchor;
/* encode the match */
if (level == LEVEL_2) {
if (distance < MAX_DISTANCE) {
if (len < 7) {
output[outOffset + op++] = (byte) ((len << 5) + (distance >>> 8));
output[outOffset + op++] = (byte) (distance & 255);
} else {
output[outOffset + op++] = (byte) ((7 << 5) + (distance >>> 8));
for (len -= 7; len >= 255; len -= 255) {
output[outOffset + op++] = (byte) 255;
}
output[outOffset + op++] = (byte) len;
output[outOffset + op++] = (byte) (distance & 255);
}
} else {
/* far away, but not yet in the another galaxy... */
if (len < 7) {
distance -= MAX_DISTANCE;
output[outOffset + op++] = (byte) ((len << 5) + 31);
output[outOffset + op++] = (byte) 255;
output[outOffset + op++] = (byte) (distance >>> 8);
output[outOffset + op++] = (byte) (distance & 255);
} else {
distance -= MAX_DISTANCE;
output[outOffset + op++] = (byte) ((7 << 5) + 31);
for (len -= 7; len >= 255; len -= 255) {
output[outOffset + op++] = (byte) 255;
}
output[outOffset + op++] = (byte) len;
output[outOffset + op++] = (byte) 255;
output[outOffset + op++] = (byte) (distance >>> 8);
output[outOffset + op++] = (byte) (distance & 255);
}
}
} else {
if (len > MAX_LEN - 2) {
while (len > MAX_LEN - 2) {
output[outOffset + op++] = (byte) ((7 << 5) + (distance >>> 8));
output[outOffset + op++] = (byte) (MAX_LEN - 2 - 7 - 2);
output[outOffset + op++] = (byte) (distance & 255);
len -= MAX_LEN - 2;
}
}
if (len < 7) {
output[outOffset + op++] = (byte) ((len << 5) + (distance >>> 8));
output[outOffset + op++] = (byte) (distance & 255);
} else {
output[outOffset + op++] = (byte) ((7 << 5) + (distance >>> 8));
output[outOffset + op++] = (byte) (len - 7);
output[outOffset + op++] = (byte) (distance & 255);
}
}
/* update the hash at match boundary */
//HASH_FUNCTION(hval,ip);
hval = hashFunction(input, inOffset + ip);
htab[hval] = ip++;
//HASH_FUNCTION(hval,ip);
hval = hashFunction(input, inOffset + ip);
htab[hval] = ip++;
/* assuming literal copy */
output[outOffset + op++] = (byte)(MAX_COPY - 1);
continue;
// Moved to be inline, with a 'continue'
/*
* literal:
*
output[outOffset + op++] = input[inOffset + anchor++];
ip = anchor;
copy++;
if(copy == MAX_COPY){
copy = 0;
output[outOffset + op++] = MAX_COPY-1;
}
*/
}
/* left-over as literal copy */
ipBound++;
while (ip <= ipBound) {
output[outOffset + op++] = input[inOffset + ip++];
copy++;
if (copy == MAX_COPY) {
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
}
/* if we have copied something, adjust the copy length */
if (copy != 0) {
//*(op-copy-1) = copy-1;
output[outOffset + op - copy - 1] = (byte) (copy - 1);
} else {
op--;
}
if (level == LEVEL_2) {
/* marker for fastlz2 */
output[outOffset] |= 1 << 5;
}
return op;
}
/**
* Decompress a block of compressed data and returns the size of the decompressed block.
* If error occurs, e.g. the compressed data is corrupted or the output buffer is not large
* enough, then 0 (zero) will be returned instead.
*
* Decompression is memory safe and guaranteed not to write the output buffer
* more than what is specified in outLength.
*/
public static int decompress(byte[] input, int inOffset, int inLength,
byte[] output, int outOffset, int outLength) {
//int level = ((*(const flzuint8*)input) >> 5) + 1;
int level = (input[inOffset] >> 5) + 1;
if (level != LEVEL_1 && level != LEVEL_2) {
throw new Exception($"invalid level: {level} (expected: {LEVEL_1} or {LEVEL_2})");
}
// const flzuint8* ip = (const flzuint8*) input;
int ip = 0;
// flzuint8* op = (flzuint8*) output;
int op = 0;
// flzuint32 ctrl = (*ip++) & 31;
long ctrl = input[inOffset + ip++] & 31;
int loop = 1;
do {
// const flzuint8* refs = op;
int refs = op;
// flzuint32 len = ctrl >> 5;
long len = ctrl >> 5;
// flzuint32 ofs = (ctrl & 31) << 8;
long ofs = (ctrl & 31) << 8;
if (ctrl >= 32) {
len--;
// refs -= ofs;
refs -= (int)ofs;
int code;
if (len == 6) {
if (level == LEVEL_1) {
// len += *ip++;
len += input[inOffset + ip++] & 0xFF;
} else {
do {
code = input[inOffset + ip++] & 0xFF;
len += code;
} while (code == 255);
}
}
if (level == LEVEL_1) {
// refs -= *ip++;
refs -= input[inOffset + ip++] & 0xFF;
} else {
code = input[inOffset + ip++] & 0xFF;
refs -= code;
/* match from 16-bit distance */
// if(FASTLZ_UNEXPECT_CONDITIONAL(code==255))
// if(FASTLZ_EXPECT_CONDITIONAL(ofs==(31 << 8)))
if (code == 255 && ofs == 31 << 8) {
ofs = (input[inOffset + ip++] & 0xFF) << 8;
ofs += input[inOffset + ip++] & 0xFF;
refs = (int) (op - ofs - MAX_DISTANCE);
}
}
// if the output index + length of block(?) + 3(?) is over the output limit?
if (op + len + 3 > outLength) {
return 0;
}
// if (FASTLZ_UNEXPECT_CONDITIONAL(refs-1 < (flzuint8 *)output))
// if the address space of refs-1 is < the address of output?
// if we are still at the beginning of the output address?
if (refs - 1 < 0) {
return 0;
}
if (ip < inLength) {
ctrl = input[inOffset + ip++] & 0xFF;
} else {
loop = 0;
}
if (refs == op) {
/* optimize copy for a run */
// flzuint8 b = refs[-1];
byte b = output[outOffset + refs - 1];
output[outOffset + op++] = b;
output[outOffset + op++] = b;
output[outOffset + op++] = b;
while (len != 0) {
output[outOffset + op++] = b;
--len;
}
} else {
/* copy from reference */
refs--;
// *op++ = *refs++;
output[outOffset + op++] = output[outOffset + refs++];
output[outOffset + op++] = output[outOffset + refs++];
output[outOffset + op++] = output[outOffset + refs++];
while (len != 0) {
output[outOffset + op++] = output[outOffset + refs++];
--len;
}
}
} else {
ctrl++;
if (op + ctrl > outLength) {
return 0;
}
if (ip + ctrl > inLength) {
return 0;
}
//*op++ = *ip++;
output[outOffset + op++] = input[inOffset + ip++];
for (--ctrl; ctrl != 0; ctrl--) {
// *op++ = *ip++;
output[outOffset + op++] = input[inOffset + ip++];
}
loop = ip < inLength ? 1 : 0;
if (loop != 0) {
// ctrl = *ip++;
ctrl = input[inOffset + ip++] & 0xFF;
}
}
// while(FASTLZ_EXPECT_CONDITIONAL(loop));
} while (loop != 0);
// return op - (flzuint8*)output;
return op;
}
private static int hashFunction(byte[] p, int offset) {
int v = readU16(p, offset);
v ^= readU16(p, offset + 1) ^ v >> 16 - HASH_LOG;
v &= HASH_MASK;
return v;
}
private static int readU16(byte[] data, int offset) {
if (offset + 1 >= data.Length) {
return data[offset] & 0xff;
}
return (data[offset + 1] & 0xff) << 8 | data[offset] & 0xff;
}
private FastLz() { }
}

View File

@ -0,0 +1,39 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 DotRecast.Core;
using K4os.Compression.LZ4;
namespace DotRecast.Detour.TileCache.Io.Compress;
public class FastLzTileCacheCompressor : TileCacheCompressor {
public byte[] decompress(byte[] buf, int offset, int len, int outputlen) {
byte[] output = new byte[outputlen];
FastLz.decompress(buf, offset, len, output, 0, outputlen);
return output;
}
public byte[] compress(byte[] buf) {
byte[] output = new byte[FastLz.calculateOutputBufferLength(buf.Length)];
int len = FastLz.compress(buf, 0, buf.Length, output, 0, output.Length);
return ArrayUtils.CopyOf(output, len);
}
}

View File

@ -0,0 +1,34 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 K4os.Compression.LZ4;
namespace DotRecast.Detour.TileCache.Io.Compress;
public class LZ4TileCacheCompressor : TileCacheCompressor {
public byte[] decompress(byte[] buf, int offset, int len, int outputlen) {
return LZ4Pickler.Unpickle(buf, offset, len);
}
public byte[] compress(byte[] buf) {
return LZ4Pickler.Pickle(buf);
}
}

View File

@ -0,0 +1,28 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache.Io.Compress;
public class TileCacheCompressorFactory {
public static TileCacheCompressor get(bool cCompatibility) {
return cCompatibility ? new FastLzTileCacheCompressor() : new LZ4TileCacheCompressor();
}
}

View File

@ -0,0 +1,60 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.IO;
using DotRecast.Core;
namespace DotRecast.Detour.TileCache.Io;
public class TileCacheLayerHeaderReader {
public TileCacheLayerHeader read(ByteBuffer data, bool cCompatibility) {
TileCacheLayerHeader header = new TileCacheLayerHeader();
header.magic = data.getInt();
header.version = data.getInt();
if (header.magic != TileCacheLayerHeader.DT_TILECACHE_MAGIC)
throw new IOException("Invalid magic");
if (header.version != TileCacheLayerHeader.DT_TILECACHE_VERSION)
throw new IOException("Invalid version");
header.tx = data.getInt();
header.ty = data.getInt();
header.tlayer = data.getInt();
for (int j = 0; j < 3; j++) {
header.bmin[j] = data.getFloat();
}
for (int j = 0; j < 3; j++) {
header.bmax[j] = data.getFloat();
}
header.hmin = data.getShort() & 0xFFFF;
header.hmax = data.getShort() & 0xFFFF;
header.width = data.get() & 0xFF;
header.height = data.get() & 0xFF;
header.minx = data.get() & 0xFF;
header.maxx = data.get() & 0xFF;
header.miny = data.get() & 0xFF;
header.maxy = data.get() & 0xFF;
if (cCompatibility) {
data.getShort(); // C struct padding
}
return header;
}
}

View File

@ -0,0 +1,53 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
namespace DotRecast.Detour.TileCache.Io;
public class TileCacheLayerHeaderWriter : DetourWriter {
public void write(BinaryWriter stream, TileCacheLayerHeader header, ByteOrder order, bool cCompatibility) {
write(stream, header.magic, order);
write(stream, header.version, order);
write(stream, header.tx, order);
write(stream, header.ty, order);
write(stream, header.tlayer, order);
for (int j = 0; j < 3; j++) {
write(stream, header.bmin[j], order);
}
for (int j = 0; j < 3; j++) {
write(stream, header.bmax[j], order);
}
write(stream, (short) header.hmin, order);
write(stream, (short) header.hmax, order);
write(stream, (byte) header.width);
write(stream, (byte) header.height);
write(stream, (byte) header.minx);
write(stream, (byte) header.maxx);
write(stream, (byte) header.miny);
write(stream, (byte) header.maxy);
if (cCompatibility) {
write(stream, (short) 0, order); // C struct padding
}
}
}

View File

@ -0,0 +1,94 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
using DotRecast.Detour.TileCache.Io.Compress;
namespace DotRecast.Detour.TileCache.Io;
public class TileCacheReader {
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public TileCache read(BinaryReader @is, int maxVertPerPoly, TileCacheMeshProcess meshProcessor) {
ByteBuffer bb = IOUtils.toByteBuffer(@is);
return read(bb, maxVertPerPoly, meshProcessor);
}
public TileCache read(ByteBuffer bb, int maxVertPerPoly, TileCacheMeshProcess meshProcessor) {
TileCacheSetHeader header = new TileCacheSetHeader();
header.magic = bb.getInt();
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) {
header.magic = IOUtils.swapEndianness(header.magic);
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) {
throw new IOException("Invalid magic");
}
bb.order(bb.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
header.version = bb.getInt();
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION) {
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J) {
throw new IOException("Invalid version");
}
}
bool cCompatibility = header.version == TileCacheSetHeader.TILECACHESET_VERSION;
header.numTiles = bb.getInt();
header.meshParams = paramReader.read(bb);
header.cacheParams = readCacheParams(bb, cCompatibility);
NavMesh mesh = new NavMesh(header.meshParams, maxVertPerPoly);
TileCacheCompressor compressor = TileCacheCompressorFactory.get(cCompatibility);
TileCache tc = new TileCache(header.cacheParams, new TileCacheStorageParams(bb.order(), cCompatibility), mesh,
compressor, meshProcessor);
// Read tiles.
for (int i = 0; i < header.numTiles; ++i) {
long tileRef = bb.getInt();
int dataSize = bb.getInt();
if (tileRef == 0 || dataSize == 0) {
break;
}
byte[] data = bb.ReadBytes(dataSize).ToArray();
long tile = tc.addTile(data, 0);
if (tile != 0) {
tc.buildNavMeshTile(tile);
}
}
return tc;
}
private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility) {
TileCacheParams option = new TileCacheParams();
for (int i = 0; i < 3; i++) {
option.orig[i] = bb.getFloat();
}
option.cs = bb.getFloat();
option.ch = bb.getFloat();
option.width = bb.getInt();
option.height = bb.getInt();
option.walkableHeight = bb.getFloat();
option.walkableRadius = bb.getFloat();
option.walkableClimb = bb.getFloat();
option.maxSimplificationError = bb.getFloat();
option.maxTiles = bb.getInt();
option.maxObstacles = bb.getInt();
return option;
}
}

View File

@ -0,0 +1,33 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache.Io;
public class TileCacheSetHeader {
public const int TILECACHESET_MAGIC = 'T' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // 'TSET';
public const int TILECACHESET_VERSION = 1;
public const int TILECACHESET_VERSION_RECAST4J = 0x8801;
public int magic;
public int version;
public int numTiles;
public NavMeshParams meshParams = new NavMeshParams();
public TileCacheParams cacheParams = new TileCacheParams();
}

View File

@ -0,0 +1,74 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
namespace DotRecast.Detour.TileCache.Io;
public class TileCacheWriter : DetourWriter {
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
private readonly TileCacheBuilder builder = new TileCacheBuilder();
public void write(BinaryWriter stream, TileCache cache, ByteOrder order, bool cCompatibility) {
write(stream, TileCacheSetHeader.TILECACHESET_MAGIC, order);
write(stream, cCompatibility ? TileCacheSetHeader.TILECACHESET_VERSION
: TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J, order);
int numTiles = 0;
for (int i = 0; i < cache.getTileCount(); ++i) {
CompressedTile tile = cache.getTile(i);
if (tile == null || tile.data == null)
continue;
numTiles++;
}
write(stream, numTiles, order);
paramWriter.write(stream, cache.getNavMesh().getParams(), order);
writeCacheParams(stream, cache.getParams(), order);
for (int i = 0; i < cache.getTileCount(); i++) {
CompressedTile tile = cache.getTile(i);
if (tile == null || tile.data == null)
continue;
write(stream, (int) cache.getTileRef(tile), order);
byte[] data = tile.data;
TileCacheLayer layer = cache.decompressTile(tile);
data = builder.compressTileCacheLayer(layer, order, cCompatibility);
write(stream, data.Length, order);
stream.Write(data);
}
}
private void writeCacheParams(BinaryWriter stream, TileCacheParams option, ByteOrder order) {
for (int i = 0; i < 3; i++) {
write(stream, option.orig[i], order);
}
write(stream, option.cs, order);
write(stream, option.ch, order);
write(stream, option.width, order);
write(stream, option.height, order);
write(stream, option.walkableHeight, order);
write(stream, option.walkableRadius, order);
write(stream, option.walkableClimb, order);
write(stream, option.maxSimplificationError, order);
write(stream, option.maxTiles, order);
write(stream, option.maxObstacles, order);
}
}

View File

@ -0,0 +1,24 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public class ObstacleRequest {
public ObstacleRequestAction action;
public long refs;
}

View File

@ -0,0 +1,23 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public enum ObstacleRequestAction {
REQUEST_ADD, REQUEST_REMOVE
}

View File

@ -0,0 +1,24 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public enum ObstacleState {
DT_OBSTACLE_EMPTY, DT_OBSTACLE_PROCESSING, DT_OBSTACLE_PROCESSED, DT_OBSTACLE_REMOVING
}

View File

@ -0,0 +1,604 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using DotRecast.Core;
using DotRecast.Detour.TileCache.Io;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.TileCache;
public class TileCache {
int m_tileLutSize; /// < Tile hash lookup size (must be pot).
int m_tileLutMask; /// < Tile hash lookup mask.
private readonly CompressedTile[] m_posLookup; /// < Tile hash lookup.
private CompressedTile m_nextFreeTile; /// < Freelist of tiles.
private readonly CompressedTile[] m_tiles; /// < List of tiles. // TODO: (PP) replace with list
private readonly int m_saltBits; /// < Number of salt bits in the tile ID.
private readonly int m_tileBits; /// < Number of tile bits in the tile ID.
private readonly NavMesh m_navmesh;
private readonly TileCacheParams m_params;
private readonly TileCacheStorageParams m_storageParams;
private readonly TileCacheCompressor m_tcomp;
private readonly TileCacheMeshProcess m_tmproc;
private readonly List<TileCacheObstacle> m_obstacles = new();
private TileCacheObstacle m_nextFreeObstacle;
private readonly List<ObstacleRequest> m_reqs = new();
private readonly List<long> m_update = new();
private readonly TileCacheBuilder builder = new TileCacheBuilder();
private readonly TileCacheLayerHeaderReader tileReader = new TileCacheLayerHeaderReader();
private bool contains(List<long> a, long v) {
return a.Contains(v);
}
/// Encodes a tile id.
private long encodeTileId(int salt, int it) {
return ((long) salt << m_tileBits) | it;
}
/// Decodes a tile salt.
private int decodeTileIdSalt(long refs) {
long saltMask = (1L << m_saltBits) - 1;
return (int) ((refs >> m_tileBits) & saltMask);
}
/// Decodes a tile id.
private int decodeTileIdTile(long refs) {
long tileMask = (1L << m_tileBits) - 1;
return (int) (refs & tileMask);
}
/// Encodes an obstacle id.
private long encodeObstacleId(int salt, int it) {
return ((long) salt << 16) | it;
}
/// Decodes an obstacle salt.
private int decodeObstacleIdSalt(long refs) {
long saltMask = ((long) 1 << 16) - 1;
return (int) ((refs >> 16) & saltMask);
}
/// Decodes an obstacle id.
private int decodeObstacleIdObstacle(long refs) {
long tileMask = ((long) 1 << 16) - 1;
return (int) (refs & tileMask);
}
public TileCache(TileCacheParams option, TileCacheStorageParams storageParams, NavMesh navmesh,
TileCacheCompressor tcomp, TileCacheMeshProcess tmprocs) {
m_params = option;
m_storageParams = storageParams;
m_navmesh = navmesh;
m_tcomp = tcomp;
m_tmproc = tmprocs;
m_tileLutSize = nextPow2(m_params.maxTiles / 4);
if (m_tileLutSize == 0) {
m_tileLutSize = 1;
}
m_tileLutMask = m_tileLutSize - 1;
m_tiles = new CompressedTile[m_params.maxTiles];
m_posLookup = new CompressedTile[m_tileLutSize];
for (int i = m_params.maxTiles - 1; i >= 0; --i) {
m_tiles[i] = new CompressedTile(i);
m_tiles[i].next = m_nextFreeTile;
m_nextFreeTile = m_tiles[i];
}
m_tileBits = ilog2(nextPow2(m_params.maxTiles));
m_saltBits = Math.Min(31, 32 - m_tileBits);
if (m_saltBits < 10) {
throw new Exception("Too few salt bits: " + m_saltBits);
}
}
public CompressedTile getTileByRef(long refs) {
if (refs == 0) {
return null;
}
int tileIndex = decodeTileIdTile(refs);
int tileSalt = decodeTileIdSalt(refs);
if (tileIndex >= m_params.maxTiles) {
return null;
}
CompressedTile tile = m_tiles[tileIndex];
if (tile.salt != tileSalt) {
return null;
}
return tile;
}
public List<long> getTilesAt(int tx, int ty) {
List<long> tiles = new();
// Find tile based on hash.
int h = NavMesh.computeTileHash(tx, ty, m_tileLutMask);
CompressedTile tile = m_posLookup[h];
while (tile != null) {
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty) {
tiles.Add(getTileRef(tile));
}
tile = tile.next;
}
return tiles;
}
CompressedTile getTileAt(int tx, int ty, int tlayer) {
// Find tile based on hash.
int h = NavMesh.computeTileHash(tx, ty, m_tileLutMask);
CompressedTile tile = m_posLookup[h];
while (tile != null) {
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty && tile.header.tlayer == tlayer) {
return tile;
}
tile = tile.next;
}
return null;
}
public long getTileRef(CompressedTile tile) {
if (tile == null) {
return 0;
}
int it = tile.index;
return encodeTileId(tile.salt, it);
}
public long getObstacleRef(TileCacheObstacle ob) {
if (ob == null) {
return 0;
}
int idx = ob.index;
return encodeObstacleId(ob.salt, idx);
}
public TileCacheObstacle getObstacleByRef(long refs) {
if (refs == 0) {
return null;
}
int idx = decodeObstacleIdObstacle(refs);
if (idx >= m_obstacles.Count) {
return null;
}
TileCacheObstacle ob = m_obstacles[idx];
int salt = decodeObstacleIdSalt(refs);
if (ob.salt != salt) {
return null;
}
return ob;
}
public long addTile(byte[] data, int flags) {
// Make sure the data is in right format.
ByteBuffer buf = new ByteBuffer(data);
buf.order(m_storageParams.byteOrder);
TileCacheLayerHeader header = tileReader.read(buf, m_storageParams.cCompatibility);
// Make sure the location is free.
if (getTileAt(header.tx, header.ty, header.tlayer) != null) {
return 0;
}
// Allocate a tile.
CompressedTile tile = null;
if (m_nextFreeTile != null) {
tile = m_nextFreeTile;
m_nextFreeTile = tile.next;
tile.next = null;
}
// Make sure we could allocate a tile.
if (tile == null) {
throw new Exception("Out of storage");
}
// Insert tile into the position lut.
int h = NavMesh.computeTileHash(header.tx, header.ty, m_tileLutMask);
tile.next = m_posLookup[h];
m_posLookup[h] = tile;
// Init tile.
tile.header = header;
tile.data = data;
tile.compressed = align4(buf.position());
tile.flags = flags;
return getTileRef(tile);
}
private int align4(int i) {
return (i + 3) & (~3);
}
public void removeTile(long refs) {
if (refs == 0) {
throw new Exception("Invalid tile ref");
}
int tileIndex = decodeTileIdTile(refs);
int tileSalt = decodeTileIdSalt(refs);
if (tileIndex >= m_params.maxTiles) {
throw new Exception("Invalid tile index");
}
CompressedTile tile = m_tiles[tileIndex];
if (tile.salt != tileSalt) {
throw new Exception("Invalid tile salt");
}
// Remove tile from hash lookup.
int h = NavMesh.computeTileHash(tile.header.tx, tile.header.ty, m_tileLutMask);
CompressedTile prev = null;
CompressedTile cur = m_posLookup[h];
while (cur != null) {
if (cur == tile) {
if (prev != null) {
prev.next = cur.next;
} else {
m_posLookup[h] = cur.next;
}
break;
}
prev = cur;
cur = cur.next;
}
tile.header = null;
tile.data = null;
tile.compressed = 0;
tile.flags = 0;
// Update salt, salt should never be zero.
tile.salt = (tile.salt + 1) & ((1 << m_saltBits) - 1);
if (tile.salt == 0) {
tile.salt++;
}
// Add to free list.
tile.next = m_nextFreeTile;
m_nextFreeTile = tile;
}
// Cylinder obstacle
public long addObstacle(float[] pos, float radius, float height) {
TileCacheObstacle ob = allocObstacle();
ob.type = TileCacheObstacle.TileCacheObstacleType.CYLINDER;
vCopy(ob.pos, pos);
ob.radius = radius;
ob.height = height;
return addObstacleRequest(ob).refs;
}
// Aabb obstacle
public long addBoxObstacle(float[] bmin, float[] bmax) {
TileCacheObstacle ob = allocObstacle();
ob.type = TileCacheObstacle.TileCacheObstacleType.BOX;
vCopy(ob.bmin, bmin);
vCopy(ob.bmax, bmax);
return addObstacleRequest(ob).refs;
}
// Box obstacle: can be rotated in Y
public long addBoxObstacle(float[] center, float[] extents, float yRadians) {
TileCacheObstacle ob = allocObstacle();
ob.type = TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX;
vCopy(ob.center, center);
vCopy(ob.extents, extents);
float coshalf = (float) Math.Cos(0.5f * yRadians);
float sinhalf = (float) Math.Sin(-0.5f * yRadians);
ob.rotAux[0] = coshalf * sinhalf;
ob.rotAux[1] = coshalf * coshalf - 0.5f;
return addObstacleRequest(ob).refs;
}
private ObstacleRequest addObstacleRequest(TileCacheObstacle ob) {
ObstacleRequest req = new ObstacleRequest();
req.action = ObstacleRequestAction.REQUEST_ADD;
req.refs = getObstacleRef(ob);
m_reqs.Add(req);
return req;
}
public void removeObstacle(long refs) {
if (refs == 0) {
return;
}
ObstacleRequest req = new ObstacleRequest();
req.action = ObstacleRequestAction.REQUEST_REMOVE;
req.refs = refs;
m_reqs.Add(req);
}
private TileCacheObstacle allocObstacle() {
TileCacheObstacle o = m_nextFreeObstacle;
if (o == null) {
o = new TileCacheObstacle(m_obstacles.Count);
m_obstacles.Add(o);
} else {
m_nextFreeObstacle = o.next;
}
o.state = ObstacleState.DT_OBSTACLE_PROCESSING;
o.touched.Clear();
o.pending.Clear();
o.next = null;
return o;
}
List<long> queryTiles(float[] bmin, float[] bmax) {
List<long> results = new();
float tw = m_params.width * m_params.cs;
float th = m_params.height * m_params.cs;
int tx0 = (int) Math.Floor((bmin[0] - m_params.orig[0]) / tw);
int tx1 = (int) Math.Floor((bmax[0] - m_params.orig[0]) / tw);
int ty0 = (int) Math.Floor((bmin[2] - m_params.orig[2]) / th);
int ty1 = (int) Math.Floor((bmax[2] - m_params.orig[2]) / th);
for (int ty = ty0; ty <= ty1; ++ty) {
for (int tx = tx0; tx <= tx1; ++tx) {
List<long> tiles = getTilesAt(tx, ty);
foreach (long i in tiles) {
CompressedTile tile = m_tiles[decodeTileIdTile(i)];
float[] tbmin = new float[3];
float[] tbmax = new float[3];
calcTightTileBounds(tile.header, tbmin, tbmax);
if (overlapBounds(bmin, bmax, tbmin, tbmax)) {
results.Add(i);
}
}
}
}
return results;
}
/**
* Updates the tile cache by rebuilding tiles touched by unfinished obstacle requests.
*
* @return Returns true if the tile cache is fully up to date with obstacle requests and tile rebuilds. If the tile
* cache is up to date another (immediate) call to update will have no effect; otherwise another call will
* continue processing obstacle requests and tile rebuilds.
*/
public bool update() {
if (0 == m_update.Count) {
// Process requests.
foreach (ObstacleRequest req in m_reqs) {
int idx = decodeObstacleIdObstacle(req.refs);
if (idx >= m_obstacles.Count) {
continue;
}
TileCacheObstacle ob = m_obstacles[idx];
int salt = decodeObstacleIdSalt(req.refs);
if (ob.salt != salt) {
continue;
}
if (req.action == ObstacleRequestAction.REQUEST_ADD) {
// Find touched tiles.
float[] bmin = new float[3];
float[] bmax = new float[3];
getObstacleBounds(ob, bmin, bmax);
ob.touched = queryTiles(bmin, bmax);
// Add tiles to update list.
ob.pending.Clear();
foreach (long j in ob.touched) {
if (!contains(m_update, j)) {
m_update.Add(j);
}
ob.pending.Add(j);
}
} else if (req.action == ObstacleRequestAction.REQUEST_REMOVE) {
// Prepare to remove obstacle.
ob.state = ObstacleState.DT_OBSTACLE_REMOVING;
// Add tiles to update list.
ob.pending.Clear();
foreach (long j in ob.touched) {
if (!contains(m_update, j)) {
m_update.Add(j);
}
ob.pending.Add(j);
}
}
}
m_reqs.Clear();
}
// Process updates
if (0 < m_update.Count) {
long refs = m_update[0];
m_update.RemoveAt(0);
// Build mesh
buildNavMeshTile(refs);
// Update obstacle states.
for (int i = 0; i < m_obstacles.Count; ++i) {
TileCacheObstacle ob = m_obstacles[i];
if (ob.state == ObstacleState.DT_OBSTACLE_PROCESSING
|| ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
// Remove handled tile from pending list.
ob.pending.Remove(refs);
// If all pending tiles processed, change state.
if (0 == ob.pending.Count) {
if (ob.state == ObstacleState.DT_OBSTACLE_PROCESSING) {
ob.state = ObstacleState.DT_OBSTACLE_PROCESSED;
} else if (ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
ob.state = ObstacleState.DT_OBSTACLE_EMPTY;
// Update salt, salt should never be zero.
ob.salt = (ob.salt + 1) & ((1 << 16) - 1);
if (ob.salt == 0) {
ob.salt++;
}
// Return obstacle to free list.
ob.next = m_nextFreeObstacle;
m_nextFreeObstacle = ob;
}
}
}
}
}
return 0 == m_update.Count && 0 == m_reqs.Count;
}
public void buildNavMeshTile(long refs) {
int idx = decodeTileIdTile(refs);
if (idx > m_params.maxTiles) {
throw new Exception("Invalid tile index");
}
CompressedTile tile = m_tiles[idx];
int salt = decodeTileIdSalt(refs);
if (tile.salt != salt) {
throw new Exception("Invalid tile salt");
}
int walkableClimbVx = (int) (m_params.walkableClimb / m_params.ch);
// Decompress tile layer data.
TileCacheLayer layer = decompressTile(tile);
// Rasterize obstacles.
for (int i = 0; i < m_obstacles.Count; ++i) {
TileCacheObstacle ob = m_obstacles[i];
if (ob.state == ObstacleState.DT_OBSTACLE_EMPTY || ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
continue;
}
if (contains(ob.touched, refs)) {
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER) {
builder.markCylinderArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.pos, ob.radius,
ob.height, 0);
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX) {
builder.markBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.bmin, ob.bmax, 0);
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX) {
builder.markBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.center, ob.extents,
ob.rotAux, 0);
}
}
}
// Build navmesh
builder.buildTileCacheRegions(layer, walkableClimbVx);
TileCacheContourSet lcset = builder.buildTileCacheContours(layer, walkableClimbVx,
m_params.maxSimplificationError);
TileCachePolyMesh polyMesh = builder.buildTileCachePolyMesh(lcset, m_navmesh.getMaxVertsPerPoly());
// Early out if the mesh tile is empty.
if (polyMesh.npolys == 0) {
m_navmesh.removeTile(m_navmesh.getTileRefAt(tile.header.tx, tile.header.ty, tile.header.tlayer));
return;
}
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = polyMesh.verts;
option.vertCount = polyMesh.nverts;
option.polys = polyMesh.polys;
option.polyAreas = polyMesh.areas;
option.polyFlags = polyMesh.flags;
option.polyCount = polyMesh.npolys;
option.nvp = m_navmesh.getMaxVertsPerPoly();
option.walkableHeight = m_params.walkableHeight;
option.walkableRadius = m_params.walkableRadius;
option.walkableClimb = m_params.walkableClimb;
option.tileX = tile.header.tx;
option.tileZ = tile.header.ty;
option.tileLayer = tile.header.tlayer;
option.cs = m_params.cs;
option.ch = m_params.ch;
option.buildBvTree = false;
option.bmin = tile.header.bmin;
option.bmax = tile.header.bmax;
if (m_tmproc != null) {
m_tmproc.process(option);
}
MeshData meshData = NavMeshBuilder.createNavMeshData(option);
// Remove existing tile.
m_navmesh.removeTile(m_navmesh.getTileRefAt(tile.header.tx, tile.header.ty, tile.header.tlayer));
// Add new tile, or leave the location empty. if (navData) { // Let the
if (meshData != null) {
m_navmesh.addTile(meshData, 0, 0);
}
}
public TileCacheLayer decompressTile(CompressedTile tile) {
TileCacheLayer layer = builder.decompressTileCacheLayer(m_tcomp, tile.data, m_storageParams.byteOrder,
m_storageParams.cCompatibility);
return layer;
}
void calcTightTileBounds(TileCacheLayerHeader header, float[] bmin, float[] bmax) {
float cs = m_params.cs;
bmin[0] = header.bmin[0] + header.minx * cs;
bmin[1] = header.bmin[1];
bmin[2] = header.bmin[2] + header.miny * cs;
bmax[0] = header.bmin[0] + (header.maxx + 1) * cs;
bmax[1] = header.bmax[1];
bmax[2] = header.bmin[2] + (header.maxy + 1) * cs;
}
void getObstacleBounds(TileCacheObstacle ob, float[] bmin, float[] bmax) {
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER) {
bmin[0] = ob.pos[0] - ob.radius;
bmin[1] = ob.pos[1];
bmin[2] = ob.pos[2] - ob.radius;
bmax[0] = ob.pos[0] + ob.radius;
bmax[1] = ob.pos[1] + ob.height;
bmax[2] = ob.pos[2] + ob.radius;
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX) {
vCopy(bmin, ob.bmin);
vCopy(bmax, ob.bmax);
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX) {
float maxr = 1.41f * Math.Max(ob.extents[0], ob.extents[2]);
bmin[0] = ob.center[0] - maxr;
bmax[0] = ob.center[0] + maxr;
bmin[1] = ob.center[1] - ob.extents[1];
bmax[1] = ob.center[1] + ob.extents[1];
bmin[2] = ob.center[2] - maxr;
bmax[2] = ob.center[2] + maxr;
}
}
public TileCacheParams getParams() {
return m_params;
}
public TileCacheCompressor getCompressor() {
return m_tcomp;
}
public int getTileCount() {
return m_params.maxTiles;
}
public CompressedTile getTile(int i) {
return m_tiles[i];
}
public NavMesh getNavMesh() {
return m_navmesh;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public interface TileCacheCompressor {
byte[] decompress(byte[] buf, int offset, int len, int outputlen);
byte[] compress(byte[] buf);
}

View File

@ -0,0 +1,26 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public class TileCacheContour {
public int nverts;
public int[] verts;
public int reg;
public int area;
}

View File

@ -0,0 +1,24 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public class TileCacheContourSet {
public int nconts;
public TileCacheContour[] conts;
}

View File

@ -0,0 +1,28 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public class TileCacheLayer {
public TileCacheLayerHeader header;
public int regCount; /// < Region count.
public short[] heights; // char
public short[] areas; // char
public short[] cons; // char
public short[] regs; // char
}

View File

@ -0,0 +1,35 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public class TileCacheLayerHeader {
public const int DT_TILECACHE_MAGIC = 'D' << 24 | 'T' << 16 | 'L' << 8 | 'R'; /// < 'DTLR';
public const int DT_TILECACHE_VERSION = 1;
public int magic; /// < Data magic
public int version; /// < Data version
public int tx, ty, tlayer;
public float[] bmin = new float[3];
public float[] bmax = new float[3];
public int hmin, hmax; /// < Height min/max range
public int width, height; /// < Dimension of the layer.
public int minx, maxx, miny, maxy; /// < Usable sub-region.
}

View File

@ -0,0 +1,24 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public interface TileCacheMeshProcess {
void process(NavMeshDataCreateParams option);
}

View File

@ -0,0 +1,50 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour.TileCache;
public class TileCacheObstacle {
public enum TileCacheObstacleType {
CYLINDER, BOX, ORIENTED_BOX
};
public readonly int index;
public TileCacheObstacleType type;
public readonly float[] pos = new float[3];
public readonly float[] bmin = new float[3];
public readonly float[] bmax = new float[3];
public float radius, height;
public readonly float[] center = new float[3];
public readonly float[] extents = new float[3];
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();
public readonly List<long> pending = new();
public int salt;
public ObstacleState state = ObstacleState.DT_OBSTACLE_EMPTY;
public TileCacheObstacle next;
public TileCacheObstacle(int index) {
salt = 1;
this.index = index;
}
}

View File

@ -0,0 +1,32 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public class TileCacheParams {
public readonly float[] orig = new float[3];
public float cs, ch;
public int width, height;
public float walkableHeight;
public float walkableRadius;
public float walkableClimb;
public float maxSimplificationError;
public int maxTiles;
public int maxObstacles;
}

View File

@ -0,0 +1,33 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour.TileCache;
public class TileCachePolyMesh {
public int nvp;
public int nverts; /// < 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 TileCachePolyMesh(int nvp) {
this.nvp = nvp;
}
}

View File

@ -0,0 +1,34 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 DotRecast.Core;
namespace DotRecast.Detour.TileCache;
public class TileCacheStorageParams {
public readonly ByteOrder byteOrder;
public readonly bool cCompatibility;
public TileCacheStorageParams(ByteOrder byteOrder, bool cCompatibility) {
this.byteOrder = byteOrder;
this.cCompatibility = cCompatibility;
}
}

View File

@ -0,0 +1,34 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
/**
* Bounding volume node.
*
* @note This structure is rarely if ever used by the end user.
* @see MeshTile
*/
public class BVNode {
/** Minimum bounds of the node's AABB. [(x, y, z)] */
public int[] bmin = new int[3];
/** 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

@ -0,0 +1,41 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
public class ClosestPointOnPolyResult {
private readonly bool posOverPoly;
private readonly float[] closest;
public ClosestPointOnPolyResult(bool posOverPoly, float[] closest) {
this.posOverPoly = posOverPoly;
this.closest = closest;
}
/** Returns true if the position is over the polygon. */
public bool isPosOverPoly() {
return posOverPoly;
}
/** Returns the closest point on the polygon. [(x, y, z)] */
public float[] getClosest() {
return closest;
}
}

View File

@ -0,0 +1,237 @@
/*
recast4j copyright (c) 2021 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;
namespace DotRecast.Detour;
using static DetourCommon;
/**
* Convex-convex intersection based on "Computational Geometry in C" by Joseph O'Rourke
*/
public static class ConvexConvexIntersection {
private static readonly float EPSILON = 0.0001f;
private enum InFlag {
Pin, Qin, Unknown,
}
private enum Intersection {
None, Single, Overlap,
}
public static float[] intersect(float[] p, float[] q) {
int n = p.Length / 3;
int m = q.Length / 3;
float[] inters = new float[Math.Max(m, n) * 3 * 3];
int ii = 0;
/* Initialize variables. */
float[] a = new float[3];
float[] b = new float[3];
float[] a1 = new float[3];
float[] b1 = new float[3];
int aa = 0;
int ba = 0;
int ai = 0;
int bi = 0;
InFlag f = InFlag.Unknown;
bool FirstPoint = true;
float[] ip = new float[3];
float[] iq = new float[3];
do {
vCopy(a, p, 3 * (ai % n));
vCopy(b, q, 3 * (bi % m));
vCopy(a1, p, 3 * ((ai + n - 1) % n)); // prev a
vCopy(b1, q, 3 * ((bi + m - 1) % m)); // prev b
float[] A = vSub(a, a1);
float[] B = vSub(b, b1);
float cross = B[0] * A[2] - A[0] * B[2];// triArea2D({0, 0}, A, B);
float aHB = triArea2D(b1, b, a);
float bHA = triArea2D(a1, a, b);
if (Math.Abs(cross) < EPSILON) {
cross = 0f;
}
bool parallel = cross == 0f;
Intersection code = parallel ? parallelInt(a1, a, b1, b, ip, iq) : segSegInt(a1, a, b1, b, ip, iq);
if (code == Intersection.Single) {
if (FirstPoint) {
FirstPoint = false;
aa = ba = 0;
}
ii = addVertex(inters, ii, ip);
f = inOut(f, aHB, bHA);
}
/*-----Advance rules-----*/
/* Special case: A & B overlap and oppositely oriented. */
if (code == Intersection.Overlap && vDot2D(A, B) < 0) {
ii = addVertex(inters, ii, ip);
ii = addVertex(inters, ii, iq);
break;
}
/* Special case: A & B parallel and separated. */
if (parallel && aHB < 0f && bHA < 0f) {
return null;
}
/* Special case: A & B collinear. */
else if (parallel && Math.Abs(aHB) < EPSILON && Math.Abs(bHA) < EPSILON) {
/* Advance but do not output point. */
if (f == InFlag.Pin) {
ba++;
bi++;
} else {
aa++;
ai++;
}
}
/* Generic cases. */
else if (cross >= 0) {
if (bHA > 0) {
if (f == InFlag.Pin) {
ii = addVertex(inters, ii, a);
}
aa++;
ai++;
} else {
if (f == InFlag.Qin) {
ii = addVertex(inters, ii, b);
}
ba++;
bi++;
}
} else {
if (aHB > 0) {
if (f == InFlag.Qin) {
ii = addVertex(inters, ii, b);
}
ba++;
bi++;
} else {
if (f == InFlag.Pin) {
ii = addVertex(inters, ii, a);
}
aa++;
ai++;
}
}
/* Quit when both adv. indices have cycled, or one has cycled twice. */
} while ((aa < n || ba < m) && aa < 2 * n && ba < 2 * m);
/* Deal with special cases: not implemented. */
if (f == InFlag.Unknown) {
return null;
}
float[] copied = new float[ii];
Array.Copy(inters, copied, ii);
return copied;
}
private static int addVertex(float[] inters, int ii, float[] p) {
if (ii > 0) {
if (inters[ii - 3] == p[0] && inters[ii - 2] == p[1] && inters[ii - 1] == p[2]) {
return ii;
}
if (inters[0] == p[0] && inters[1] == p[1] && inters[2] == p[2]) {
return ii;
}
}
inters[ii] = p[0];
inters[ii + 1] = p[1];
inters[ii + 2] = p[2];
return ii + 3;
}
private static InFlag inOut(InFlag inflag, float aHB, float bHA) {
if (aHB > 0) {
return InFlag.Pin;
} else if (bHA > 0) {
return InFlag.Qin;
}
return inflag;
}
private static Intersection segSegInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q) {
var isec = intersectSegSeg2D(a, b, c, d);
if (null != isec) {
float s = isec.Item1;
float t = isec.Item2;
if (s >= 0.0f && s <= 1.0f && t >= 0.0f && t <= 1.0f) {
p[0] = a[0] + (b[0] - a[0]) * s;
p[1] = a[1] + (b[1] - a[1]) * s;
p[2] = a[2] + (b[2] - a[2]) * s;
return Intersection.Single;
}
}
return Intersection.None;
}
private static Intersection parallelInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q) {
if (between(a, b, c) && between(a, b, d)) {
vCopy(p, c);
vCopy(q, d);
return Intersection.Overlap;
}
if (between(c, d, a) && between(c, d, b)) {
vCopy(p, a);
vCopy(q, b);
return Intersection.Overlap;
}
if (between(a, b, c) && between(c, d, b)) {
vCopy(p, c);
vCopy(q, b);
return Intersection.Overlap;
}
if (between(a, b, c) && between(c, d, a)) {
vCopy(p, c);
vCopy(q, a);
return Intersection.Overlap;
}
if (between(a, b, d) && between(c, d, b)) {
vCopy(p, d);
vCopy(q, b);
return Intersection.Overlap;
}
if (between(a, b, d) && between(c, d, a)) {
vCopy(p, d);
vCopy(q, a);
return Intersection.Overlap;
}
return Intersection.None;
}
private static bool between(float[] a, float[] b, float[] c) {
if (Math.Abs(a[0] - b[0]) > Math.Abs(a[2] - b[2])) {
return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0]));
} else {
return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2]));
}
}
}

View File

@ -0,0 +1,101 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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;
namespace DotRecast.Detour;
using static DetourCommon;
/**
* <b>The Default Implementation</b>
*
* At construction: All area costs default to 1.0. All flags are included and none are excluded.
*
* If a polygon has both an include and an exclude flag, it will be excluded.
*
* The way filtering works, a navigation mesh polygon must have at least one flag set to ever be considered by a query.
* So a polygon with no flags will never be considered.
*
* Setting the include flags to 0 will result in all polygons being excluded.
*
* <b>Custom Implementations</b>
*
* Implement a custom query filter by overriding the virtual passFilter() and getCost() functions. If this is done, both
* functions should be as fast as possible. Use cached local copies of data rather than accessing your own objects where
* possible.
*
* Custom implementations do not need to adhere to the flags or cost logic used by the default implementation.
*
* In order for A* searches to work properly, the cost should be proportional to the travel distance. Implementing a
* cost modifier less than 1.0 is likely to lead to problems during pathfinding.
*
* @see NavMeshQuery
*/
public class DefaultQueryFilter : QueryFilter {
private int m_excludeFlags;
private int m_includeFlags;
private readonly float[] m_areaCost = new float[NavMesh.DT_MAX_AREAS];
public DefaultQueryFilter() {
m_includeFlags = 0xffff;
m_excludeFlags = 0;
for (int i = 0; i < NavMesh.DT_MAX_AREAS; ++i) {
m_areaCost[i] = 1.0f;
}
}
public DefaultQueryFilter(int includeFlags, int excludeFlags, float[] areaCost) {
m_includeFlags = includeFlags;
m_excludeFlags = excludeFlags;
for (int i = 0; i < Math.Min(NavMesh.DT_MAX_AREAS, areaCost.Length); ++i) {
m_areaCost[i] = areaCost[i];
}
for (int i = areaCost.Length; i < NavMesh.DT_MAX_AREAS; ++i) {
m_areaCost[i] = 1.0f;
}
}
public bool passFilter(long refs, MeshTile tile, Poly poly) {
return (poly.flags & m_includeFlags) != 0 && (poly.flags & m_excludeFlags) == 0;
}
public float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef,
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly) {
return vDist(pa, pb) * m_areaCost[curPoly.getArea()];
}
public int getIncludeFlags() {
return m_includeFlags;
}
public void setIncludeFlags(int flags) {
m_includeFlags = flags;
}
public int getExcludeFlags() {
return m_excludeFlags;
}
public void setExcludeFlags(int flags) {
m_excludeFlags = flags;
}
}

View File

@ -0,0 +1,39 @@
/*
recast4j copyright (c) 2021 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.
*/
namespace DotRecast.Detour;
using static DetourCommon;
public class DefaultQueryHeuristic : QueryHeuristic {
private readonly float scale;
public DefaultQueryHeuristic() : this(0.999f)
{
}
public DefaultQueryHeuristic(float scale) {
this.scale = scale;
}
public float getCost(float[] neighbourPos, float[] endPos) {
return vDist(neighbourPos, endPos) * scale;
}
}

View File

@ -0,0 +1,31 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
public class DetourBuilder {
public MeshData build(NavMeshDataCreateParams option, int tileX, int tileY) {
MeshData data = NavMeshBuilder.createNavMeshData(option);
if (data != null) {
data.header.x = tileX;
data.header.y = tileY;
}
return data;
}
}

View File

@ -0,0 +1,636 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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;
namespace DotRecast.Detour;
public static class DetourCommon {
public const float EPS = 1e-4f;
/// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s))
/// @param[out] dest The result vector. [(x, y, z)]
/// @param[in] v1 The base vector. [(x, y, z)]
/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)]
/// @param[in] s The amount to scale @p v2 by before adding to @p v1.
public static float[] vMad(float[] v1, float[] v2, float s) {
float[] dest = new float[3];
dest[0] = v1[0] + v2[0] * s;
dest[1] = v1[1] + v2[1] * s;
dest[2] = v1[2] + v2[2] * s;
return dest;
}
/// Performs a linear interpolation between two vectors. (@p v1 toward @p
/// v2)
/// @param[out] dest The result vector. [(x, y, x)]
/// @param[in] v1 The starting vector.
/// @param[in] v2 The destination vector.
/// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0]
public static float[] vLerp(float[] verts, int v1, int v2, float t) {
float[] dest = new float[3];
dest[0] = verts[v1 + 0] + (verts[v2 + 0] - verts[v1 + 0]) * t;
dest[1] = verts[v1 + 1] + (verts[v2 + 1] - verts[v1 + 1]) * t;
dest[2] = verts[v1 + 2] + (verts[v2 + 2] - verts[v1 + 2]) * t;
return dest;
}
public static float[] vLerp(float[] v1, float[] v2, float t) {
float[] dest = new float[3];
dest[0] = v1[0] + (v2[0] - v1[0]) * t;
dest[1] = v1[1] + (v2[1] - v1[1]) * t;
dest[2] = v1[2] + (v2[2] - v1[2]) * t;
return dest;
}
public static float[] vSub(VectorPtr v1, VectorPtr v2) {
float[] dest = new float[3];
dest[0] = v1.get(0) - v2.get(0);
dest[1] = v1.get(1) - v2.get(1);
dest[2] = v1.get(2) - v2.get(2);
return dest;
}
public static float[] vSub(float[] v1, float[] v2) {
float[] dest = new float[3];
dest[0] = v1[0] - v2[0];
dest[1] = v1[1] - v2[1];
dest[2] = v1[2] - v2[2];
return dest;
}
public static float[] vAdd(float[] v1, float[] v2) {
float[] dest = new float[3];
dest[0] = v1[0] + v2[0];
dest[1] = v1[1] + v2[1];
dest[2] = v1[2] + v2[2];
return dest;
}
public static float[] vCopy(float[] @in) {
float[] @out = new float[3];
@out[0] = @in[0];
@out[1] = @in[1];
@out[2] = @in[2];
return @out;
}
public static void vSet(float[] @out, float a, float b, float c) {
@out[0] = a;
@out[1] = b;
@out[2] = c;
}
public static void vCopy(float[] @out, float[] @in) {
@out[0] = @in[0];
@out[1] = @in[1];
@out[2] = @in[2];
}
public static void vCopy(float[] @out, float[] @in, int i) {
@out[0] = @in[i];
@out[1] = @in[i + 1];
@out[2] = @in[i + 2];
}
public static void vMin(float[] @out, float[] @in, int i) {
@out[0] = Math.Min(@out[0], @in[i]);
@out[1] = Math.Min(@out[1], @in[i + 1]);
@out[2] = Math.Min(@out[2], @in[i + 2]);
}
public static void vMax(float[] @out, float[] @in, int i) {
@out[0] = Math.Max(@out[0], @in[i]);
@out[1] = Math.Max(@out[1], @in[i + 1]);
@out[2] = Math.Max(@out[2], @in[i + 2]);
}
/// Returns the distance between two points.
/// @param[in] v1 A point. [(x, y, z)]
/// @param[in] v2 A point. [(x, y, z)]
/// @return The distance between the two points.
public static float vDist(float[] v1, float[] v2) {
float dx = v2[0] - v1[0];
float dy = v2[1] - v1[1];
float dz = v2[2] - v1[2];
return (float) Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
/// Returns the distance between two points.
/// @param[in] v1 A point. [(x, y, z)]
/// @param[in] v2 A point. [(x, y, z)]
/// @return The distance between the two points.
public static float vDistSqr(float[] v1, float[] v2) {
float dx = v2[0] - v1[0];
float dy = v2[1] - v1[1];
float dz = v2[2] - v1[2];
return dx * dx + dy * dy + dz * dz;
}
public static float sqr(float a) {
return a * a;
}
/// Derives the square of the scalar length of the vector. (len * len)
/// @param[in] v The vector. [(x, y, z)]
/// @return The square of the scalar length of the vector.
public static float vLenSqr(float[] v) {
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
}
public static float vLen(float[] v) {
return (float) Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
public static float vDist(float[] v1, float[] verts, int i) {
float dx = verts[i] - v1[0];
float dy = verts[i + 1] - v1[1];
float dz = verts[i + 2] - v1[2];
return (float) Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
public static float clamp(float v, float min, float max) {
return Math.Max(Math.Min(v, max), min);
}
public static int clamp(int v, int min, int max) {
return Math.Max(Math.Min(v, max), min);
}
/// Derives 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 distance between the point on the xz-plane.
///
/// The vectors are projected onto the xz-plane, so the y-values are
/// ignored.
public static float vDist2D(float[] v1, float[] v2) {
float dx = v2[0] - v1[0];
float dz = v2[2] - v1[2];
return (float) Math.Sqrt(dx * dx + dz * dz);
}
public static float vDist2DSqr(float[] v1, float[] v2) {
float dx = v2[0] - v1[0];
float dz = v2[2] - v1[2];
return dx * dx + dz * dz;
}
public static float vDist2DSqr(float[] p, float[] verts, int i) {
float dx = verts[i] - p[0];
float dz = verts[i + 2] - p[2];
return dx * dx + dz * dz;
}
/// Normalizes the vector.
/// @param[in,out] v The vector to normalize. [(x, y, z)]
public static void vNormalize(float[] v) {
float d = (float) (1.0f / Math.Sqrt(sqr(v[0]) + sqr(v[1]) + sqr(v[2])));
if (d != 0) {
v[0] *= d;
v[1] *= d;
v[2] *= d;
}
}
private static readonly float EQUAL_THRESHOLD = sqr(1.0f / 16384.0f);
/// 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(float[] p0, float[] p1) {
return vEqual(p0, p1, EQUAL_THRESHOLD);
}
public static bool vEqual(float[] p0, float[] p1, float thresholdSqr) {
float d = vDistSqr(p0, p1);
return d < thresholdSqr;
}
/// Derives the dot product of two vectors on the xz-plane. (@p u . @p v)
/// @param[in] u A vector [(x, y, z)]
/// @param[in] v A vector [(x, y, z)]
/// @return The dot product on the xz-plane.
///
/// The vectors are projected onto the xz-plane, so the y-values are
/// ignored.
public static float vDot2D(float[] u, float[] v) {
return u[0] * v[0] + u[2] * v[2];
}
public static float vDot2D(float[] u, float[] v, int vi) {
return u[0] * v[vi] + u[2] * v[vi + 2];
}
/// Derives the xz-plane 2D perp product of the two vectors. (uz*vx - ux*vz)
/// @param[in] u The LHV vector [(x, y, z)]
/// @param[in] v The RHV vector [(x, y, z)]
/// @return The dot product on the xz-plane.
///
/// The vectors are projected onto the xz-plane, so the y-values are
/// ignored.
public static float vPerp2D(float[] u, float[] v) {
return u[2] * v[0] - u[0] * v[2];
}
/// @}
/// @name Computational geometry helper functions.
/// @{
/// Derives the signed xz-plane area of the triangle ABC, or the
/// relationship of line AB to point C.
/// @param[in] a Vertex A. [(x, y, z)]
/// @param[in] b Vertex B. [(x, y, z)]
/// @param[in] c Vertex C. [(x, y, z)]
/// @return The signed xz-plane area of the triangle.
public static float triArea2D(float[] verts, int a, int b, int c) {
float abx = verts[b] - verts[a];
float abz = verts[b + 2] - verts[a + 2];
float acx = verts[c] - verts[a];
float acz = verts[c + 2] - verts[a + 2];
return acx * abz - abx * acz;
}
public static float triArea2D(float[] a, float[] b, float[] c) {
float abx = b[0] - a[0];
float abz = b[2] - a[2];
float acx = c[0] - a[0];
float acz = c[2] - a[2];
return acx * abz - abx * acz;
}
/// Determines if two axis-aligned bounding boxes overlap.
/// @param[in] amin Minimum bounds of box A. [(x, y, z)]
/// @param[in] amax Maximum bounds of box A. [(x, y, z)]
/// @param[in] bmin Minimum 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.
/// @see dtOverlapBounds
public static bool overlapQuantBounds(int[] amin, int[] amax, int[] bmin, int[] bmax) {
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap;
}
/// Determines if two axis-aligned bounding boxes overlap.
/// @param[in] amin Minimum bounds of box A. [(x, y, z)]
/// @param[in] amax Maximum bounds of box A. [(x, y, z)]
/// @param[in] bmin Minimum 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.
/// @see dtOverlapQuantBounds
public static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax) {
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap;
}
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] p, float[] q) {
float pqx = q[0] - p[0];
float pqz = q[2] - p[2];
float dx = pt[0] - p[0];
float dz = pt[2] - p[2];
float d = pqx * pqx + pqz * pqz;
float t = pqx * dx + pqz * dz;
if (d > 0) {
t /= d;
}
if (t < 0) {
t = 0;
} else if (t > 1) {
t = 1;
}
dx = p[0] + t * pqx - pt[0];
dz = p[2] + t * pqz - pt[2];
return Tuple.Create(dx * dx + dz * dz, t);
}
public static float? closestHeightPointTriangle(float[] p, float[] a, float[] b, float[] c) {
float[] v0 = vSub(c, a);
float[] v1 = vSub(b, a);
float[] v2 = vSub(p, a);
// Compute scaled barycentric coordinates
float denom = v0[0] * v1[2] - v0[2] * v1[0];
if (Math.Abs(denom) < EPS) {
return null;
}
float u = v1[2] * v2[0] - v1[0] * v2[2];
float v = v0[0] * v2[2] - v0[2] * v2[0];
if (denom < 0) {
denom = -denom;
u = -u;
v = -v;
}
// If point lies inside the triangle, return interpolated ycoord.
if (u >= 0.0f && v >= 0.0f && (u + v) <= denom) {
float h = a[1] + (v0[1] * u + v1[1] * v) / denom;
return h;
}
return null;
}
/// @par
///
/// All points are projected onto the xz-plane, so the y-values are ignored.
public static bool pointInPolygon(float[] pt, float[] verts, int nverts) {
// TODO: Replace pnpoly with triArea2D tests?
int i, j;
bool c = false;
for (i = 0, j = nverts - 1; i < nverts; j = i++) {
int vi = i * 3;
int vj = j * 3;
if (((verts[vi + 2] > pt[2]) != (verts[vj + 2] > pt[2])) && (pt[0] < (verts[vj + 0] - verts[vi + 0])
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0])) {
c = !c;
}
}
return c;
}
public static bool distancePtPolyEdgesSqr(float[] pt, float[] verts, int nverts, float[] ed, float[] et) {
// TODO: Replace pnpoly with triArea2D tests?
int i, j;
bool c = false;
for (i = 0, j = nverts - 1; i < nverts; j = i++) {
int vi = i * 3;
int vj = j * 3;
if (((verts[vi + 2] > pt[2]) != (verts[vj + 2] > pt[2])) && (pt[0] < (verts[vj + 0] - verts[vi + 0])
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0])) {
c = !c;
}
Tuple<float, float> edet = distancePtSegSqr2D(pt, verts, vj, vi);
ed[j] = edet.Item1;
et[j] = edet.Item2;
}
return c;
}
public static float[] projectPoly(float[] axis, float[] poly, int npoly) {
float rmin, rmax;
rmin = rmax = vDot2D(axis, poly, 0);
for (int i = 1; i < npoly; ++i) {
float d = vDot2D(axis, poly, i * 3);
rmin = Math.Min(rmin, d);
rmax = Math.Max(rmax, d);
}
return new float[] { rmin, rmax };
}
public static bool overlapRange(float amin, float amax, float bmin, float bmax, float eps) {
return ((amin + eps) > bmax || (amax - eps) < bmin) ? false : true;
}
static float eps = 1e-4f;
/// @par
///
/// All vertices are projected onto the xz-plane, so the y-values are ignored.
public static bool overlapPolyPoly2D(float[] polya, int npolya, float[] polyb, int npolyb) {
for (int i = 0, j = npolya - 1; i < npolya; j = i++) {
int va = j * 3;
int vb = i * 3;
float[] n = new float[] { polya[vb + 2] - polya[va + 2], 0, -(polya[vb + 0] - polya[va + 0]) };
float[] aminmax = projectPoly(n, polya, npolya);
float[] bminmax = projectPoly(n, polyb, npolyb);
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps)) {
// Found separating axis
return false;
}
}
for (int i = 0, j = npolyb - 1; i < npolyb; j = i++) {
int va = j * 3;
int vb = i * 3;
float[] n = new float[] { polyb[vb + 2] - polyb[va + 2], 0, -(polyb[vb + 0] - polyb[va + 0]) };
float[] aminmax = projectPoly(n, polya, npolya);
float[] bminmax = projectPoly(n, polyb, npolyb);
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps)) {
// Found separating axis
return false;
}
}
return true;
}
// Returns a random point in a convex polygon.
// Adapted from Graphics Gems article.
public static float[] randomPointInConvexPoly(float[] pts, int npts, float[] areas, float s, float t) {
// Calc triangle araes
float areasum = 0.0f;
for (int i = 2; i < npts; i++) {
areas[i] = triArea2D(pts, 0, (i - 1) * 3, i * 3);
areasum += Math.Max(0.001f, areas[i]);
}
// Find sub triangle weighted by area.
float thr = s * areasum;
float acc = 0.0f;
float u = 1.0f;
int tri = npts - 1;
for (int i = 2; i < npts; i++) {
float dacc = areas[i];
if (thr >= acc && thr < (acc + dacc)) {
u = (thr - acc) / dacc;
tri = i;
break;
}
acc += dacc;
}
float v = (float) Math.Sqrt(t);
float a = 1 - v;
float b = (1 - u) * v;
float c = u * v;
int pa = 0;
int pb = (tri - 1) * 3;
int pc = tri * 3;
return new float[] { a * pts[pa] + b * pts[pb] + c * pts[pc],
a * pts[pa + 1] + b * pts[pb + 1] + c * pts[pc + 1],
a * pts[pa + 2] + b * pts[pb + 2] + c * pts[pc + 2] };
}
public static int nextPow2(int v) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
public static int ilog2(int v) {
int r;
int shift;
r = (v > 0xffff ? 1 : 0) << 4;
v >>= r;
shift = (v > 0xff ? 1 : 0) << 3;
v >>= shift;
r |= shift;
shift = (v > 0xf ? 1 : 0) << 2;
v >>= shift;
r |= shift;
shift = (v > 0x3 ? 1 : 0) << 1;
v >>= shift;
r |= shift;
r |= (v >> 1);
return r;
}
public class IntersectResult {
public bool intersects;
public float tmin;
public float tmax = 1f;
public int segMin = -1;
public int segMax = -1;
}
public static IntersectResult intersectSegmentPoly2D(float[] p0, float[] p1, float[] verts, int nverts) {
IntersectResult result = new IntersectResult();
float EPS = 0.00000001f;
float[] dir = vSub(p1, p0);
VectorPtr p0v = new VectorPtr(p0);
for (int i = 0, j = nverts - 1; i < nverts; j = i++) {
VectorPtr vpj = new VectorPtr(verts, j * 3);
float[] edge = vSub(new VectorPtr(verts, i * 3), vpj);
float[] diff = vSub(p0v, vpj);
float n = vPerp2D(edge, diff);
float d = vPerp2D(dir, edge);
if (Math.Abs(d) < EPS) {
// S is nearly parallel to this edge
if (n < 0) {
return result;
} else {
continue;
}
}
float t = n / d;
if (d < 0) {
// segment S is entering across this edge
if (t > result.tmin) {
result.tmin = t;
result.segMin = j;
// S enters after leaving polygon
if (result.tmin > result.tmax) {
return result;
}
}
} else {
// segment S is leaving across this edge
if (t < result.tmax) {
result.tmax = t;
result.segMax = j;
// S leaves before entering polygon
if (result.tmax < result.tmin) {
return result;
}
}
}
}
result.intersects = true;
return result;
}
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] verts, int p, int q) {
float pqx = verts[q + 0] - verts[p + 0];
float pqz = verts[q + 2] - verts[p + 2];
float dx = pt[0] - verts[p + 0];
float dz = pt[2] - verts[p + 2];
float d = pqx * pqx + pqz * pqz;
float t = pqx * dx + pqz * dz;
if (d > 0) {
t /= d;
}
if (t < 0) {
t = 0;
} else if (t > 1) {
t = 1;
}
dx = verts[p + 0] + t * pqx - pt[0];
dz = verts[p + 2] + t * pqz - pt[2];
return Tuple.Create(dx * dx + dz * dz, t);
}
public static int oppositeTile(int side) {
return (side + 4) & 0x7;
}
public static float vperpXZ(float[] a, float[] b) {
return a[0] * b[2] - a[2] * b[0];
}
public static Tuple<float, float>? intersectSegSeg2D(float[] ap, float[] aq, float[] bp, float[] bq) {
float[] u = vSub(aq, ap);
float[] v = vSub(bq, bp);
float[] w = vSub(ap, bp);
float d = vperpXZ(u, v);
if (Math.Abs(d) < 1e-6f)
{
return null;
}
float s = vperpXZ(v, w) / d;
float t = vperpXZ(u, w) / d;
return Tuple.Create(s, t);
}
public static float[] vScale(float[] @in, float scale) {
float[] @out = new float[3];
@out[0] = @in[0] * scale;
@out[1] = @in[1] * scale;
@out[2] = @in[2] * scale;
return @out;
}
/// Checks that the specified vector's components are all finite.
/// @param[in] v A point. [(x, y, z)]
/// @return True if all of the point's components are finite, i.e. not NaN
/// or any of the infinities.
public static bool vIsFinite(float[] v) {
return float.IsFinite(v[0]) && float.IsFinite(v[1]) && float.IsFinite(v[2]);
}
/// Checks that the specified vector's 2D components are finite.
/// @param[in] v A point. [(x, y, z)]
public static bool vIsFinite2D(float[] v) {
return float.IsFinite(v[0]) && float.IsFinite(v[2]);
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,45 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
//TODO: (PP) Add comments
public class FindDistanceToWallResult {
private readonly float distance;
private readonly float[] position;
private readonly float[] normal;
public FindDistanceToWallResult(float distance, float[] position, float[] normal) {
this.distance = distance;
this.position = position;
this.normal = normal;
}
public float getDistance() {
return distance;
}
public float[] getPosition() {
return position;
}
public float[] getNormal() {
return normal;
}
}

View File

@ -0,0 +1,42 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
//TODO: (PP) Add comments
public class FindLocalNeighbourhoodResult {
private readonly List<long> refs;
private readonly List<long> parentRefs;
public FindLocalNeighbourhoodResult(List<long> refs, List<long> parentRefs) {
this.@refs = refs;
this.parentRefs = parentRefs;
}
public List<long> getRefs() {
return refs;
}
public List<long> getParentRefs() {
return parentRefs;
}
}

View File

@ -0,0 +1,53 @@
using System;
namespace DotRecast.Detour;
using static DetourCommon;
public class FindNearestPolyQuery : PolyQuery {
private readonly NavMeshQuery query;
private readonly float[] center;
private long nearestRef;
private float[] nearestPt;
private bool overPoly;
private float nearestDistanceSqr;
public FindNearestPolyQuery(NavMeshQuery query, float[] center) {
this.query = query;
this.center = center;
nearestDistanceSqr = float.MaxValue;
nearestPt = new float[] { center[0], center[1], center[2] };
}
public void process(MeshTile tile, Poly poly, long refs) {
// Find nearest polygon amongst the nearby polygons.
Result<ClosestPointOnPolyResult> closest = query.closestPointOnPoly(refs, center);
bool posOverPoly = closest.result.isPosOverPoly();
float[] closestPtPoly = closest.result.getClosest();
// If a point is directly over a polygon and closer than
// climb height, favor that instead of straight line nearest point.
float d = 0;
float[] diff = vSub(center, closestPtPoly);
if (posOverPoly) {
d = Math.Abs(diff[1]) - tile.data.header.walkableClimb;
d = d > 0 ? d * d : 0;
} else {
d = vLenSqr(diff);
}
if (d < nearestDistanceSqr) {
nearestPt = closestPtPoly;
nearestDistanceSqr = d;
nearestRef = refs;
overPoly = posOverPoly;
}
}
public FindNearestPolyResult result() {
return new FindNearestPolyResult(nearestRef, nearestPt, overPoly);
}
}

View File

@ -0,0 +1,46 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
public class FindNearestPolyResult {
private readonly long nearestRef;
private readonly float[] nearestPos;
private readonly bool overPoly;
public FindNearestPolyResult(long nearestRef, float[] nearestPos, bool overPoly) {
this.nearestRef = nearestRef;
this.nearestPos = nearestPos;
this.overPoly = overPoly;
}
/** Returns the reference id of the nearest polygon. 0 if no polygon is found. */
public long getNearestRef() {
return nearestRef;
}
/** Returns the nearest point on the polygon. [opt] [(x, y, z)]. Unchanged if no polygon is found. */
public float[] getNearestPos() {
return nearestPos;
}
public bool isOverPoly() {
return overPoly;
}
}

View File

@ -0,0 +1,49 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
// TODO: (PP) Add comments
public class FindPolysAroundResult {
private readonly List<long> refs;
private readonly List<long> parentRefs;
private readonly List<float> costs;
public FindPolysAroundResult(List<long> refs, List<long> parentRefs, List<float> costs) {
this.@refs = refs;
this.parentRefs = parentRefs;
this.costs = costs;
}
public List<long> getRefs() {
return refs;
}
public List<long> getParentRefs() {
return parentRefs;
}
public List<float> getCosts() {
return costs;
}
}

View File

@ -0,0 +1,41 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
//TODO: (PP) Add comments
public class FindRandomPointResult {
private readonly long randomRef;
private readonly float[] randomPt;
public FindRandomPointResult(long randomRef, float[] randomPt) {
this.randomRef = randomRef;
this.randomPt = randomPt;
}
/// @param[out] randomRef The reference id of the random location.
public long getRandomRef() {
return randomRef;
}
/// @param[out] randomPt The random location.
public float[] getRandomPt() {
return randomPt;
}
}

View File

@ -0,0 +1,43 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
public class GetPolyWallSegmentsResult {
private readonly List<float[]> segmentVerts;
private readonly List<long> segmentRefs;
public GetPolyWallSegmentsResult(List<float[]> segmentVerts, List<long> segmentRefs) {
this.segmentVerts = segmentVerts;
this.segmentRefs = segmentRefs;
}
public List<float[]> getSegmentVerts() {
return segmentVerts;
}
public List<long> getSegmentRefs() {
return segmentRefs;
}
}

View File

@ -0,0 +1,82 @@
/*
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 DetourWriter {
protected void write(BinaryWriter stream, float value, ByteOrder order) {
byte[] bytes = BitConverter.GetBytes(value);
int i = BitConverter.ToInt32(bytes, 0);
write(stream, i, order);
}
protected void write(BinaryWriter stream, short value, ByteOrder order) {
if (order == ByteOrder.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, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
write(stream, (int) (value >>> 32), order);
write(stream, (int) (value & 0xFFFFFFFF), order);
} else {
write(stream, (int) (value & 0xFFFFFFFF), order);
write(stream, (int) (value >>> 32), order);
}
}
protected void write(BinaryWriter stream, int value, ByteOrder order) {
if (order == ByteOrder.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,58 @@
/*
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 ByteBuffer toByteBuffer(BinaryReader @is, bool direct) {
byte[] data = toByteArray(@is);
if (direct) {
Array.Reverse(data);
}
return new ByteBuffer(data);
}
public static byte[] toByteArray(BinaryReader inputStream) {
using var baos = new MemoryStream();
byte[] buffer = new byte[4096];
int l;
while ((l = inputStream.Read(buffer)) > 0) {
baos.Write(buffer, 0, l);
}
return baos.ToArray();
}
public static ByteBuffer toByteBuffer(BinaryReader inputStream)
{
var bytes = toByteArray(inputStream);
return new ByteBuffer(bytes);
}
public static int swapEndianness(int i) {
var s = ((i >>> 24) & 0xFF) | ((i>>8) & 0xFF00) | ((i<<8) & 0xFF0000) | ((i << 24) & 0xFF000000);
return (int)s;
}
}

View File

@ -0,0 +1,201 @@
/*
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.IO;
using DotRecast.Core;
namespace DotRecast.Detour.Io;
public class MeshDataReader {
public const int DT_POLY_DETAIL_SIZE = 10;
public MeshData read(BinaryReader stream, int maxVertPerPoly) {
ByteBuffer buf = IOUtils.toByteBuffer(stream);
return read(buf, maxVertPerPoly, false);
}
public MeshData read(ByteBuffer buf, int maxVertPerPoly) {
return read(buf, maxVertPerPoly, false);
}
public MeshData read32Bit(BinaryReader stream, int maxVertPerPoly) {
ByteBuffer buf = IOUtils.toByteBuffer(stream);
return read(buf, maxVertPerPoly, true);
}
public MeshData read32Bit(ByteBuffer buf, int maxVertPerPoly) {
return read(buf, maxVertPerPoly, true);
}
public MeshData read(ByteBuffer buf, int maxVertPerPoly, bool is32Bit)
{
MeshData data = new MeshData();
MeshHeader header = new MeshHeader();
data.header = header;
header.magic = buf.getInt();
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC) {
header.magic = IOUtils.swapEndianness(header.magic);
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC) {
throw new IOException("Invalid magic");
}
buf.order(buf.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
header.version = buf.getInt();
if (header.version != MeshHeader.DT_NAVMESH_VERSION) {
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_FIRST
|| header.version > MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST) {
throw new IOException("Invalid version " + header.version);
}
}
bool cCompatibility = header.version == MeshHeader.DT_NAVMESH_VERSION;
header.x = buf.getInt();
header.y = buf.getInt();
header.layer = buf.getInt();
header.userId = buf.getInt();
header.polyCount = buf.getInt();
header.vertCount = buf.getInt();
header.maxLinkCount = buf.getInt();
header.detailMeshCount = buf.getInt();
header.detailVertCount = buf.getInt();
header.detailTriCount = buf.getInt();
header.bvNodeCount = buf.getInt();
header.offMeshConCount = buf.getInt();
header.offMeshBase = buf.getInt();
header.walkableHeight = buf.getFloat();
header.walkableRadius = buf.getFloat();
header.walkableClimb = buf.getFloat();
for (int j = 0; j < 3; j++) {
header.bmin[j] = buf.getFloat();
}
for (int j = 0; j < 3; j++) {
header.bmax[j] = buf.getFloat();
}
header.bvQuantFactor = buf.getFloat();
data.verts = readVerts(buf, header.vertCount);
data.polys = readPolys(buf, header, maxVertPerPoly);
if (cCompatibility) {
buf.position(buf.position() + header.maxLinkCount * getSizeofLink(is32Bit));
}
data.detailMeshes = readPolyDetails(buf, header, cCompatibility);
data.detailVerts = readVerts(buf, header.detailVertCount);
data.detailTris = readDTris(buf, header);
data.bvTree = readBVTree(buf, header);
data.offMeshCons = readOffMeshCons(buf, header);
return data;
}
public const int LINK_SIZEOF = 16;
public const int LINK_SIZEOF32BIT = 12;
public static int getSizeofLink(bool is32Bit) {
return is32Bit ? LINK_SIZEOF32BIT : LINK_SIZEOF;
}
private float[] readVerts(ByteBuffer buf, int count) {
float[] verts = new float[count * 3];
for (int i = 0; i < verts.Length; i++) {
verts[i] = buf.getFloat();
}
return verts;
}
private Poly[] readPolys(ByteBuffer buf, MeshHeader header, int maxVertPerPoly) {
Poly[] polys = new Poly[header.polyCount];
for (int i = 0; i < polys.Length; i++) {
polys[i] = new Poly(i, maxVertPerPoly);
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK) {
buf.getInt(); // polys[i].firstLink
}
for (int j = 0; j < polys[i].verts.Length; j++) {
polys[i].verts[j] = buf.getShort() & 0xFFFF;
}
for (int j = 0; j < polys[i].neis.Length; j++) {
polys[i].neis[j] = buf.getShort() & 0xFFFF;
}
polys[i].flags = buf.getShort() & 0xFFFF;
polys[i].vertCount = buf.get() & 0xFF;
polys[i].areaAndtype = buf.get() & 0xFF;
}
return polys;
}
private PolyDetail[] readPolyDetails(ByteBuffer buf, MeshHeader header, bool cCompatibility) {
PolyDetail[] polys = new PolyDetail[header.detailMeshCount];
for (int i = 0; i < polys.Length; i++) {
polys[i] = new PolyDetail();
polys[i].vertBase = buf.getInt();
polys[i].triBase = buf.getInt();
polys[i].vertCount = buf.get() & 0xFF;
polys[i].triCount = buf.get() & 0xFF;
if (cCompatibility) {
buf.getShort(); // C struct padding
}
}
return polys;
}
private int[] readDTris(ByteBuffer buf, MeshHeader header) {
int[] tris = new int[4 * header.detailTriCount];
for (int i = 0; i < tris.Length; i++) {
tris[i] = buf.get() & 0xFF;
}
return tris;
}
private BVNode[] readBVTree(ByteBuffer buf, MeshHeader header) {
BVNode[] nodes = new BVNode[header.bvNodeCount];
for (int i = 0; i < nodes.Length; i++) {
nodes[i] = new BVNode();
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE) {
for (int j = 0; j < 3; j++) {
nodes[i].bmin[j] = buf.getShort() & 0xFFFF;
}
for (int j = 0; j < 3; j++) {
nodes[i].bmax[j] = buf.getShort() & 0xFFFF;
}
} else {
for (int j = 0; j < 3; j++) {
nodes[i].bmin[j] = buf.getInt();
}
for (int j = 0; j < 3; j++) {
nodes[i].bmax[j] = buf.getInt();
}
}
nodes[i].i = buf.getInt();
}
return nodes;
}
private OffMeshConnection[] readOffMeshCons(ByteBuffer buf, MeshHeader header) {
OffMeshConnection[] cons = new OffMeshConnection[header.offMeshConCount];
for (int i = 0; i < cons.Length; i++) {
cons[i] = new OffMeshConnection();
for (int j = 0; j < 6; j++) {
cons[i].pos[j] = buf.getFloat();
}
cons[i].rad = buf.getFloat();
cons[i].poly = buf.getShort() & 0xFFFF;
cons[i].flags = buf.get() & 0xFF;
cons[i].side = buf.get() & 0xFF;
cons[i].userId = buf.getInt();
}
return cons;
}
}

View File

@ -0,0 +1,142 @@
/*
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.IO;
using DotRecast.Core;
namespace DotRecast.Detour.Io;
public class MeshDataWriter : DetourWriter {
public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
MeshHeader header = data.header;
write(stream, header.magic, order);
write(stream, cCompatibility ? MeshHeader.DT_NAVMESH_VERSION : MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
write(stream, header.x, order);
write(stream, header.y, order);
write(stream, header.layer, order);
write(stream, header.userId, order);
write(stream, header.polyCount, order);
write(stream, header.vertCount, order);
write(stream, header.maxLinkCount, order);
write(stream, header.detailMeshCount, order);
write(stream, header.detailVertCount, order);
write(stream, header.detailTriCount, order);
write(stream, header.bvNodeCount, order);
write(stream, header.offMeshConCount, order);
write(stream, header.offMeshBase, order);
write(stream, header.walkableHeight, order);
write(stream, header.walkableRadius, order);
write(stream, header.walkableClimb, order);
write(stream, header.bmin[0], order);
write(stream, header.bmin[1], order);
write(stream, header.bmin[2], order);
write(stream, header.bmax[0], order);
write(stream, header.bmax[1], order);
write(stream, header.bmax[2], order);
write(stream, header.bvQuantFactor, order);
writeVerts(stream, data.verts, header.vertCount, order);
writePolys(stream, data, order, cCompatibility);
if (cCompatibility) {
byte[] linkPlaceholder = new byte[header.maxLinkCount * MeshDataReader.getSizeofLink(false)];
stream.Write(linkPlaceholder);
}
writePolyDetails(stream, data, order, cCompatibility);
writeVerts(stream, data.detailVerts, header.detailVertCount, order);
writeDTris(stream, data);
writeBVTree(stream, data, order, cCompatibility);
writeOffMeshCons(stream, data, order);
}
private void writeVerts(BinaryWriter stream, float[] verts, int count, ByteOrder order) {
for (int i = 0; i < count * 3; i++) {
write(stream, verts[i], order);
}
}
private void writePolys(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
for (int i = 0; i < data.header.polyCount; i++) {
if (cCompatibility) {
write(stream, 0xFFFF, order);
}
for (int j = 0; j < data.polys[i].verts.Length; j++) {
write(stream, (short) data.polys[i].verts[j], order);
}
for (int j = 0; j < data.polys[i].neis.Length; j++) {
write(stream, (short) data.polys[i].neis[j], order);
}
write(stream, (short) data.polys[i].flags, order);
write(stream, (byte)data.polys[i].vertCount);
write(stream, (byte)data.polys[i].areaAndtype);
}
}
private void writePolyDetails(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{
for (int i = 0; i < data.header.detailMeshCount; i++) {
write(stream, data.detailMeshes[i].vertBase, order);
write(stream, data.detailMeshes[i].triBase, order);
write(stream, (byte)data.detailMeshes[i].vertCount);
write(stream, (byte)data.detailMeshes[i].triCount);
if (cCompatibility) {
write(stream, (short) 0, order);
}
}
}
private void writeDTris(BinaryWriter stream, MeshData data) {
for (int i = 0; i < data.header.detailTriCount * 4; i++) {
write(stream, (byte)data.detailTris[i]);
}
}
private void writeBVTree(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
for (int i = 0; i < data.header.bvNodeCount; i++) {
if (cCompatibility) {
for (int j = 0; j < 3; j++) {
write(stream, (short) data.bvTree[i].bmin[j], order);
}
for (int j = 0; j < 3; j++) {
write(stream, (short) data.bvTree[i].bmax[j], order);
}
} else {
for (int j = 0; j < 3; j++) {
write(stream, data.bvTree[i].bmin[j], order);
}
for (int j = 0; j < 3; j++) {
write(stream, data.bvTree[i].bmax[j], order);
}
}
write(stream, data.bvTree[i].i, order);
}
}
private void writeOffMeshCons(BinaryWriter stream, MeshData data, ByteOrder order) {
for (int i = 0; i < data.header.offMeshConCount; i++) {
for (int j = 0; j < 6; j++) {
write(stream, data.offMeshCons[i].pos[j], order);
}
write(stream, data.offMeshCons[i].rad, order);
write(stream, (short) data.offMeshCons[i].poly, order);
write(stream, (byte) data.offMeshCons[i].flags);
write(stream, (byte) data.offMeshCons[i].side);
write(stream, data.offMeshCons[i].userId, order);
}
}
}

View File

@ -0,0 +1,127 @@
/*
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;
using static DetourCommon;
public class MeshSetReader {
private readonly MeshDataReader meshReader = new MeshDataReader();
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public NavMesh read(BinaryReader @is, int maxVertPerPoly) {
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, false);
}
public NavMesh read(ByteBuffer bb, int maxVertPerPoly) {
return read(bb, maxVertPerPoly, false);
}
public NavMesh read32Bit(BinaryReader @is, int maxVertPerPoly) {
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, true);
}
public NavMesh read32Bit(ByteBuffer bb, int maxVertPerPoly) {
return read(bb, maxVertPerPoly, true);
}
public NavMesh read(BinaryReader @is) {
return read(IOUtils.toByteBuffer(@is));
}
public NavMesh read(ByteBuffer bb) {
return read(bb, -1, false);
}
NavMesh read(ByteBuffer bb, int maxVertPerPoly, bool is32Bit) {
NavMeshSetHeader header = readHeader(bb, maxVertPerPoly);
if (header.maxVertsPerPoly <= 0) {
throw new IOException("Invalid number of verts per poly " + header.maxVertsPerPoly);
}
bool cCompatibility = header.version == NavMeshSetHeader.NAVMESHSET_VERSION;
NavMesh mesh = new NavMesh(header.option, header.maxVertsPerPoly);
readTiles(bb, is32Bit, header, cCompatibility, mesh);
return mesh;
}
private NavMeshSetHeader readHeader(ByteBuffer bb, int maxVertsPerPoly) {
NavMeshSetHeader header = new NavMeshSetHeader();
header.magic = bb.getInt();
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) {
header.magic = IOUtils.swapEndianness(header.magic);
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) {
throw new IOException("Invalid magic " + header.magic);
}
bb.order(bb.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
header.version = bb.getInt();
if (header.version != NavMeshSetHeader.NAVMESHSET_VERSION && header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J_1
&& header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J) {
throw new IOException("Invalid version " + header.version);
}
header.numTiles = bb.getInt();
header.option = paramReader.read(bb);
header.maxVertsPerPoly = maxVertsPerPoly;
if (header.version == NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J) {
header.maxVertsPerPoly = bb.getInt();
}
return header;
}
private void readTiles(ByteBuffer bb, bool is32Bit, NavMeshSetHeader header, bool cCompatibility, NavMesh mesh)
{
// Read tiles.
for (int i = 0; i < header.numTiles; ++i) {
NavMeshTileHeader tileHeader = new NavMeshTileHeader();
if (is32Bit) {
tileHeader.tileRef = convert32BitRef(bb.getInt(), header.option);
} else {
tileHeader.tileRef = bb.getLong();
}
tileHeader.dataSize = bb.getInt();
if (tileHeader.tileRef == 0 || tileHeader.dataSize == 0) {
break;
}
if (cCompatibility && !is32Bit) {
bb.getInt(); // C struct padding
}
MeshData data = meshReader.read(bb, mesh.getMaxVertsPerPoly(), is32Bit);
mesh.addTile(data, i, tileHeader.tileRef);
}
}
private long convert32BitRef(int refs, NavMeshParams option) {
int m_tileBits = ilog2(nextPow2(option.maxTiles));
int m_polyBits = ilog2(nextPow2(option.maxPolys));
// Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow.
int m_saltBits = Math.Min(31, 32 - m_tileBits - m_polyBits);
int saltMask = (1 << m_saltBits) - 1;
int tileMask = (1 << m_tileBits) - 1;
int polyMask = (1 << m_polyBits) - 1;
int salt = ((refs >> (m_polyBits + m_tileBits)) & saltMask);
int it = ((refs >> m_polyBits) & tileMask);
int ip = refs & polyMask;
return NavMesh.encodePolyId(salt, it, ip);
}
}

View File

@ -0,0 +1,78 @@
/*
Recast4J Copyright (c) 2015-2018 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.IO;
using DotRecast.Core;
namespace DotRecast.Detour.Io;
public class MeshSetWriter : DetourWriter {
private readonly MeshDataWriter writer = new MeshDataWriter();
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
public void write(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
writeHeader(stream, mesh, order, cCompatibility);
writeTiles(stream, mesh, order, cCompatibility);
}
private void writeHeader(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
write(stream, NavMeshSetHeader.NAVMESHSET_MAGIC, order);
write(stream, cCompatibility ? NavMeshSetHeader.NAVMESHSET_VERSION : NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J, order);
int numTiles = 0;
for (int i = 0; i < mesh.getMaxTiles(); ++i) {
MeshTile tile = mesh.getTile(i);
if (tile == null || tile.data == null || tile.data.header == null) {
continue;
}
numTiles++;
}
write(stream, numTiles, order);
paramWriter.write(stream, mesh.getParams(), order);
if (!cCompatibility) {
write(stream, mesh.getMaxVertsPerPoly(), order);
}
}
private void writeTiles(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
for (int i = 0; i < mesh.getMaxTiles(); ++i) {
MeshTile tile = mesh.getTile(i);
if (tile == null || tile.data == null || tile.data.header == null) {
continue;
}
NavMeshTileHeader tileHeader = new NavMeshTileHeader();
tileHeader.tileRef = mesh.getTileRef(tile);
using MemoryStream bb = new MemoryStream();
using BinaryWriter baos = new BinaryWriter(bb);
writer.write(baos, tile.data, order, cCompatibility);
baos.Flush();
baos.Close();
byte[] ba = bb.ToArray();
tileHeader.dataSize = ba.Length;
write(stream, tileHeader.tileRef, order);
write(stream, tileHeader.dataSize, order);
if (cCompatibility) {
write(stream, 0, order); // C struct padding
}
stream.Write(ba);
}
}
}

View File

@ -0,0 +1,19 @@
using DotRecast.Core;
namespace DotRecast.Detour.Io;
public class NavMeshParamReader {
public NavMeshParams read(ByteBuffer bb) {
NavMeshParams option = new NavMeshParams();
option.orig[0] = bb.getFloat();
option.orig[1] = bb.getFloat();
option.orig[2] = bb.getFloat();
option.tileWidth = bb.getFloat();
option.tileHeight = bb.getFloat();
option.maxTiles = bb.getInt();
option.maxPolys = bb.getInt();
return option;
}
}

View File

@ -0,0 +1,18 @@
using System.IO;
using DotRecast.Core;
namespace DotRecast.Detour.Io;
public class NavMeshParamWriter : DetourWriter {
public void write(BinaryWriter stream, NavMeshParams option, ByteOrder order) {
write(stream, option.orig[0], order);
write(stream, option.orig[1], order);
write(stream, option.orig[2], order);
write(stream, option.tileWidth, order);
write(stream, option.tileHeight, order);
write(stream, option.maxTiles, order);
write(stream, option.maxPolys, order);
}
}

View File

@ -0,0 +1,33 @@
/*
Recast4J Copyright (c) 2015-2018 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.
*/
namespace DotRecast.Detour.Io;
public class NavMeshSetHeader {
public const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // 'MSET';
public const int NAVMESHSET_VERSION = 1;
public const int NAVMESHSET_VERSION_RECAST4J_1 = 0x8801;
public const int NAVMESHSET_VERSION_RECAST4J = 0x8802;
public int magic;
public int version;
public int numTiles;
public NavMeshParams option = new NavMeshParams();
public int maxVertsPerPoly;
}

View File

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

View File

@ -0,0 +1,730 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
namespace DotRecast.Detour;
using static DetourCommon;
public class LegacyNavMeshQuery : NavMeshQuery {
private static float H_SCALE = 0.999f; // Search heuristic scale.
public LegacyNavMeshQuery(NavMesh nav) : base(nav) {
}
public override Result<List<long>> findPath(long startRef, long endRef, float[] startPos, float[] endPos, QueryFilter filter,
int options, float raycastLimit) {
return findPath(startRef, endRef, startPos, endPos, filter);
}
public override Result<List<long>> findPath(long startRef, long endRef, float[] startPos, float[] endPos,
QueryFilter filter) {
// Validate input
if (!m_nav.isValidPolyRef(startRef) || !m_nav.isValidPolyRef(endRef) || null == startPos
|| !vIsFinite(startPos) || null == endPos || !vIsFinite(endPos) || null == filter) {
return Results.invalidParam<List<long>>();
}
if (startRef == endRef) {
List<long> singlePath = new(1);
singlePath.Add(startRef);
return Results.success(singlePath);
}
m_nodePool.clear();
m_openList.clear();
Node startNode = m_nodePool.getNode(startRef);
vCopy(startNode.pos, startPos);
startNode.pidx = 0;
startNode.cost = 0;
startNode.total = vDist(startPos, endPos) * H_SCALE;
startNode.id = startRef;
startNode.flags = Node.DT_NODE_OPEN;
m_openList.push(startNode);
Node lastBestNode = startNode;
float lastBestNodeCost = startNode.total;
Status status = Status.SUCCSESS;
while (!m_openList.isEmpty()) {
// Remove node from open list and put it in closed list.
Node bestNode = m_openList.pop();
bestNode.flags &= ~Node.DT_NODE_OPEN;
bestNode.flags |= Node.DT_NODE_CLOSED;
// Reached the goal, stop searching.
if (bestNode.id == endRef) {
lastBestNode = bestNode;
break;
}
// Get current poly and tile.
// The API input has been cheked already, skip checking internal data.
long bestRef = bestNode.id;
Tuple<MeshTile, Poly> tileAndPoly = m_nav.getTileAndPolyByRefUnsafe(bestRef);
MeshTile bestTile = tileAndPoly.Item1;
Poly bestPoly = tileAndPoly.Item2;
// Get parent poly and tile.
long parentRef = 0;
MeshTile parentTile = null;
Poly parentPoly = null;
if (bestNode.pidx != 0) {
parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id;
}
if (parentRef != 0) {
tileAndPoly = m_nav.getTileAndPolyByRefUnsafe(parentRef);
parentTile = tileAndPoly.Item1;
parentPoly = tileAndPoly.Item2;
}
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
long neighbourRef = bestTile.links[i].refs;
// Skip invalid ids and do not expand back to where we came from.
if (neighbourRef == 0 || neighbourRef == parentRef) {
continue;
}
// Get neighbour poly and tile.
// The API input has been cheked already, skip checking internal data.
tileAndPoly = m_nav.getTileAndPolyByRefUnsafe(neighbourRef);
MeshTile neighbourTile = tileAndPoly.Item1;
Poly neighbourPoly = tileAndPoly.Item2;
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
continue;
}
// deal explicitly with crossing tile boundaries
int crossSide = 0;
if (bestTile.links[i].side != 0xff) {
crossSide = bestTile.links[i].side >> 1;
}
// get the node
Node neighbourNode = m_nodePool.getNode(neighbourRef, crossSide);
// If the node is visited the first time, calculate node position.
if (neighbourNode.flags == 0) {
Result<float[]> midpod = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
neighbourTile);
if (!midpod.failed()) {
neighbourNode.pos = midpod.result;
}
}
// Calculate cost and heuristic.
float cost = 0;
float heuristic = 0;
// Special case for last node.
if (neighbourRef == endRef) {
// Cost
float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly,
bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
float endCost = filter.getCost(neighbourNode.pos, endPos, bestRef, bestTile, bestPoly, neighbourRef,
neighbourTile, neighbourPoly, 0L, null, null);
cost = bestNode.cost + curCost + endCost;
heuristic = 0;
} else {
// Cost
float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly,
bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
cost = bestNode.cost + curCost;
heuristic = vDist(neighbourNode.pos, endPos) * H_SCALE;
}
float total = cost + heuristic;
// The node is already in open list and the new result is worse, skip.
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
continue;
}
// The node is already visited and process, and the new result is worse, skip.
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) {
continue;
}
// Add or update the node.
neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode);
neighbourNode.id = neighbourRef;
neighbourNode.flags = (neighbourNode.flags & ~Node.DT_NODE_CLOSED);
neighbourNode.cost = cost;
neighbourNode.total = total;
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
// Already in open, update node location.
m_openList.modify(neighbourNode);
} else {
// Put the node in open list.
neighbourNode.flags |= Node.DT_NODE_OPEN;
m_openList.push(neighbourNode);
}
// Update nearest node to target so far.
if (heuristic < lastBestNodeCost) {
lastBestNodeCost = heuristic;
lastBestNode = neighbourNode;
}
}
}
List<long> path = getPathToNode(lastBestNode);
if (lastBestNode.id != endRef) {
status = Status.PARTIAL_RESULT;
}
return Results.of(status, path);
}
/**
* Updates an in-progress sliced path query.
*
* @param maxIter
* The maximum number of iterations to perform.
* @return The status flags for the query.
*/
public override Result<int> updateSlicedFindPath(int maxIter) {
if (!m_query.status.isInProgress()) {
return Results.of(m_query.status, 0);
}
// Make sure the request is still valid.
if (!m_nav.isValidPolyRef(m_query.startRef) || !m_nav.isValidPolyRef(m_query.endRef)) {
m_query.status = Status.FAILURE;
return Results.of(m_query.status, 0);
}
int iter = 0;
while (iter < maxIter && !m_openList.isEmpty()) {
iter++;
// Remove node from open list and put it in closed list.
Node bestNode = m_openList.pop();
bestNode.flags &= ~Node.DT_NODE_OPEN;
bestNode.flags |= Node.DT_NODE_CLOSED;
// Reached the goal, stop searching.
if (bestNode.id == m_query.endRef) {
m_query.lastBestNode = bestNode;
m_query.status = Status.SUCCSESS;
return Results.of(m_query.status, iter);
}
// Get current poly and tile.
// The API input has been cheked already, skip checking internal
// data.
long bestRef = bestNode.id;
Result<Tuple<MeshTile, Poly>> tileAndPoly = m_nav.getTileAndPolyByRef(bestRef);
if (tileAndPoly.failed()) {
m_query.status = Status.FAILURE;
// The polygon has disappeared during the sliced query, fail.
return Results.of(m_query.status, iter);
}
MeshTile bestTile = tileAndPoly.result.Item1;
Poly bestPoly = tileAndPoly.result.Item2;
// Get parent and grand parent poly and tile.
long parentRef = 0, grandpaRef = 0;
MeshTile parentTile = null;
Poly parentPoly = null;
Node parentNode = null;
if (bestNode.pidx != 0) {
parentNode = m_nodePool.getNodeAtIdx(bestNode.pidx);
parentRef = parentNode.id;
if (parentNode.pidx != 0) {
grandpaRef = m_nodePool.getNodeAtIdx(parentNode.pidx).id;
}
}
if (parentRef != 0) {
bool invalidParent = false;
tileAndPoly = m_nav.getTileAndPolyByRef(parentRef);
invalidParent = tileAndPoly.failed();
if (invalidParent || (grandpaRef != 0 && !m_nav.isValidPolyRef(grandpaRef))) {
// The polygon has disappeared during the sliced query,
// fail.
m_query.status = Status.FAILURE;
return Results.of(m_query.status, iter);
}
parentTile = tileAndPoly.result.Item1;
parentPoly = tileAndPoly.result.Item2;
}
// decide whether to test raycast to previous nodes
bool tryLOS = false;
if ((m_query.options & DT_FINDPATH_ANY_ANGLE) != 0) {
if ((parentRef != 0) && (vDistSqr(parentNode.pos, bestNode.pos) < m_query.raycastLimitSqr)) {
tryLOS = true;
}
}
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
long neighbourRef = bestTile.links[i].refs;
// Skip invalid ids and do not expand back to where we came
// from.
if (neighbourRef == 0 || neighbourRef == parentRef) {
continue;
}
// Get neighbour poly and tile.
// The API input has been cheked already, skip checking internal
// data.
Tuple<MeshTile, Poly> tileAndPolyUns = m_nav.getTileAndPolyByRefUnsafe(neighbourRef);
MeshTile neighbourTile = tileAndPolyUns.Item1;
Poly neighbourPoly = tileAndPolyUns.Item2;
if (!m_query.filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
continue;
}
// get the neighbor node
Node neighbourNode = m_nodePool.getNode(neighbourRef, 0);
// do not expand to nodes that were already visited from the
// same parent
if (neighbourNode.pidx != 0 && neighbourNode.pidx == bestNode.pidx) {
continue;
}
// If the node is visited the first time, calculate node
// position.
if (neighbourNode.flags == 0) {
Result<float[]> midpod = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
neighbourTile);
if (!midpod.failed()) {
neighbourNode.pos = midpod.result;
}
}
// Calculate cost and heuristic.
float cost = 0;
float heuristic = 0;
// raycast parent
bool foundShortCut = false;
if (tryLOS) {
Result<RaycastHit> rayHit = raycast(parentRef, parentNode.pos, neighbourNode.pos, m_query.filter,
DT_RAYCAST_USE_COSTS, grandpaRef);
if (rayHit.succeeded()) {
foundShortCut = rayHit.result.t >= 1.0f;
if (foundShortCut) {
// shortcut found using raycast. Using shorter cost
// instead
cost = parentNode.cost + rayHit.result.pathCost;
}
}
}
// update move cost
if (!foundShortCut) {
// No shortcut found.
float curCost = m_query.filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile,
parentPoly, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
cost = bestNode.cost + curCost;
}
// Special case for last node.
if (neighbourRef == m_query.endRef) {
float endCost = m_query.filter.getCost(neighbourNode.pos, m_query.endPos, bestRef, bestTile,
bestPoly, neighbourRef, neighbourTile, neighbourPoly, 0, null, null);
cost = cost + endCost;
heuristic = 0;
} else {
heuristic = vDist(neighbourNode.pos, m_query.endPos) * H_SCALE;
}
float total = cost + heuristic;
// The node is already in open list and the new result is worse,
// skip.
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
continue;
}
// The node is already visited and process, and the new result
// is worse, skip.
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) {
continue;
}
// Add or update the node.
neighbourNode.pidx = foundShortCut ? bestNode.pidx : m_nodePool.getNodeIdx(bestNode);
neighbourNode.id = neighbourRef;
neighbourNode.flags = (neighbourNode.flags & ~(Node.DT_NODE_CLOSED | Node.DT_NODE_PARENT_DETACHED));
neighbourNode.cost = cost;
neighbourNode.total = total;
if (foundShortCut) {
neighbourNode.flags = (neighbourNode.flags | Node.DT_NODE_PARENT_DETACHED);
}
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
// Already in open, update node location.
m_openList.modify(neighbourNode);
} else {
// Put the node in open list.
neighbourNode.flags |= Node.DT_NODE_OPEN;
m_openList.push(neighbourNode);
}
// Update nearest node to target so far.
if (heuristic < m_query.lastBestNodeCost) {
m_query.lastBestNodeCost = heuristic;
m_query.lastBestNode = neighbourNode;
}
}
}
// Exhausted all nodes, but could not find path.
if (m_openList.isEmpty()) {
m_query.status = Status.PARTIAL_RESULT;
}
return Results.of(m_query.status, iter);
}
/// Finalizes and returns the results of a sliced path query.
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
/// [(polyRef) * @p pathCount]
/// @returns The status flags for the query.
public override Result<List<long>> finalizeSlicedFindPath() {
List<long> path = new(64);
if (m_query.status.isFailed()) {
// Reset query.
m_query = new QueryData();
return Results.failure(path);
}
if (m_query.startRef == m_query.endRef) {
// Special case: the search starts and ends at same poly.
path.Add(m_query.startRef);
} else {
// Reverse the path.
if (m_query.lastBestNode.id != m_query.endRef) {
m_query.status = Status.PARTIAL_RESULT;
}
Node prev = null;
Node node = m_query.lastBestNode;
int prevRay = 0;
do {
Node next = m_nodePool.getNodeAtIdx(node.pidx);
node.pidx = m_nodePool.getNodeIdx(prev);
prev = node;
int nextRay = node.flags & Node.DT_NODE_PARENT_DETACHED; // keep track of whether parent is not adjacent
// (i.e. due to raycast shortcut)
node.flags = (node.flags & ~Node.DT_NODE_PARENT_DETACHED) | prevRay; // and store it in the reversed
// path's node
prevRay = nextRay;
node = next;
} while (node != null);
// Store path
node = prev;
do {
Node next = m_nodePool.getNodeAtIdx(node.pidx);
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0) {
Result<RaycastHit> iresult = raycast(node.id, node.pos, next.pos, m_query.filter, 0, 0);
if (iresult.succeeded()) {
path.AddRange(iresult.result.path);
}
// raycast ends on poly boundary and the path might include the next poly boundary.
if (path[path.Count - 1] == next.id) {
path.RemoveAt(path.Count - 1); // remove to avoid duplicates
}
} else {
path.Add(node.id);
}
node = next;
} while (node != null);
}
Status status = m_query.status;
// Reset query.
m_query = new QueryData();
return Results.of(status, path);
}
/// Finalizes and returns the results of an incomplete sliced path query, returning the path to the furthest
/// polygon on the existing path that was visited during the search.
/// @param[in] existing An array of polygon references for the existing path.
/// @param[in] existingSize The number of polygon in the @p existing array.
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
/// [(polyRef) * @p pathCount]
/// @returns The status flags for the query.
public override Result<List<long>> finalizeSlicedFindPathPartial(List<long> existing) {
List<long> path = new(64);
if (null == existing || existing.Count <= 0) {
return Results.failure(path);
}
if (m_query.status.isFailed()) {
// Reset query.
m_query = new QueryData();
return Results.failure(path);
}
if (m_query.startRef == m_query.endRef) {
// Special case: the search starts and ends at same poly.
path.Add(m_query.startRef);
} else {
// Find furthest existing node that was visited.
Node prev = null;
Node node = null;
for (int i = existing.Count - 1; i >= 0; --i) {
node = m_nodePool.findNode(existing[i]);
if (node != null) {
break;
}
}
if (node == null) {
m_query.status = Status.PARTIAL_RESULT;
node = m_query.lastBestNode;
}
// Reverse the path.
int prevRay = 0;
do {
Node next = m_nodePool.getNodeAtIdx(node.pidx);
node.pidx = m_nodePool.getNodeIdx(prev);
prev = node;
int nextRay = node.flags & Node.DT_NODE_PARENT_DETACHED; // keep track of whether parent is not adjacent
// (i.e. due to raycast shortcut)
node.flags = (node.flags & ~Node.DT_NODE_PARENT_DETACHED) | prevRay; // and store it in the reversed
// path's node
prevRay = nextRay;
node = next;
} while (node != null);
// Store path
node = prev;
do {
Node next = m_nodePool.getNodeAtIdx(node.pidx);
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0) {
Result<RaycastHit> iresult = raycast(node.id, node.pos, next.pos, m_query.filter, 0, 0);
if (iresult.succeeded()) {
path.AddRange(iresult.result.path);
}
// raycast ends on poly boundary and the path might include the next poly boundary.
if (path[path.Count - 1] == next.id) {
path.RemoveAt(path.Count - 1); // remove to avoid duplicates
}
} else {
path.Add(node.id);
}
node = next;
} while (node != null);
}
Status status = m_query.status;
// Reset query.
m_query = new QueryData();
return Results.of(status, path);
}
public override Result<FindDistanceToWallResult> findDistanceToWall(long startRef, float[] centerPos, float maxRadius,
QueryFilter filter) {
// Validate input
if (!m_nav.isValidPolyRef(startRef) || null == centerPos || !vIsFinite(centerPos) || maxRadius < 0
|| !float.IsFinite(maxRadius) || null == filter) {
return Results.invalidParam<FindDistanceToWallResult>();
}
m_nodePool.clear();
m_openList.clear();
Node startNode = m_nodePool.getNode(startRef);
vCopy(startNode.pos, centerPos);
startNode.pidx = 0;
startNode.cost = 0;
startNode.total = 0;
startNode.id = startRef;
startNode.flags = Node.DT_NODE_OPEN;
m_openList.push(startNode);
float radiusSqr = sqr(maxRadius);
float[] hitPos = new float[3];
VectorPtr bestvj = null;
VectorPtr bestvi = null;
while (!m_openList.isEmpty()) {
Node bestNode = m_openList.pop();
bestNode.flags &= ~Node.DT_NODE_OPEN;
bestNode.flags |= Node.DT_NODE_CLOSED;
// Get poly and tile.
// The API input has been cheked already, skip checking internal data.
long bestRef = bestNode.id;
Tuple<MeshTile, Poly> tileAndPoly = m_nav.getTileAndPolyByRefUnsafe(bestRef);
MeshTile bestTile = tileAndPoly.Item1;
Poly bestPoly = tileAndPoly.Item2;
// Get parent poly and tile.
long parentRef = 0;
if (bestNode.pidx != 0) {
parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id;
}
// Hit test walls.
for (int i = 0, j = bestPoly.vertCount - 1; i < bestPoly.vertCount; j = i++) {
// Skip non-solid edges.
if ((bestPoly.neis[j] & NavMesh.DT_EXT_LINK) != 0) {
// Tile border.
bool solid = true;
for (int k = bestTile.polyLinks[bestPoly.index]; k != NavMesh.DT_NULL_LINK; k = bestTile.links[k].next) {
Link link = bestTile.links[k];
if (link.edge == j) {
if (link.refs != 0) {
Tuple<MeshTile, Poly> linkTileAndPoly = m_nav.getTileAndPolyByRefUnsafe(link.refs);
MeshTile neiTile = linkTileAndPoly.Item1;
Poly neiPoly = linkTileAndPoly.Item2;
if (filter.passFilter(link.refs, neiTile, neiPoly)) {
solid = false;
}
}
break;
}
}
if (!solid) {
continue;
}
} else if (bestPoly.neis[j] != 0) {
// Internal edge
int idx = (bestPoly.neis[j] - 1);
long refs = m_nav.getPolyRefBase(bestTile) | idx;
if (filter.passFilter(refs, bestTile, bestTile.data.polys[idx])) {
continue;
}
}
// Calc distance to the edge.
int vj = bestPoly.verts[j] * 3;
int vi = bestPoly.verts[i] * 3;
Tuple<float, float> distseg = distancePtSegSqr2D(centerPos, bestTile.data.verts, vj, vi);
float distSqr = distseg.Item1;
float tseg = distseg.Item2;
// Edge is too far, skip.
if (distSqr > radiusSqr) {
continue;
}
// Hit wall, update radius.
radiusSqr = distSqr;
// Calculate hit pos.
hitPos[0] = bestTile.data.verts[vj] + (bestTile.data.verts[vi] - bestTile.data.verts[vj]) * tseg;
hitPos[1] = bestTile.data.verts[vj + 1]
+ (bestTile.data.verts[vi + 1] - bestTile.data.verts[vj + 1]) * tseg;
hitPos[2] = bestTile.data.verts[vj + 2]
+ (bestTile.data.verts[vi + 2] - bestTile.data.verts[vj + 2]) * tseg;
bestvj = new VectorPtr(bestTile.data.verts, vj);
bestvi = new VectorPtr(bestTile.data.verts, vi);
}
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
Link link = bestTile.links[i];
long neighbourRef = link.refs;
// Skip invalid neighbours and do not follow back to parent.
if (neighbourRef == 0 || neighbourRef == parentRef) {
continue;
}
// Expand to neighbour.
Tuple<MeshTile, Poly> neighbourTileAndPoly = m_nav.getTileAndPolyByRefUnsafe(neighbourRef);
MeshTile neighbourTile = neighbourTileAndPoly.Item1;
Poly neighbourPoly = neighbourTileAndPoly.Item2;
// Skip off-mesh connections.
if (neighbourPoly.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION) {
continue;
}
// Calc distance to the edge.
int va = bestPoly.verts[link.edge] * 3;
int vb = bestPoly.verts[(link.edge + 1) % bestPoly.vertCount] * 3;
Tuple<float, float> distseg = distancePtSegSqr2D(centerPos, bestTile.data.verts, va, vb);
float distSqr = distseg.Item1;
// If the circle is not touching the next polygon, skip it.
if (distSqr > radiusSqr) {
continue;
}
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
continue;
}
Node neighbourNode = m_nodePool.getNode(neighbourRef);
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0) {
continue;
}
// Cost
if (neighbourNode.flags == 0) {
Result<float[]> midPoint = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
neighbourTile);
if (midPoint.succeeded()) {
neighbourNode.pos = midPoint.result;
}
}
float total = bestNode.total + vDist(bestNode.pos, neighbourNode.pos);
// The node is already in open list and the new result is worse, skip.
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
continue;
}
neighbourNode.id = neighbourRef;
neighbourNode.flags = (neighbourNode.flags & ~Node.DT_NODE_CLOSED);
neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode);
neighbourNode.total = total;
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
m_openList.modify(neighbourNode);
} else {
neighbourNode.flags |= Node.DT_NODE_OPEN;
m_openList.push(neighbourNode);
}
}
}
// Calc hit normal.
float[] hitNormal = new float[3];
if (bestvi != null && bestvj != null) {
float[] tangent = vSub(bestvi, bestvj);
hitNormal[0] = tangent[2];
hitNormal[1] = 0;
hitNormal[2] = -tangent[0];
vNormalize(hitNormal);
}
return Results.success(new FindDistanceToWallResult((float) Math.Sqrt(radiusSqr), hitPos, hitNormal));
}
}

View File

@ -0,0 +1,41 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
/**
* Defines a link between polygons.
*
* @note This structure is rarely if ever used by the end user.
* @see MeshTile
*/
public class Link {
/** Neighbour reference. (The neighbor that is linked to.) */
public long refs;
/** Index of the next link. */
public int next;
/** 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

@ -0,0 +1,45 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
public class MeshData {
/** The tile header. */
public MeshHeader header;
/** The tile vertices. [Size: MeshHeader::vertCount] */
public float[] verts;
/** The tile polygons. [Size: MeshHeader::polyCount] */
public Poly[] polys;
/** The tile's detail sub-meshes. [Size: MeshHeader::detailMeshCount] */
public PolyDetail[] detailMeshes;
/** The detail mesh's unique vertices. [(x, y, z) * MeshHeader::detailVertCount] */
public float[] detailVerts;
/**
* The detail mesh's triangles. [(vertA, vertB, vertC) * MeshHeader::detailTriCount] See DetailTriEdgeFlags and
* NavMesh::getDetailTriEdgeFlags.
*/
public int[] detailTris;
/**
* The tile bounding volume nodes. [Size: MeshHeader::bvNodeCount] (Will be null if bounding volumes are disabled.)
*/
public BVNode[] bvTree;
/** The tile off-mesh connections. [Size: MeshHeader::offMeshConCount] */
public OffMeshConnection[] offMeshCons;
}

View File

@ -0,0 +1,78 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
/** Provides high level information related to a dtMeshTile object. */
public class MeshHeader {
/** A magic number used to detect compatibility of navigation tile data. */
public const int DT_NAVMESH_MAGIC = 'D' << 24 | 'N' << 16 | 'A' << 8 | 'V';
/** A version number used to detect compatibility of navigation tile data. */
public const int DT_NAVMESH_VERSION = 7;
public const int DT_NAVMESH_VERSION_RECAST4J_FIRST = 0x8807;
public const int DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK = 0x8808;
public const int DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE = 0x8809;
public const int DT_NAVMESH_VERSION_RECAST4J_LAST = 0x8809;
/** A magic number used to detect the compatibility of navigation tile states. */
public const int DT_NAVMESH_STATE_MAGIC = 'D' << 24 | 'N' << 16 | 'M' << 8 | 'S';
/** A version number used to detect compatibility of navigation tile states. */
public const int DT_NAVMESH_STATE_VERSION = 1;
/** Tile magic number. (Used to identify the data format.) */
public int magic;
/** Tile data format version number. */
public int version;
/** The x-position of the tile within the dtNavMesh tile grid. (x, y, layer) */
public int x;
/** The y-position of the tile within the dtNavMesh tile grid. (x, y, layer) */
public int y;
/** The layer of the tile within the dtNavMesh tile grid. (x, y, layer) */
public int layer;
/** The user defined id of the tile. */
public int userId;
/** The number of polygons in the tile. */
public int polyCount;
/** The number of vertices in the tile. */
public int vertCount;
/** The number of allocated links. */
public int maxLinkCount;
/** The number of sub-meshes in the detail mesh. */
public int detailMeshCount;
/** The number of unique vertices in the detail mesh. (In addition to the polygon vertices.) */
public int detailVertCount;
/** The number of triangles in the detail mesh. */
public int detailTriCount;
/** The number of bounding volume nodes. (Zero if bounding volumes are disabled.) */
public int bvNodeCount;
/** The number of off-mesh connections. */
public int offMeshConCount;
/** The index of the first polygon which is an off-mesh connection. */
public int offMeshBase;
/** The height of the agents using the tile. */
public float walkableHeight;
/** The radius of the agents using the tile. */
public float walkableRadius;
/** The maximum climb height of the agents using the tile. */
public float walkableClimb;
/** The minimum bounds of the tile's AABB. [(x, y, z)] */
public readonly float[] bmin = new float[3];
/** The maximum bounds of the tile's AABB. [(x, y, z)] */
public readonly float[] bmax = new float[3];
/** The bounding volume quantization factor. */
public float bvQuantFactor;
}

View File

@ -0,0 +1,45 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
/**
* Defines a navigation mesh tile.
*/
public class MeshTile {
public readonly int index;
/** Counter describing modifications to the tile. */
public int salt;
/** The tile data. */
public MeshData data;
public int[] polyLinks;
/** The tile links. */
public readonly List<Link> links = new();
/** Index to the next free link. */
public int linksFreeList = NavMesh.DT_NULL_LINK; // FIXME: Remove
/** Tile flags. (See: #dtTileFlags) */
public int flags;
public MeshTile(int index) {
this.index = index;
}
}

View File

@ -0,0 +1,45 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
public class MoveAlongSurfaceResult {
/** The result position of the mover. [(x, y, z)] */
private readonly float[] resultPos;
/** The reference ids of the polygons visited during the move. */
private readonly List<long> visited;
public MoveAlongSurfaceResult(float[] resultPos, List<long> visited) {
this.resultPos = resultPos;
this.visited = visited;
}
public float[] getResultPos() {
return resultPos;
}
public List<long> getVisited() {
return visited;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,601 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
using static DetourCommon;
public class NavMeshBuilder {
const int MESH_NULL_IDX = 0xffff;
public class BVItem {
public readonly int[] bmin = new int[3];
public readonly int[] bmax = new int[3];
public int i;
};
private class CompareItemX : IComparer<BVItem> {
public int Compare(BVItem a, BVItem b) {
return a.bmin[0].CompareTo(b.bmin[0]);
}
}
private class CompareItemY : IComparer<BVItem> {
public int Compare(BVItem a, BVItem b) {
return a.bmin[1].CompareTo(b.bmin[1]);
}
}
private class CompareItemZ : IComparer<BVItem> {
public int Compare(BVItem a, BVItem b) {
return a.bmin[2].CompareTo(b.bmin[2]);
}
}
private static int[][] calcExtends(BVItem[] items, int nitems, int imin, int imax) {
int[] bmin = new int[3];
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) {
BVItem it = items[i];
if (it.bmin[0] < bmin[0])
bmin[0] = it.bmin[0];
if (it.bmin[1] < bmin[1])
bmin[1] = it.bmin[1];
if (it.bmin[2] < bmin[2])
bmin[2] = it.bmin[2];
if (it.bmax[0] > bmax[0])
bmax[0] = it.bmax[0];
if (it.bmax[1] > bmax[1])
bmax[1] = it.bmax[1];
if (it.bmax[2] > bmax[2])
bmax[2] = it.bmax[2];
}
return new int[][] { bmin, bmax };
}
private static int longestAxis(int x, int y, int z) {
int axis = 0;
int maxVal = x;
if (y > maxVal) {
axis = 1;
maxVal = y;
}
if (z > maxVal) {
axis = 2;
maxVal = z;
}
return axis;
}
public static int subdivide(BVItem[] items, int nitems, int imin, int imax, int curNode, BVNode[] nodes) {
int inum = imax - imin;
int icur = curNode;
BVNode node = new BVNode();
nodes[curNode++] = node;
if (inum == 1) {
// Leaf
node.bmin[0] = items[imin].bmin[0];
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;
} else {
// Split
int[][] minmax = calcExtends(items, nitems, imin, imax);
node.bmin = minmax[0];
node.bmax = minmax[1];
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1],
node.bmax[2] - node.bmin[2]);
if (axis == 0) {
// Sort along x-axis
Array.Sort(items, imin, inum, new CompareItemX());
} else if (axis == 1) {
// Sort along y-axis
Array.Sort(items, imin, inum, new CompareItemY());
} else {
// Sort along z-axis
Array.Sort(items, imin, inum, new CompareItemZ());
}
int isplit = imin + inum / 2;
// Left
curNode = subdivide(items, nitems, imin, isplit, curNode, nodes);
// Right
curNode = subdivide(items, nitems, isplit, imax, curNode, nodes);
int iescape = curNode - icur;
// Negative index means escape.
node.i = -iescape;
}
return curNode;
}
private static int createBVTree(NavMeshDataCreateParams option, BVNode[] nodes) {
// Build tree
float quantFactor = 1 / option.cs;
BVItem[] items = new BVItem[option.polyCount];
for (int i = 0; i < option.polyCount; i++) {
BVItem it = new BVItem();
items[i] = it;
it.i = i;
// Calc polygon bounds. Use detail meshes if available.
if (option.detailMeshes != null) {
int vb = option.detailMeshes[i * 4 + 0];
int ndv = option.detailMeshes[i * 4 + 1];
float[] bmin = new float[3];
float[] bmax = new float[3];
int dv = vb * 3;
vCopy(bmin, option.detailVerts, dv);
vCopy(bmax, option.detailVerts, dv);
for (int j = 1; j < ndv; j++) {
vMin(bmin, option.detailVerts, dv + j * 3);
vMax(bmax, option.detailVerts, dv + j * 3);
}
// BV-tree uses cs for all dimensions
it.bmin[0] = clamp((int) ((bmin[0] - option.bmin[0]) * quantFactor), 0, int.MaxValue);
it.bmin[1] = clamp((int) ((bmin[1] - option.bmin[1]) * quantFactor), 0, int.MaxValue);
it.bmin[2] = clamp((int) ((bmin[2] - option.bmin[2]) * quantFactor), 0, int.MaxValue);
it.bmax[0] = clamp((int) ((bmax[0] - option.bmin[0]) * quantFactor), 0, int.MaxValue);
it.bmax[1] = clamp((int) ((bmax[1] - option.bmin[1]) * quantFactor), 0, int.MaxValue);
it.bmax[2] = clamp((int) ((bmax[2] - option.bmin[2]) * quantFactor), 0, int.MaxValue);
} else {
int p = i * option.nvp * 2;
it.bmin[0] = it.bmax[0] = option.verts[option.polys[p] * 3 + 0];
it.bmin[1] = it.bmax[1] = option.verts[option.polys[p] * 3 + 1];
it.bmin[2] = it.bmax[2] = option.verts[option.polys[p] * 3 + 2];
for (int j = 1; j < option.nvp; ++j) {
if (option.polys[p + j] == MESH_NULL_IDX)
break;
int x = option.verts[option.polys[p + j] * 3 + 0];
int y = option.verts[option.polys[p + j] * 3 + 1];
int z = option.verts[option.polys[p + j] * 3 + 2];
if (x < it.bmin[0])
it.bmin[0] = x;
if (y < it.bmin[1])
it.bmin[1] = y;
if (z < it.bmin[2])
it.bmin[2] = z;
if (x > it.bmax[0])
it.bmax[0] = x;
if (y > it.bmax[1])
it.bmax[1] = y;
if (z > it.bmax[2])
it.bmax[2] = z;
}
// Remap y
it.bmin[1] = (int) Math.Floor(it.bmin[1] * option.ch * quantFactor);
it.bmax[1] = (int) Math.Ceiling(it.bmax[1] * option.ch * quantFactor);
}
}
return subdivide(items, option.polyCount, 0, option.polyCount, 0, nodes);
}
const int XP = 1 << 0;
const int ZP = 1 << 1;
const int XM = 1 << 2;
const int ZM = 1 << 3;
public static int classifyOffMeshPoint(VectorPtr pt, float[] bmin, float[] bmax) {
int outcode = 0;
outcode |= (pt.get(0) >= bmax[0]) ? XP : 0;
outcode |= (pt.get(2) >= bmax[2]) ? ZP : 0;
outcode |= (pt.get(0) < bmin[0]) ? XM : 0;
outcode |= (pt.get(2) < bmin[2]) ? ZM : 0;
switch (outcode) {
case XP:
return 0;
case XP | ZP:
return 1;
case ZP:
return 2;
case XM | ZP:
return 3;
case XM:
return 4;
case XM | ZM:
return 5;
case ZM:
return 6;
case XP | ZM:
return 7;
}
return 0xff;
}
/**
* Builds navigation mesh tile data from the provided tile creation data.
*
* @param option
* Tile creation data.
*
* @return created tile data
*/
public static MeshData createNavMeshData(NavMeshDataCreateParams option) {
if (option.vertCount >= 0xffff)
return null;
if (option.vertCount == 0 || option.verts == null)
return null;
if (option.polyCount == 0 || option.polys == null)
return null;
int nvp = option.nvp;
// Classify off-mesh connection points. We store only the connections
// whose start point is inside the tile.
int[] offMeshConClass = null;
int storedOffMeshConCount = 0;
int offMeshConLinkCount = 0;
if (option.offMeshConCount > 0) {
offMeshConClass = new int[option.offMeshConCount * 2];
// Find tight heigh bounds, used for culling out off-mesh start
// locations.
float hmin = float.MaxValue;
float hmax = -float.MaxValue;
if (option.detailVerts != null && option.detailVertsCount != 0) {
for (int i = 0; i < option.detailVertsCount; ++i) {
float h = option.detailVerts[i * 3 + 1];
hmin = Math.Min(hmin, h);
hmax = Math.Max(hmax, h);
}
} else {
for (int i = 0; i < option.vertCount; ++i) {
int iv = i * 3;
float h = option.bmin[1] + option.verts[iv + 1] * option.ch;
hmin = Math.Min(hmin, h);
hmax = Math.Max(hmax, h);
}
}
hmin -= option.walkableClimb;
hmax += option.walkableClimb;
float[] bmin = new float[3];
float[] bmax = new float[3];
vCopy(bmin, option.bmin);
vCopy(bmax, option.bmax);
bmin[1] = hmin;
bmax[1] = hmax;
for (int i = 0; i < option.offMeshConCount; ++i) {
VectorPtr p0 = new VectorPtr(option.offMeshConVerts, (i * 2 + 0) * 3);
VectorPtr p1 = new VectorPtr(option.offMeshConVerts, (i * 2 + 1) * 3);
offMeshConClass[i * 2 + 0] = classifyOffMeshPoint(p0, bmin, bmax);
offMeshConClass[i * 2 + 1] = classifyOffMeshPoint(p1, bmin, bmax);
// Zero out off-mesh start positions which are not even
// potentially touching the mesh.
if (offMeshConClass[i * 2 + 0] == 0xff) {
if (p0.get(1) < bmin[1] || p0.get(1) > bmax[1])
offMeshConClass[i * 2 + 0] = 0;
}
// Count how many links should be allocated for off-mesh
// connections.
if (offMeshConClass[i * 2 + 0] == 0xff)
offMeshConLinkCount++;
if (offMeshConClass[i * 2 + 1] == 0xff)
offMeshConLinkCount++;
if (offMeshConClass[i * 2 + 0] == 0xff)
storedOffMeshConCount++;
}
}
// Off-mesh connectionss are stored as polygons, adjust values.
int totPolyCount = option.polyCount + storedOffMeshConCount;
int totVertCount = option.vertCount + storedOffMeshConCount * 2;
// Find portal edges which are at tile borders.
int edgeCount = 0;
int portalCount = 0;
for (int i = 0; i < option.polyCount; ++i) {
int p = i * 2 * nvp;
for (int j = 0; j < nvp; ++j) {
if (option.polys[p + j] == MESH_NULL_IDX)
break;
edgeCount++;
if ((option.polys[p + nvp + j] & 0x8000) != 0) {
int dir = option.polys[p + nvp + j] & 0xf;
if (dir != 0xf)
portalCount++;
}
}
}
int maxLinkCount = edgeCount + portalCount * 2 + offMeshConLinkCount * 2;
// Find unique detail vertices.
int uniqueDetailVertCount = 0;
int detailTriCount = 0;
if (option.detailMeshes != null) {
// Has detail mesh, count unique detail vertex count and use input
// detail tri count.
detailTriCount = option.detailTriCount;
for (int i = 0; i < option.polyCount; ++i) {
int p = i * nvp * 2;
int ndv = option.detailMeshes[i * 4 + 1];
int nv = 0;
for (int j = 0; j < nvp; ++j) {
if (option.polys[p + j] == MESH_NULL_IDX)
break;
nv++;
}
ndv -= nv;
uniqueDetailVertCount += ndv;
}
} else {
// No input detail mesh, build detail mesh from nav polys.
uniqueDetailVertCount = 0; // No extra detail verts.
detailTriCount = 0;
for (int i = 0; i < option.polyCount; ++i) {
int p = i * nvp * 2;
int nv = 0;
for (int j = 0; j < nvp; ++j) {
if (option.polys[p + j] == MESH_NULL_IDX)
break;
nv++;
}
detailTriCount += nv - 2;
}
}
int bvTreeSize = option.buildBvTree ? option.polyCount * 2 : 0;
MeshHeader header = new MeshHeader();
float[] navVerts = new float[3 * totVertCount];
Poly[] navPolys = new Poly[totPolyCount];
PolyDetail[] navDMeshes = new PolyDetail[option.polyCount];
float[] navDVerts = new float[3 * uniqueDetailVertCount];
int[] navDTris = new int[4 * detailTriCount];
BVNode[] navBvtree = new BVNode[bvTreeSize];
OffMeshConnection[] offMeshCons = new OffMeshConnection[storedOffMeshConCount];
// Store header
header.magic = MeshHeader.DT_NAVMESH_MAGIC;
header.version = MeshHeader.DT_NAVMESH_VERSION;
header.x = option.tileX;
header.y = option.tileZ;
header.layer = option.tileLayer;
header.userId = option.userId;
header.polyCount = totPolyCount;
header.vertCount = totVertCount;
header.maxLinkCount = maxLinkCount;
vCopy(header.bmin, option.bmin);
vCopy(header.bmax, option.bmax);
header.detailMeshCount = option.polyCount;
header.detailVertCount = uniqueDetailVertCount;
header.detailTriCount = detailTriCount;
header.bvQuantFactor = 1.0f / option.cs;
header.offMeshBase = option.polyCount;
header.walkableHeight = option.walkableHeight;
header.walkableRadius = option.walkableRadius;
header.walkableClimb = option.walkableClimb;
header.offMeshConCount = storedOffMeshConCount;
header.bvNodeCount = bvTreeSize;
int offMeshVertsBase = option.vertCount;
int offMeshPolyBase = option.polyCount;
// Store vertices
// Mesh vertices
for (int i = 0; i < option.vertCount; ++i) {
int iv = i * 3;
int v = i * 3;
navVerts[v] = option.bmin[0] + option.verts[iv] * option.cs;
navVerts[v + 1] = option.bmin[1] + option.verts[iv + 1] * option.ch;
navVerts[v + 2] = option.bmin[2] + option.verts[iv + 2] * option.cs;
}
// Off-mesh link vertices.
int n = 0;
for (int i = 0; i < option.offMeshConCount; ++i) {
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff) {
int linkv = i * 2 * 3;
int v = (offMeshVertsBase + n * 2) * 3;
Array.Copy(option.offMeshConVerts, linkv, navVerts, v, 6);
n++;
}
}
// Store polygons
// Mesh polys
int src = 0;
for (int i = 0; i < option.polyCount; ++i) {
Poly p = new Poly(i, nvp);
navPolys[i] = p;
p.vertCount = 0;
p.flags = option.polyFlags[i];
p.setArea(option.polyAreas[i]);
p.setType(Poly.DT_POLYTYPE_GROUND);
for (int j = 0; j < nvp; ++j) {
if (option.polys[src + j] == MESH_NULL_IDX)
break;
p.verts[j] = option.polys[src + j];
if ((option.polys[src + nvp + j] & 0x8000) != 0) {
// Border or portal edge.
int dir = option.polys[src + nvp + j] & 0xf;
if (dir == 0xf) // Border
p.neis[j] = 0;
else if (dir == 0) // Portal x-
p.neis[j] = NavMesh.DT_EXT_LINK | 4;
else if (dir == 1) // Portal z+
p.neis[j] = NavMesh.DT_EXT_LINK | 2;
else if (dir == 2) // Portal x+
p.neis[j] = NavMesh.DT_EXT_LINK | 0;
else if (dir == 3) // Portal z-
p.neis[j] = NavMesh.DT_EXT_LINK | 6;
} else {
// Normal connection
p.neis[j] = option.polys[src + nvp + j] + 1;
}
p.vertCount++;
}
src += nvp * 2;
}
// Off-mesh connection vertices.
n = 0;
for (int i = 0; i < option.offMeshConCount; ++i) {
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff) {
Poly p = new Poly(offMeshPolyBase + n, nvp);
navPolys[offMeshPolyBase + n] = p;
p.vertCount = 2;
p.verts[0] = offMeshVertsBase + n * 2;
p.verts[1] = offMeshVertsBase + n * 2 + 1;
p.flags = option.offMeshConFlags[i];
p.setArea(option.offMeshConAreas[i]);
p.setType(Poly.DT_POLYTYPE_OFFMESH_CONNECTION);
n++;
}
}
// Store detail meshes and vertices.
// The nav polygon vertices are stored as the first vertices on each
// mesh.
// We compress the mesh data by skipping them and using the navmesh
// coordinates.
if (option.detailMeshes != null) {
int vbase = 0;
for (int i = 0; i < option.polyCount; ++i) {
PolyDetail dtl = new PolyDetail();
navDMeshes[i] = dtl;
int vb = option.detailMeshes[i * 4 + 0];
int ndv = option.detailMeshes[i * 4 + 1];
int nv = navPolys[i].vertCount;
dtl.vertBase = vbase;
dtl.vertCount = (ndv - nv);
dtl.triBase = option.detailMeshes[i * 4 + 2];
dtl.triCount = option.detailMeshes[i * 4 + 3];
// Copy vertices except the first 'nv' verts which are equal to
// nav poly verts.
if (ndv - nv != 0) {
Array.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv));
vbase += ndv - nv;
}
}
// Store triangles.
Array.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount);
} else {
// Create dummy detail mesh by triangulating polys.
int tbase = 0;
for (int i = 0; i < option.polyCount; ++i) {
PolyDetail dtl = new PolyDetail();
navDMeshes[i] = dtl;
int nv = navPolys[i].vertCount;
dtl.vertBase = 0;
dtl.vertCount = 0;
dtl.triBase = tbase;
dtl.triCount = (nv - 2);
// Triangulate polygon (local indices).
for (int j = 2; j < nv; ++j) {
int t = tbase * 4;
navDTris[t + 0] = 0;
navDTris[t + 1] = (j - 1);
navDTris[t + 2] = j;
// Bit for each edge that belongs to poly boundary.
navDTris[t + 3] = (1 << 2);
if (j == 2)
navDTris[t + 3] |= (1 << 0);
if (j == nv - 1)
navDTris[t + 3] |= (1 << 4);
tbase++;
}
}
}
// Store and create BVtree.
// TODO: take detail mesh into account! use byte per bbox extent?
if (option.buildBvTree) {
// Do not set header.bvNodeCount set to make it work look exactly the same as in original Detour
header.bvNodeCount = createBVTree(option, navBvtree);
}
// Store Off-Mesh connections.
n = 0;
for (int i = 0; i < option.offMeshConCount; ++i) {
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff) {
OffMeshConnection con = new OffMeshConnection();
offMeshCons[n] = con;
con.poly = (offMeshPolyBase + n);
// Copy connection end-points.
int endPts = i * 2 * 3;
Array.Copy(option.offMeshConVerts, endPts, con.pos, 0, 6);
con.rad = option.offMeshConRad[i];
con.flags = option.offMeshConDir[i] != 0 ? NavMesh.DT_OFFMESH_CON_BIDIR : 0;
con.side = offMeshConClass[i * 2 + 1];
if (option.offMeshConUserID != null)
con.userId = option.offMeshConUserID[i];
n++;
}
}
MeshData nmd = new MeshData();
nmd.header = header;
nmd.verts = navVerts;
nmd.polys = navPolys;
nmd.detailMeshes = navDMeshes;
nmd.detailVerts = navDVerts;
nmd.detailTris = navDTris;
nmd.bvTree = navBvtree;
nmd.offMeshCons = offMeshCons;
return nmd;
}
}

View File

@ -0,0 +1,101 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
/// Represents the source data used to build an navigation mesh tile.
public class NavMeshDataCreateParams {
/// @name Polygon Mesh Attributes
/// Used to create the base navigation graph.
/// See #rcPolyMesh for details related to these attributes.
/// @{
public int[] verts; /// < The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx]
public int vertCount; /// < The number vertices in the polygon mesh. [Limit: >= 3]
public int[] polys; /// < The polygon data. [Size: #polyCount * 2 * #nvp]
public int[] polyFlags; /// < The user defined flags assigned to each polygon. [Size: #polyCount]
public int[] polyAreas; /// < The user defined area ids assigned to each polygon. [Size: #polyCount]
public int polyCount; /// < Number of polygons in the mesh. [Limit: >= 1]
public int nvp; /// < Number maximum number of vertices per polygon. [Limit: >= 3]
/// @}
/// @name Height Detail Attributes (Optional)
/// See #rcPolyMeshDetail for details related to these attributes.
/// @{
public int[] detailMeshes; /// < The height detail sub-mesh data. [Size: 4 * #polyCount]
public float[] detailVerts; /// < The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu]
public int detailVertsCount; /// < The number of vertices in the detail mesh.
public int[] detailTris; /// < The detail mesh triangles. [Size: 4 * #detailTriCount]
public int detailTriCount; /// < The number of triangles in the detail mesh.
/// @}
/// @name Off-Mesh Connections Attributes (Optional)
/// Used to define a custom point-to-point edge within the navigation graph, an
/// off-mesh connection is a user defined traversable connection made up to two vertices,
/// at least one of which resides within a navigation mesh polygon.
/// @{
/// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu]
public float[] offMeshConVerts;
/// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu]
public float[] offMeshConRad;
/// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount]
public int[] offMeshConFlags;
/// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount]
public int[] offMeshConAreas;
/// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount]
///
/// 0 = Travel only from endpoint A to endpoint B.<br/>
/// #DT_OFFMESH_CON_BIDIR = Bidirectional travel.
public int[] offMeshConDir;
/// The user defined ids of the off-mesh connection. [Size: #offMeshConCount]
public int[] offMeshConUserID;
/// The number of off-mesh connections. [Limit: >= 0]
public int offMeshConCount;
/// @}
/// @name Tile Attributes
/// @note The tile grid/layer data can be left at zero if the destination is a single tile mesh.
/// @{
public int userId; /// < The user defined id of the tile.
public int tileX; /// < The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.)
public int tileZ; /// < The tile's y-grid location within the multi-tile desitation mesh. (Along the z-axis.)
public int tileLayer; /// < The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.)
public float[] bmin; /// < The minimum bounds of the tile. [(x, y, z)] [Unit: wu]
public float[] bmax; /// < The maximum bounds of the tile. [(x, y, z)] [Unit: wu]
/// @}
/// @name General Configuration Attributes
/// @{
public float walkableHeight; /// < The agent height. [Unit: wu]
public float walkableRadius; /// < The agent radius. [Unit: wu]
public float walkableClimb; /// < The agent maximum traversable ledge. (Up/Down) [Unit: wu]
public float cs; /// < The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu]
public float ch; /// < The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu]
/// True if a bounding volume tree should be built for the tile.
/// @note The BVTree is not normally needed for layered navigation meshes.
public bool buildBvTree;
/// @}
}

View File

@ -0,0 +1,38 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
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.
*
* @see NavMesh
*/
public class NavMeshParams {
/** The world space origin of the navigation mesh's tile space. [(x, y, z)] */
public readonly float[] orig = new float[3];
/** The width of each tile. (Along the x-axis.) */
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

@ -0,0 +1,61 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
public class Node {
public const int DT_NODE_OPEN = 0x01;
public const int DT_NODE_CLOSED = 0x02;
/** parent of the node is not adjacent. Found using raycast. */
public const int DT_NODE_PARENT_DETACHED = 0x04;
public readonly int index;
/** Position of the node. */
public float[] pos = new float[3];
/** 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 Node(int index) {
this.index = index;
}
public override string ToString() {
return "Node [id=" + id + "]";
}
}

View File

@ -0,0 +1,98 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
public class NodePool
{
private readonly Dictionary<long, List<Node>> m_map = new();
private readonly List<Node> m_nodes = new();
public NodePool() {
}
public void clear() {
m_nodes.Clear();
m_map.Clear();
}
public List<Node> findNodes(long id) {
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes == null) {
nodes = new();
}
return nodes;
}
public Node findNode(long id) {
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes != null && 0 != nodes.Count) {
return nodes[0];
}
return null;
}
public Node getNode(long id, int state) {
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes != null) {
foreach (Node node in nodes) {
if (node.state == state) {
return node;
}
}
}
return create(id, state);
}
protected Node create(long id, int state) {
Node node = new Node(m_nodes.Count + 1);
node.id = id;
node.state = state;
m_nodes.Add(node);
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes == null) {
nodes = new();
m_map.Add(id, nodes);
}
nodes.Add(node);
return node;
}
public int getNodeIdx(Node node) {
return node != null ? node.index : 0;
}
public Node getNodeAtIdx(int idx) {
return idx != 0 ? m_nodes[idx - 1] : null;
}
public Node getNode(long refs) {
return getNode(refs, 0);
}
public Dictionary<long, List<Node>> getNodeMap() {
return m_map;
}
}

View File

@ -0,0 +1,62 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
using System.Collections.Generic;
public class NodeQueue {
private readonly List<Node> m_heap = new();
public int count()
{
return m_heap.Count;
}
public void clear() {
m_heap.Clear();
}
public Node top()
{
return m_heap[0];
}
public Node pop()
{
var node = top();
m_heap.Remove(node);
return node;
}
public void push(Node node) {
m_heap.Add(node);
m_heap.Sort((x, y) => x.total.CompareTo(y.total));
}
public void modify(Node node) {
m_heap.Remove(node);
push(node);
}
public bool isEmpty()
{
return 0 == m_heap.Count;
}
}

View File

@ -0,0 +1,43 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
/**
* 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.
*/
public class OffMeshConnection {
/** The endpoints of the connection. [(ax, ay, az, bx, by, bz)] */
public float[] pos = new float[6];
/** The radius of the endpoints. [Limit: >= 0] */
public float rad;
/** The polygon reference of the connection within the tile. */
public int poly;
/**
* Link flags.
*
* @note These are not the connection's user defined flags. Those are assigned via the connection's Poly definition.
* These are link flags used for internal purposes.
*/
public int flags;
/** End point side. */
public int side;
/** The id of the offmesh connection. (User assigned when the navigation mesh is built.) */
public int userId;
}

View File

@ -0,0 +1,70 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
/** Defines a polygon within a MeshTile object. */
public class Poly {
public readonly int index;
/** The polygon is a standard convex polygon that is part of the surface of the mesh. */
public const int DT_POLYTYPE_GROUND = 0;
/** The polygon is an off-mesh connection consisting of two vertices. */
public const int DT_POLYTYPE_OFFMESH_CONNECTION = 1;
/** The indices of the polygon's vertices. The actual vertices are located in MeshTile::verts. */
public readonly int[] verts;
/** Packed data representing neighbor polygons references and flags for each edge. */
public readonly int[] neis;
/** The user defined polygon flags. */
public int flags;
/** The number of vertices in the polygon. */
public int vertCount;
/**
* The bit packed area id and polygon type.
*
* @note Use the structure's set and get methods to access this value.
*/
public int areaAndtype;
public Poly(int index, int maxVertsPerPoly) {
this.index = index;
verts = new int[maxVertsPerPoly];
neis = new int[maxVertsPerPoly];
}
/** Sets the user defined area id. [Limit: &lt; {@link org.recast4j.detour.NavMesh#DT_MAX_AREAS}] */
public void setArea(int a) {
areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f);
}
/** Sets the polygon type. (See: #dtPolyTypes.) */
public void setType(int t) {
areaAndtype = (areaAndtype & 0x3f) | (t << 6);
}
/** Gets the user defined area id. */
public int getArea() {
return areaAndtype & 0x3f;
}
/** Gets the polygon type. (See: #dtPolyTypes) */
public int getType() {
return areaAndtype >> 6;
}
}

View File

@ -0,0 +1,31 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
/** Defines the location of detail sub-mesh data within a dtMeshTile. */
public class PolyDetail {
/** The offset of the vertices in the MeshTile::detailVerts array. */
public int vertBase;
/** The offset of the triangles in the MeshTile::detailTris array. */
public int triBase;
/** The number of vertices in the sub-mesh. */
public int vertCount;
/** The number of triangles in the sub-mesh. */
public int triCount;
}

View File

@ -0,0 +1,6 @@
namespace DotRecast.Detour;
public interface PolyQuery {
void process(MeshTile tile, Poly poly, long refs);
}

View File

@ -0,0 +1,94 @@
/*
recast4j copyright (c) 2021 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;
namespace DotRecast.Detour;
using static DetourCommon;
public interface PolygonByCircleConstraint {
float[] aply(float[] polyVerts, float[] circleCenter, float radius);
public static PolygonByCircleConstraint noop() {
return new NoOpPolygonByCircleConstraint();
}
public static PolygonByCircleConstraint strict() {
return new StrictPolygonByCircleConstraint();
}
public class NoOpPolygonByCircleConstraint : PolygonByCircleConstraint {
public float[] aply(float[] polyVerts, float[] circleCenter, float radius) {
return polyVerts;
}
}
/**
* Calculate the intersection between a polygon and a circle. A dodecagon is used as an approximation of the circle.
*/
public class StrictPolygonByCircleConstraint : PolygonByCircleConstraint {
private const int CIRCLE_SEGMENTS = 12;
private static float[] unitCircle;
public float[] aply(float[] verts, float[] center, float radius) {
float radiusSqr = radius * radius;
int outsideVertex = -1;
for (int pv = 0; pv < verts.Length; pv += 3) {
if (vDist2DSqr(center, verts, pv) > radiusSqr) {
outsideVertex = pv;
break;
}
}
if (outsideVertex == -1) {
// polygon inside circle
return verts;
}
float[] qCircle = circle(center, radius);
float[] intersection = ConvexConvexIntersection.intersect(verts, qCircle);
if (intersection == null && pointInPolygon(center, verts, verts.Length / 3)) {
// circle inside polygon
return qCircle;
}
return intersection;
}
private float[] circle(float[] center, float radius) {
if (unitCircle == null) {
unitCircle = new float[CIRCLE_SEGMENTS * 3];
for (int i = 0; i < CIRCLE_SEGMENTS; i++) {
double a = i * Math.PI * 2 / CIRCLE_SEGMENTS;
unitCircle[3 * i] = (float) Math.Cos(a);
unitCircle[3 * i + 1] = 0;
unitCircle[3 * i + 2] = (float) -Math.Sin(a);
}
}
float[] circle = new float[12 * 3];
for (int i = 0; i < CIRCLE_SEGMENTS * 3; i += 3) {
circle[i] = unitCircle[i] * radius + center[0];
circle[i + 1] = center[1];
circle[i + 2] = unitCircle[i + 2] * radius + center[2];
}
return circle;
}
}
}

View File

@ -0,0 +1,32 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
public class QueryData {
public Status status;
public Node lastBestNode;
public float lastBestNodeCost;
public long startRef, endRef;
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public QueryFilter filter;
public int options;
public float raycastLimitSqr;
public QueryHeuristic heuristic;
}

View File

@ -0,0 +1,28 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
public interface QueryFilter {
bool passFilter(long refs, MeshTile tile, Poly poly);
float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef, MeshTile curTile,
Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly);
}

View File

@ -0,0 +1,25 @@
/*
recast4j copyright (c) 2021 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.
*/
namespace DotRecast.Detour;
public interface QueryHeuristic {
float getCost(float[] neighbourPos, float[] endPos);
}

View File

@ -0,0 +1,39 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Detour;
/**
* Provides information about raycast hit. Filled by NavMeshQuery::raycast
*/
public class RaycastHit {
/** The hit parameter. (float.MaxValue if no wall hit.) */
public float t;
/** hitNormal The normal of the nearest wall hit. [(x, y, z)] */
public readonly float[] hitNormal = new float[3];
/** Visited polygons. */
public readonly List<long> path = new();
/** The cost of the path until hit. */
public float pathCost;
/** The index of the edge on the readonly polygon where the wall was hit. */
public int hitEdgeIndex;
}

View File

@ -0,0 +1,82 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
public static class Results
{
public static Result<T> success<T>(T result) {
return new Result<T>(result, Status.SUCCSESS, null);
}
public static Result<T> failure<T>() {
return new Result<T>(default, Status.FAILURE, null);
}
public static Result<T> invalidParam<T>() {
return new Result<T>(default, Status.FAILURE_INVALID_PARAM, null);
}
public static Result<T> failure<T>(string message) {
return new Result<T>(default, Status.FAILURE, message);
}
public static Result<T> invalidParam<T>(string message) {
return new Result<T>(default, Status.FAILURE_INVALID_PARAM, message);
}
public static Result<T> failure<T>(T result) {
return new Result<T>(result, Status.FAILURE, null);
}
public static Result<T> partial<T>(T result) {
return new Result<T>(default, Status.PARTIAL_RESULT, null);
}
public static Result<T> of<T>(Status status, string message) {
return new Result<T>(default, status, message);
}
public static Result<T> of<T>(Status status, T result) {
return new Result<T>(result, status, null);
}
}
public class Result<T> {
public readonly T result;
public readonly Status status;
public readonly string message;
internal Result(T result, Status status, string message) {
this.result = result;
this.status = status;
this.message = message;
}
public bool failed() {
return status.isFailed();
}
public bool succeeded() {
return status.isSuccess();
}
}

View File

@ -0,0 +1,54 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
public class Status {
public static Status FAILURE = new(0);
public static Status SUCCSESS = new(1);
public static Status IN_PROGRESS = new(2);
public static Status PARTIAL_RESULT = new(3);
public static Status FAILURE_INVALID_PARAM = new(4);
public int Value { get; }
private Status(int vlaue)
{
Value = vlaue;
}
}
public static class StatusEx
{
public static bool isFailed(this Status @this) {
return @this == Status.FAILURE || @this == Status.FAILURE_INVALID_PARAM;
}
public static bool isInProgress(this Status @this) {
return @this == Status.IN_PROGRESS;
}
public static bool isSuccess(this Status @this) {
return @this == Status.SUCCSESS || @this == Status.PARTIAL_RESULT;
}
public static bool isPartial(this Status @this) {
return @this == Status.PARTIAL_RESULT;
}
}

View File

@ -0,0 +1,47 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
using static DetourCommon;
//TODO: (PP) Add comments
public class StraightPathItem {
public float[] pos;
public int flags;
public long refs;
public StraightPathItem(float[] pos, int flags, long refs) {
this.pos = vCopy(pos);
this.flags = flags;
this.refs = refs;
}
public float[] getPos() {
return pos;
}
public int getFlags() {
return flags;
}
public long getRef() {
return refs;
}
}

View File

@ -0,0 +1,46 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Detour;
/**
* Wrapper for 3-element pieces (3D vectors) of a bigger float array.
*
*/
public class VectorPtr
{
private readonly float[] array;
private readonly int index;
public VectorPtr(float[] array) :
this(array, 0)
{
}
public VectorPtr(float[] array, int index)
{
this.array = array;
this.index = index;
}
public float get(int offset)
{
return array[index + offset];
}
}

View File

@ -0,0 +1,78 @@
using DotRecast.Detour;
using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Builder;
public abstract class AbstractNavMeshBuilder {
protected NavMeshDataCreateParams getNavMeshCreateParams(DemoInputGeomProvider m_geom, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
RecastBuilderResult rcResult) {
PolyMesh m_pmesh = rcResult.getMesh();
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
for (int i = 0; i < m_pmesh.npolys; ++i) {
m_pmesh.flags[i] = 1;
}
option.verts = m_pmesh.verts;
option.vertCount = m_pmesh.nverts;
option.polys = m_pmesh.polys;
option.polyAreas = m_pmesh.areas;
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
if (m_dmesh != null) {
option.detailMeshes = m_dmesh.meshes;
option.detailVerts = m_dmesh.verts;
option.detailVertsCount = m_dmesh.nverts;
option.detailTris = m_dmesh.tris;
option.detailTriCount = m_dmesh.ntris;
}
option.walkableHeight = m_agentHeight;
option.walkableRadius = m_agentRadius;
option.walkableClimb = m_agentMaxClimb;
option.bmin = m_pmesh.bmin;
option.bmax = m_pmesh.bmax;
option.cs = m_cellSize;
option.ch = m_cellHeight;
option.buildBvTree = true;
option.offMeshConCount = m_geom.getOffMeshConnections().Count;
option.offMeshConVerts = new float[option.offMeshConCount * 6];
option.offMeshConRad = new float[option.offMeshConCount];
option.offMeshConDir = new int[option.offMeshConCount];
option.offMeshConAreas = new int[option.offMeshConCount];
option.offMeshConFlags = new int[option.offMeshConCount];
option.offMeshConUserID = new int[option.offMeshConCount];
for (int i = 0; i < option.offMeshConCount; i++) {
DemoOffMeshConnection offMeshCon = m_geom.getOffMeshConnections()[i];
for (int j = 0; j < 6; j++) {
option.offMeshConVerts[6 * i + j] = offMeshCon.verts[j];
}
option.offMeshConRad[i] = offMeshCon.radius;
option.offMeshConDir[i] = offMeshCon.bidir ? 1 : 0;
option.offMeshConAreas[i] = offMeshCon.area;
option.offMeshConFlags[i] = offMeshCon.flags;
}
return option;
}
protected MeshData updateAreaAndFlags(MeshData meshData) {
// Update poly flags from areas.
for (int i = 0; i < meshData.polys.Length; ++i) {
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE) {
meshData.polys[i].setArea(SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND);
}
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD) {
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
} else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER) {
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
} else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR) {
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
}
}
return meshData;
}
}

View File

@ -0,0 +1,46 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast.Demo.Builder;
public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x0;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x1;
public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x2;
public const int SAMPLE_POLYAREA_TYPE_DOOR = 0x3;
public const int SAMPLE_POLYAREA_TYPE_GRASS = 0x4;
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x5;
public const int SAMPLE_POLYAREA_TYPE_JUMP_AUTO = 0x6;
public const int SAMPLE_POLYAREA_TYPE_WALKABLE = 0x3f;
public static AreaModification SAMPLE_AREAMOD_WALKABLE = new AreaModification(SAMPLE_POLYAREA_TYPE_WALKABLE);
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP);
public static readonly int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road)
public static readonly int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water).
public static readonly int SAMPLE_POLYFLAGS_DOOR = 0x04; // Ability to move through doors.
public static readonly int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public static readonly int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public static readonly int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
}

View File

@ -0,0 +1,70 @@
/*
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.Collections.Immutable;
using DotRecast.Detour;
using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Builder;
public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
public Tuple<IList<RecastBuilderResult>, NavMesh> build(DemoInputGeomProvider m_geom, PartitionType m_partitionType,
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
bool filterLedgeSpans, bool filterWalkableLowHeightSpans) {
RecastBuilderResult rcResult = buildRecastResult(m_geom, m_partitionType, m_cellSize, m_cellHeight, m_agentHeight,
m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans,
filterWalkableLowHeightSpans);
return Tuple.Create(ImmutableArray.Create(rcResult) as IList<RecastBuilderResult>,
buildNavMesh(
buildMeshData(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, rcResult),
m_vertsPerPoly));
}
private NavMesh buildNavMesh(MeshData meshData, int m_vertsPerPoly) {
return new NavMesh(meshData, m_vertsPerPoly, 0);
}
private RecastBuilderResult buildRecastResult(DemoInputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles, bool filterLedgeSpans,
bool filterWalkableLowHeightSpans) {
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles,
filterLedgeSpans, filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinSize,
m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError,
SampleAreaModifications.SAMPLE_AREAMOD_WALKABLE, true);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax());
RecastBuilder rcBuilder = new RecastBuilder();
return rcBuilder.build(m_geom, bcfg);
}
private MeshData buildMeshData(DemoInputGeomProvider m_geom, float m_cellSize, float m_cellHeight, float m_agentHeight,
float m_agentRadius, float m_agentMaxClimb, RecastBuilderResult rcResult) {
NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, rcResult);
return updateAreaAndFlags(NavMeshBuilder.createNavMeshData(option));
}
}

View File

@ -0,0 +1,127 @@
/*
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.Threading.Tasks;
using DotRecast.Core;
using DotRecast.Detour;
using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Builder;
public class TileNavMeshBuilder : AbstractNavMeshBuilder {
public TileNavMeshBuilder() {
}
public Tuple<IList<RecastBuilderResult>, NavMesh> build(DemoInputGeomProvider m_geom, PartitionType m_partitionType,
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize) {
List<RecastBuilderResult> rcResult = buildRecastResult(m_geom, m_partitionType, m_cellSize, m_cellHeight, m_agentHeight,
m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans,
filterWalkableLowHeightSpans, tileSize);
return Tuple.Create((IList<RecastBuilderResult>) rcResult,
buildNavMesh(m_geom,
buildMeshData(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, rcResult),
m_cellSize, tileSize, m_vertsPerPoly));
}
private List<RecastBuilderResult> buildRecastResult(DemoInputGeomProvider m_geom, PartitionType m_partitionType,
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize) {
RecastConfig cfg = new RecastConfig(true, tileSize, tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles, filterLedgeSpans,
filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb,
m_regionMinSize * m_regionMinSize * m_cellSize * m_cellSize,
m_regionMergeSize * m_regionMergeSize * m_cellSize * m_cellSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly,
true, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_WALKABLE);
RecastBuilder rcBuilder = new RecastBuilder();
return rcBuilder.buildTiles(m_geom, cfg, Task.Factory);
}
private NavMesh buildNavMesh(DemoInputGeomProvider geom, List<MeshData> meshData, float cellSize, int tileSize,
int vertsPerPoly) {
NavMeshParams navMeshParams = new NavMeshParams();
navMeshParams.orig[0] = geom.getMeshBoundsMin()[0];
navMeshParams.orig[1] = geom.getMeshBoundsMin()[1];
navMeshParams.orig[2] = geom.getMeshBoundsMin()[2];
navMeshParams.tileWidth = tileSize * cellSize;
navMeshParams.tileHeight = tileSize * cellSize;
// snprintf(text, 64, "Tiles %d x %d", tw, th);
navMeshParams.maxTiles = getMaxTiles(geom, cellSize, tileSize);
navMeshParams.maxPolys = getMaxPolysPerTile(geom, cellSize, tileSize);
NavMesh navMesh = new NavMesh(navMeshParams, vertsPerPoly);
meshData.forEach(md => navMesh.addTile(md, 0, 0));
return navMesh;
}
public int getMaxTiles(DemoInputGeomProvider geom, float cellSize, int tileSize) {
int tileBits = getTileBits(geom, cellSize, tileSize);
return 1 << tileBits;
}
public int getMaxPolysPerTile(DemoInputGeomProvider geom, float cellSize, int tileSize) {
int polyBits = 22 - getTileBits(geom, cellSize, tileSize);
return 1 << polyBits;
}
private int getTileBits(DemoInputGeomProvider geom, float cellSize, int tileSize) {
int[] wh = Recast.calcGridSize(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), cellSize);
int tw = (wh[0] + tileSize - 1) / tileSize;
int th = (wh[1] + tileSize - 1) / tileSize;
int tileBits = Math.Min(DetourCommon.ilog2(DetourCommon.nextPow2(tw * th)), 14);
return tileBits;
}
public int[] getTiles(DemoInputGeomProvider geom, float cellSize, int tileSize) {
int[] wh = Recast.calcGridSize(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), cellSize);
int tw = (wh[0] + tileSize - 1) / tileSize;
int th = (wh[1] + tileSize - 1) / tileSize;
return new int[] { tw, th };
}
private List<MeshData> buildMeshData(DemoInputGeomProvider m_geom, float m_cellSize, float m_cellHeight, float m_agentHeight,
float m_agentRadius, float m_agentMaxClimb, List<RecastBuilderResult> rcResult) {
// Add tiles to nav mesh
List<MeshData> meshData = new();
foreach (RecastBuilderResult result in rcResult) {
int x = result.tileX;
int z = result.tileZ;
NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight,
m_agentRadius, m_agentMaxClimb, result);
option.tileX = x;
option.tileZ = z;
MeshData md = NavMeshBuilder.createNavMeshData(option);
if (md != null) {
meshData.Add(updateAreaAndFlags(md));
}
}
return meshData;
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Silk.NET" Version="2.16.0" />
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.16.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj" />
<ProjectReference Include="..\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj" />
<ProjectReference Include="..\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj" />
<ProjectReference Include="..\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj" />
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,603 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 DotRecast.Core;
using DotRecast.Recast.Demo.Builder;
using Silk.NET.OpenGL;
namespace DotRecast.Recast.Demo.Draw;
public class DebugDraw {
private readonly GLCheckerTexture g_tex = new GLCheckerTexture();
private readonly OpenGLDraw openGlDraw = new ModernOpenGLDraw();
private readonly int[] boxIndices = { 7, 6, 5, 4, 0, 1, 2, 3, 1, 5, 6, 2, 3, 7, 4, 0, 2, 6, 7, 3, 0, 4, 5, 1, };
private readonly float[][] frustumPlanes =
{
new[] { 0f, 0f, 0f, 0f },
new[] { 0f, 0f, 0f, 0f },
new[] { 0f, 0f, 0f, 0f },
new[] { 0f, 0f, 0f, 0f },
new[] { 0f, 0f, 0f, 0f },
new[] { 0f, 0f, 0f, 0f },
};
public void begin(DebugDrawPrimitives prim) {
begin(prim, 1f);
}
public void debugDrawCylinderWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col,
float lineWidth) {
begin(DebugDrawPrimitives.LINES, lineWidth);
appendCylinderWire(minx, miny, minz, maxx, maxy, maxz, col);
end();
}
private const int CYLINDER_NUM_SEG = 16;
private readonly float[] cylinderDir = new float[CYLINDER_NUM_SEG * 2];
private bool cylinderInit = false;
private void initCylinder() {
if (!cylinderInit) {
cylinderInit = true;
for (int i = 0; i < CYLINDER_NUM_SEG; ++i) {
float a = (float) (i * Math.PI * 2 / CYLINDER_NUM_SEG);
cylinderDir[i * 2] = (float) Math.Cos(a);
cylinderDir[i * 2 + 1] = (float) Math.Sin(a);
}
}
}
void appendCylinderWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
initCylinder();
float cx = (maxx + minx) / 2;
float cz = (maxz + minz) / 2;
float rx = (maxx - minx) / 2;
float rz = (maxz - minz) / 2;
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++) {
vertex(cx + cylinderDir[j * 2 + 0] * rx, miny, cz + cylinderDir[j * 2 + 1] * rz, col);
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col);
vertex(cx + cylinderDir[j * 2 + 0] * rx, maxy, cz + cylinderDir[j * 2 + 1] * rz, col);
vertex(cx + cylinderDir[i * 2 + 0] * rx, maxy, cz + cylinderDir[i * 2 + 1] * rz, col);
}
for (int i = 0; i < CYLINDER_NUM_SEG; i += CYLINDER_NUM_SEG / 4) {
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col);
vertex(cx + cylinderDir[i * 2 + 0] * rx, maxy, cz + cylinderDir[i * 2 + 1] * rz, col);
}
}
public void debugDrawBoxWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col,
float lineWidth) {
begin(DebugDrawPrimitives.LINES, lineWidth);
appendBoxWire(minx, miny, minz, maxx, maxy, maxz, col);
end();
}
public void appendBoxWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
// Top
vertex(minx, miny, minz, col);
vertex(maxx, miny, minz, col);
vertex(maxx, miny, minz, col);
vertex(maxx, miny, maxz, col);
vertex(maxx, miny, maxz, col);
vertex(minx, miny, maxz, col);
vertex(minx, miny, maxz, col);
vertex(minx, miny, minz, col);
// bottom
vertex(minx, maxy, minz, col);
vertex(maxx, maxy, minz, col);
vertex(maxx, maxy, minz, col);
vertex(maxx, maxy, maxz, col);
vertex(maxx, maxy, maxz, col);
vertex(minx, maxy, maxz, col);
vertex(minx, maxy, maxz, col);
vertex(minx, maxy, minz, col);
// Sides
vertex(minx, miny, minz, col);
vertex(minx, maxy, minz, col);
vertex(maxx, miny, minz, col);
vertex(maxx, maxy, minz, col);
vertex(maxx, miny, maxz, col);
vertex(maxx, maxy, maxz, col);
vertex(minx, miny, maxz, col);
vertex(minx, maxy, maxz, col);
}
public void appendBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol) {
float[][] verts = {
new[] { minx, miny, minz },
new[] { maxx, miny, minz },
new[] { maxx, miny, maxz },
new[] { minx, miny, maxz },
new[] { minx, maxy, minz },
new[] { maxx, maxy, minz },
new[] { maxx, maxy, maxz },
new[] { minx, maxy, maxz } };
int idx = 0;
for (int i = 0; i < 6; ++i) {
vertex(verts[boxIndices[idx]], fcol[i]);
idx++;
vertex(verts[boxIndices[idx]], fcol[i]);
idx++;
vertex(verts[boxIndices[idx]], fcol[i]);
idx++;
vertex(verts[boxIndices[idx]], fcol[i]);
idx++;
}
}
public void debugDrawArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col,
float lineWidth) {
begin(DebugDrawPrimitives.LINES, lineWidth);
appendArc(x0, y0, z0, x1, y1, z1, h, as0, as1, col);
end();
}
public void begin(DebugDrawPrimitives prim, float size) {
getOpenGlDraw().begin(prim, size);
}
public void vertex(float[] pos, int color) {
getOpenGlDraw().vertex(pos, color);
}
public void vertex(float x, float y, float z, int color) {
getOpenGlDraw().vertex(x, y, z, color);
}
public void vertex(float[] pos, int color, float[] uv) {
getOpenGlDraw().vertex(pos, color, uv);
}
public void vertex(float x, float y, float z, int color, float u, float v) {
getOpenGlDraw().vertex(x, y, z, color, u, v);
}
public void end() {
getOpenGlDraw().end();
}
public void debugDrawCircle(float x, float y, float z, float r, int col, float lineWidth) {
begin(DebugDrawPrimitives.LINES, lineWidth);
appendCircle(x, y, z, r, col);
end();
}
private bool circleInit = false;
private const int CIRCLE_NUM_SEG = 40;
private readonly float[] circeDir = new float[CIRCLE_NUM_SEG * 2];
private float[] _viewMatrix = new float[16];
private readonly float[] _projectionMatrix = new float[16];
public void appendCircle(float x, float y, float z, float r, int col) {
if (!circleInit) {
circleInit = true;
for (int i = 0; i < CIRCLE_NUM_SEG; ++i) {
float a = (float) (i * Math.PI * 2 / CIRCLE_NUM_SEG);
circeDir[i * 2] = (float) Math.Cos(a);
circeDir[i * 2 + 1] = (float) Math.Sin(a);
}
}
for (int i = 0, j = CIRCLE_NUM_SEG - 1; i < CIRCLE_NUM_SEG; j = i++) {
vertex(x + circeDir[j * 2 + 0] * r, y, z + circeDir[j * 2 + 1] * r, col);
vertex(x + circeDir[i * 2 + 0] * r, y, z + circeDir[i * 2 + 1] * r, col);
}
}
private static readonly int NUM_ARC_PTS = 8;
private static readonly float PAD = 0.05f;
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) {
float dx = x1 - x0;
float dy = y1 - y0;
float dz = z1 - z0;
float len = (float) Math.Sqrt(dx * dx + dy * dy + dz * dz);
float[] prev = new float[3];
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD, prev);
for (int i = 1; i <= NUM_ARC_PTS; ++i) {
float u = PAD + i * ARC_PTS_SCALE;
float[] pt = new float[3];
evalArc(x0, y0, z0, dx, dy, dz, len * h, u, pt);
vertex(prev[0], prev[1], prev[2], col);
vertex(pt[0], pt[1], pt[2], col);
prev[0] = pt[0];
prev[1] = pt[1];
prev[2] = pt[2];
}
// End arrows
if (as0 > 0.001f) {
float[] p = new float[3], q = new float[3];
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD, p);
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD + 0.05f, q);
appendArrowHead(p, q, as0, col);
}
if (as1 > 0.001f) {
float[] p = new float[3], q = new float[3];
evalArc(x0, y0, z0, dx, dy, dz, len * h, 1 - PAD, p);
evalArc(x0, y0, z0, dx, dy, dz, len * h, 1 - (PAD + 0.05f), q);
appendArrowHead(p, q, as1, col);
}
}
private void evalArc(float x0, float y0, float z0, float dx, float dy, float dz, float h, float u, float[] res) {
res[0] = x0 + dx * u;
res[1] = y0 + dy * u + h * (1 - (u * 2 - 1) * (u * 2 - 1));
res[2] = z0 + dz * u;
}
public void debugDrawCross(float x, float y, float z, float size, int col, float lineWidth) {
begin(DebugDrawPrimitives.LINES, lineWidth);
appendCross(x, y, z, size, col);
end();
}
private void appendCross(float x, float y, float z, float s, int col) {
vertex(x - s, y, z, col);
vertex(x + s, y, z, col);
vertex(x, y - s, z, col);
vertex(x, y + s, z, col);
vertex(x, y, z - s, col);
vertex(x, y, z + s, col);
}
public void debugDrawBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol) {
begin(DebugDrawPrimitives.QUADS);
appendBox(minx, miny, minz, maxx, maxy, maxz, fcol);
end();
}
public void debugDrawCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
begin(DebugDrawPrimitives.TRIS);
appendCylinder(minx, miny, minz, maxx, maxy, maxz, col);
end();
}
public void appendCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
initCylinder();
int col2 = duMultCol(col, 160);
float cx = (maxx + minx) / 2;
float cz = (maxz + minz) / 2;
float rx = (maxx - minx) / 2;
float rz = (maxz - minz) / 2;
for (int i = 2; i < CYLINDER_NUM_SEG; ++i) {
int a = 0, b = i - 1, c = i;
vertex(cx + cylinderDir[a * 2 + 0] * rx, miny, cz + cylinderDir[a * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[b * 2 + 0] * rx, miny, cz + cylinderDir[b * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[c * 2 + 0] * rx, miny, cz + cylinderDir[c * 2 + 1] * rz, col2);
}
for (int i = 2; i < CYLINDER_NUM_SEG; ++i) {
int a = 0, b = i, c = i - 1;
vertex(cx + cylinderDir[a * 2 + 0] * rx, maxy, cz + cylinderDir[a * 2 + 1] * rz, col);
vertex(cx + cylinderDir[b * 2 + 0] * rx, maxy, cz + cylinderDir[b * 2 + 1] * rz, col);
vertex(cx + cylinderDir[c * 2 + 0] * rx, maxy, cz + cylinderDir[c * 2 + 1] * rz, col);
}
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++) {
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[j * 2 + 0] * rx, miny, cz + cylinderDir[j * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[j * 2 + 0] * rx, maxy, cz + cylinderDir[j * 2 + 1] * rz, col);
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[j * 2 + 0] * rx, maxy, cz + cylinderDir[j * 2 + 1] * rz, col);
vertex(cx + cylinderDir[i * 2 + 0] * rx, maxy, cz + cylinderDir[i * 2 + 1] * rz, col);
}
}
public void debugDrawArrow(float x0, float y0, float z0, float x1, float y1, float z1, float as0, float as1, int col,
float lineWidth) {
begin(DebugDrawPrimitives.LINES, lineWidth);
appendArrow(x0, y0, z0, x1, y1, z1, as0, as1, col);
end();
}
public void appendArrow(float x0, float y0, float z0, float x1, float y1, float z1, float as0, float as1, int col) {
vertex(x0, y0, z0, col);
vertex(x1, y1, z1, col);
// End arrows
float[] p = new float[] { x0, y0, z0 };
float[] q = new float[] { x1, y1, z1 };
if (as0 > 0.001f)
appendArrowHead(p, q, as0, col);
if (as1 > 0.001f)
appendArrowHead(q, p, as1, col);
}
void appendArrowHead(float[] p, float[] q, float s, int col) {
float eps = 0.001f;
if (vdistSqr(p, q) < eps * eps) {
return;
}
float[] ax = new float[3], ay = { 0, 1, 0 }, az = new float[3];
vsub(az, q, p);
vnormalize(az);
vcross(ax, ay, az);
vcross(ay, az, ax);
vnormalize(ay);
vertex(p, col);
// vertex(p[0]+az[0]*s+ay[0]*s/2, p[1]+az[1]*s+ay[1]*s/2, p[2]+az[2]*s+ay[2]*s/2, col);
vertex(p[0] + az[0] * s + ax[0] * s / 3, p[1] + az[1] * s + ax[1] * s / 3, p[2] + az[2] * s + ax[2] * s / 3, col);
vertex(p, col);
// vertex(p[0]+az[0]*s-ay[0]*s/2, p[1]+az[1]*s-ay[1]*s/2, p[2]+az[2]*s-ay[2]*s/2, col);
vertex(p[0] + az[0] * s - ax[0] * s / 3, p[1] + az[1] * s - ax[1] * s / 3, p[2] + az[2] * s - ax[2] * s / 3, col);
}
public void vcross(float[] dest, float[] v1, float[] v2) {
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
}
public void vnormalize(float[] v) {
float d = (float) (1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
v[0] *= d;
v[1] *= d;
v[2] *= d;
}
public void vsub(float[] dest, float[] v1, float[] v2) {
dest[0] = v1[0] - v2[0];
dest[1] = v1[1] - v2[1];
dest[2] = v1[2] - v2[2];
}
public float vdistSqr(float[] v1, float[] v2) {
float x = v1[0] - v2[0];
float y = v1[1] - v2[1];
float z = v1[2] - v2[2];
return x * x + y * y + z * z;
}
// public static int areaToCol(int area) {
// if (area == 0) {
// return duRGBA(0, 192, 255, 255);
// } else {
// return duIntToCol(area, 255);
// }
// }
public static int areaToCol(int area) {
switch (area) {
// Ground (0) : light blue
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE:
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND:
return duRGBA(0, 192, 255, 255);
// Water : blue
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER:
return duRGBA(0, 0, 255, 255);
// Road : brown
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD:
return duRGBA(50, 20, 12, 255);
// Door : cyan
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR:
return duRGBA(0, 255, 255, 255);
// Grass : green
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS:
return duRGBA(0, 255, 0, 255);
// Jump : yellow
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP:
return duRGBA(255, 255, 0, 255);
// Unexpected : red
default:
return duRGBA(255, 0, 0, 255);
}
}
public static int duRGBA(int r, int g, int b, int a) {
return (r) | (g << 8) | (b << 16) | (a << 24);
}
public static int duLerpCol(int ca, int cb, int u) {
int ra = ca & 0xff;
int ga = (ca >> 8) & 0xff;
int ba = (ca >> 16) & 0xff;
int aa = (ca >> 24) & 0xff;
int rb = cb & 0xff;
int gb = (cb >> 8) & 0xff;
int bb = (cb >> 16) & 0xff;
int ab = (cb >> 24) & 0xff;
int r = (ra * (255 - u) + rb * u) / 255;
int g = (ga * (255 - u) + gb * u) / 255;
int b = (ba * (255 - u) + bb * u) / 255;
int a = (aa * (255 - u) + ab * u) / 255;
return duRGBA(r, g, b, a);
}
public static int bit(int a, int b) {
return (a & (1 << b)) >>> b;
}
public static int duIntToCol(int i, int a) {
int r = bit(i, 1) + bit(i, 3) * 2 + 1;
int g = bit(i, 2) + bit(i, 4) * 2 + 1;
int b = bit(i, 0) + bit(i, 5) * 2 + 1;
return duRGBA(r * 63, g * 63, b * 63, a);
}
public static void duCalcBoxColors(int[] colors, int colTop, int colSide) {
colors[0] = duMultCol(colTop, 250);
colors[1] = duMultCol(colSide, 140);
colors[2] = duMultCol(colSide, 165);
colors[3] = duMultCol(colSide, 165);
colors[4] = duMultCol(colSide, 217);
colors[5] = duMultCol(colSide, 217);
}
public static int duMultCol(int col, int d) {
int r = col & 0xff;
int g = (col >> 8) & 0xff;
int b = (col >> 16) & 0xff;
int a = (col >> 24) & 0xff;
return duRGBA((r * d) >> 8, (g * d) >> 8, (b * d) >> 8, a);
}
public static int duTransCol(int c, int a) {
return (a << 24) | (c & 0x00ffffff);
}
public static int duDarkenCol(int col) {
return (int)(((col >> 1) & 0x007f7f7f) | (col & 0xff000000));
}
public void fog(float start, float end) {
getOpenGlDraw().fog(start, end);
}
public void fog(bool state) {
getOpenGlDraw().fog(state);
}
public void depthMask(bool state) {
getOpenGlDraw().depthMask(state);
}
public void texture(bool state) {
getOpenGlDraw().texture(g_tex, state);
}
public void init(GL gl, float fogDistance) {
getOpenGlDraw().init(gl);
}
public void clear() {
getOpenGlDraw().clear();
}
public float[] projectionMatrix(float fovy, float aspect, float near, float far) {
GLU.glhPerspectivef2(_projectionMatrix, fovy, aspect, near, far);
getOpenGlDraw().projectionMatrix(_projectionMatrix);
updateFrustum();
return _projectionMatrix;
}
public float[] viewMatrix(float[] cameraPos, float[] cameraEulers) {
float[] rx = GLU.build_4x4_rotation_matrix(cameraEulers[0], 1, 0, 0);
float[] ry = GLU.build_4x4_rotation_matrix(cameraEulers[1], 0, 1, 0);
float[] r = GLU.mul(rx, ry);
float[] t = new float[16];
t[0] = t[5] = t[10] = t[15] = 1;
t[12] = -cameraPos[0];
t[13] = -cameraPos[1];
t[14] = -cameraPos[2];
_viewMatrix = GLU.mul(r, t);
getOpenGlDraw().viewMatrix(_viewMatrix);
updateFrustum();
return _viewMatrix;
}
private OpenGLDraw getOpenGlDraw() {
return openGlDraw;
}
private void updateFrustum() {
float[] vpm = GLU.mul(_projectionMatrix, _viewMatrix);
// left
frustumPlanes[0] = normalizePlane(vpm[0 + 3] + vpm[0 + 0], vpm[4 + 3] + vpm[4 + 0], vpm[8 + 3] + vpm[8 + 0],
vpm[12 + 3] + vpm[12 + 0]);
// right
frustumPlanes[1] = normalizePlane(vpm[0 + 3] - vpm[0 + 0], vpm[4 + 3] - vpm[4 + 0], vpm[8 + 3] - vpm[8 + 0],
vpm[12 + 3] - vpm[12 + 0]);
// top
frustumPlanes[2] = normalizePlane(vpm[0 + 3] - vpm[0 + 1], vpm[4 + 3] - vpm[4 + 1], vpm[8 + 3] - vpm[8 + 1],
vpm[12 + 3] - vpm[12 + 1]);
// bottom
frustumPlanes[3] = normalizePlane(vpm[0 + 3] + vpm[0 + 1], vpm[4 + 3] + vpm[4 + 1], vpm[8 + 3] + vpm[8 + 1],
vpm[12 + 3] + vpm[12 + 1]);
// near
frustumPlanes[4] = normalizePlane(vpm[0 + 3] + vpm[0 + 2], vpm[4 + 3] + vpm[4 + 2], vpm[8 + 3] + vpm[8 + 2],
vpm[12 + 3] + vpm[12 + 2]);
// far
frustumPlanes[5] = normalizePlane(vpm[0 + 3] - vpm[0 + 2], vpm[4 + 3] - vpm[4 + 2], vpm[8 + 3] - vpm[8 + 2],
vpm[12 + 3] - vpm[12 + 2]);
}
private float[] normalizePlane(float px, float py, float pz, float pw) {
float length = (float) Math.Sqrt(px * px + py * py + pz * pz);
if (length != 0) {
length = 1f / length;
px *= length;
py *= length;
pz *= length;
pw *= length;
}
return new float[] { px, py, pz, pw };
}
public bool frustumTest(float[] bmin, float[] bmax) {
return frustumTest(new float[] { bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2] });
}
public bool frustumTest(float[] bounds) {
foreach (float[] plane in frustumPlanes) {
float p_x;
float p_y;
float p_z;
float n_x;
float n_y;
float n_z;
if (plane[0] >= 0) {
p_x = bounds[3];
n_x = bounds[0];
} else {
p_x = bounds[0];
n_x = bounds[3];
}
if (plane[1] >= 0) {
p_y = bounds[4];
n_y = bounds[1];
} else {
p_y = bounds[1];
n_y = bounds[4];
}
if (plane[2] >= 0) {
p_z = bounds[5];
n_z = bounds[2];
} else {
p_z = bounds[2];
n_z = bounds[5];
}
if (plane[0] * p_x + plane[1] * p_y + plane[2] * p_z + plane[3] < 0) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,27 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast.Demo.Draw;
public enum DebugDrawPrimitives {
POINTS,
LINES,
TRIS,
QUADS
}

View File

@ -0,0 +1,50 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast.Demo.Draw;
public class DrawMode {
public static readonly DrawMode DRAWMODE_MESH = new("Input Mesh");
public static readonly DrawMode DRAWMODE_NAVMESH = new("Navmesh");
public static readonly DrawMode DRAWMODE_NAVMESH_INVIS = new("Navmesh Invis");
public static readonly DrawMode DRAWMODE_NAVMESH_TRANS = new("Navmesh Trans");
public static readonly DrawMode DRAWMODE_NAVMESH_BVTREE = new("Navmesh BVTree");
public static readonly DrawMode DRAWMODE_NAVMESH_NODES = new("Navmesh Nodes");
public static readonly DrawMode DRAWMODE_NAVMESH_PORTALS = new("Navmesh Portals");
public static readonly DrawMode DRAWMODE_VOXELS = new("Voxels");
public static readonly DrawMode DRAWMODE_VOXELS_WALKABLE = new("Walkable Voxels");
public static readonly DrawMode DRAWMODE_COMPACT = new("Compact");
public static readonly DrawMode DRAWMODE_COMPACT_DISTANCE = new("Compact Distance");
public static readonly DrawMode DRAWMODE_COMPACT_REGIONS = new("Compact Regions");
public static readonly DrawMode DRAWMODE_REGION_CONNECTIONS = new("Region Connections");
public static readonly DrawMode DRAWMODE_RAW_CONTOURS = new("Raw Contours");
public static readonly DrawMode DRAWMODE_BOTH_CONTOURS = new("Both Contours");
public static readonly DrawMode DRAWMODE_CONTOURS = new("Contours");
public static readonly DrawMode DRAWMODE_POLYMESH = new("Poly Mesh");
public static readonly DrawMode DRAWMODE_POLYMESH_DETAIL = new("Poly Mesh Detils");
private readonly string text;
private DrawMode(string text) {
this.text = text;
}
public override string ToString() {
return text;
}
}

View File

@ -0,0 +1,62 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 Silk.NET.OpenGL;
namespace DotRecast.Recast.Demo.Draw;
public class GLCheckerTexture {
int m_texId;
public void release() {
// if (m_texId != 0) {
// glDeleteTextures(m_texId);
// }
}
public void bind() {
// if (m_texId == 0) {
// // Create checker pattern.
// int col0 = DebugDraw.duRGBA(215, 215, 215, 255);
// int col1 = DebugDraw.duRGBA(255, 255, 255, 255);
// int TSIZE = 64;
// int[] data = new int[TSIZE * TSIZE];
//
// m_texId = glGenTextures();
// glBindTexture(GL_TEXTURE_2D, m_texId);
//
// int level = 0;
// int size = TSIZE;
// while (size > 0) {
// for (int y = 0; y < size; ++y)
// for (int x = 0; x < size; ++x)
// data[x + y * size] = (x == 0 || y == 0) ? col0 : col1;
// glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
// size /= 2;
// level++;
// }
//
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// } else {
// glBindTexture(GL_TEXTURE_2D, m_texId);
// }
}
}

View File

@ -0,0 +1,432 @@
/*
recast4j copyright (c) 2015-2019 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 DotRecast.Core;
namespace DotRecast.Recast.Demo.Draw;
public class GLU {
public static float[] gluPerspective(float fovy, float aspect, float near, float far) {
float[] projectionMatrix = new float[16];
glhPerspectivef2(projectionMatrix, fovy, aspect, near, far);
//glLoadMatrixf(projectionMatrix);
return projectionMatrix;
}
public static void glhPerspectivef2(float[] matrix, float fovyInDegrees, float aspectRatio, float znear,
float zfar) {
float ymax, xmax;
ymax = (float) (znear * Math.Tan(fovyInDegrees * Math.PI / 360.0));
xmax = ymax * aspectRatio;
glhFrustumf2(matrix, -xmax, xmax, -ymax, ymax, znear, zfar);
}
private static void glhFrustumf2(float[] matrix, float left, float right, float bottom, float top, float znear,
float zfar) {
float temp, temp2, temp3, temp4;
temp = 2.0f * znear;
temp2 = right - left;
temp3 = top - bottom;
temp4 = zfar - znear;
matrix[0] = temp / temp2;
matrix[1] = 0.0f;
matrix[2] = 0.0f;
matrix[3] = 0.0f;
matrix[4] = 0.0f;
matrix[5] = temp / temp3;
matrix[6] = 0.0f;
matrix[7] = 0.0f;
matrix[8] = (right + left) / temp2;
matrix[9] = (top + bottom) / temp3;
matrix[10] = (-zfar - znear) / temp4;
matrix[11] = -1.0f;
matrix[12] = 0.0f;
matrix[13] = 0.0f;
matrix[14] = (-temp * zfar) / temp4;
matrix[15] = 0.0f;
}
public static int glhUnProjectf(float winx, float winy, float winz, float[] modelview, float[] projection,
int[] viewport, float[] objectCoordinate) {
// Transformation matrices
float[] m = new float[16], A = new float[16];
float[] @in = new float[4], @out = new float[4];
// Calculation for inverting a matrix, compute projection x modelview
// and store in A[16]
MultiplyMatrices4by4OpenGL_FLOAT(A, projection, modelview);
// Now compute the inverse of matrix A
if (glhInvertMatrixf2(A, m) == 0)
return 0;
// Transformation of normalized coordinates between -1 and 1
@in[0] = (winx - viewport[0]) / viewport[2] * 2.0f - 1.0f;
@in[1] = (winy - viewport[1]) / viewport[3] * 2.0f - 1.0f;
@in[2] = 2.0f * winz - 1.0f;
@in[3] = 1.0f;
// Objects coordinates
MultiplyMatrixByVector4by4OpenGL_FLOAT(@out, m, @in);
if (@out[3] == 0.0)
return 0;
@out[3] = 1.0f / @out[3];
objectCoordinate[0] = @out[0] * @out[3];
objectCoordinate[1] = @out[1] * @out[3];
objectCoordinate[2] = @out[2] * @out[3];
return 1;
}
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[4] = matrix1[0] * matrix2[4] + matrix1[4] * matrix2[5] + matrix1[8] * matrix2[6]
+ matrix1[12] * matrix2[7];
result[8] = matrix1[0] * matrix2[8] + matrix1[4] * matrix2[9] + matrix1[8] * matrix2[10]
+ matrix1[12] * matrix2[11];
result[12] = matrix1[0] * matrix2[12] + matrix1[4] * matrix2[13] + matrix1[8] * matrix2[14]
+ matrix1[12] * matrix2[15];
result[1] = matrix1[1] * matrix2[0] + matrix1[5] * matrix2[1] + matrix1[9] * matrix2[2]
+ matrix1[13] * matrix2[3];
result[5] = matrix1[1] * matrix2[4] + matrix1[5] * matrix2[5] + matrix1[9] * matrix2[6]
+ matrix1[13] * matrix2[7];
result[9] = matrix1[1] * matrix2[8] + matrix1[5] * matrix2[9] + matrix1[9] * matrix2[10]
+ matrix1[13] * matrix2[11];
result[13] = matrix1[1] * matrix2[12] + matrix1[5] * matrix2[13] + matrix1[9] * matrix2[14]
+ matrix1[13] * matrix2[15];
result[2] = matrix1[2] * matrix2[0] + matrix1[6] * matrix2[1] + matrix1[10] * matrix2[2]
+ matrix1[14] * matrix2[3];
result[6] = matrix1[2] * matrix2[4] + matrix1[6] * matrix2[5] + matrix1[10] * matrix2[6]
+ matrix1[14] * matrix2[7];
result[10] = matrix1[2] * matrix2[8] + matrix1[6] * matrix2[9] + matrix1[10] * matrix2[10]
+ matrix1[14] * matrix2[11];
result[14] = matrix1[2] * matrix2[12] + matrix1[6] * matrix2[13] + matrix1[10] * matrix2[14]
+ matrix1[14] * matrix2[15];
result[3] = matrix1[3] * matrix2[0] + matrix1[7] * matrix2[1] + matrix1[11] * matrix2[2]
+ matrix1[15] * matrix2[3];
result[7] = matrix1[3] * matrix2[4] + matrix1[7] * matrix2[5] + matrix1[11] * matrix2[6]
+ matrix1[15] * matrix2[7];
result[11] = matrix1[3] * matrix2[8] + matrix1[7] * matrix2[9] + matrix1[11] * matrix2[10]
+ matrix1[15] * matrix2[11];
result[15] = matrix1[3] * matrix2[12] + matrix1[7] * matrix2[13] + matrix1[11] * matrix2[14]
+ matrix1[15] * matrix2[15];
}
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[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2]
+ matrix[13] * pvector[3];
resultvector[2] = matrix[2] * pvector[0] + matrix[6] * pvector[1] + matrix[10] * pvector[2]
+ matrix[14] * pvector[3];
resultvector[3] = matrix[3] * pvector[0] + matrix[7] * pvector[1] + matrix[11] * pvector[2]
+ matrix[15] * pvector[3];
}
// This code comes directly from GLU except that it is for float
static int glhInvertMatrixf2(float[] m, float[] @out) {
float[][] wtmp = ArrayUtils.Of<float>(4, 8);
float m0, m1, m2, m3, s;
float[] r0, r1, r2, r3;
r0 = wtmp[0];
r1 = wtmp[1];
r2 = wtmp[2];
r3 = wtmp[3];
r0[0] = MAT(m, 0, 0);
r0[1] = MAT(m, 0, 1);
r0[2] = MAT(m, 0, 2);
r0[3] = MAT(m, 0, 3);
r0[4] = 1.0f;
r0[5] = r0[6] = r0[7] = 0.0f;
r1[0] = MAT(m, 1, 0);
r1[1] = MAT(m, 1, 1);
r1[2] = MAT(m, 1, 2);
r1[3] = MAT(m, 1, 3);
r1[5] = 1.0f;
r1[4] = r1[6] = r1[7] = 0.0f;
r2[0] = MAT(m, 2, 0);
r2[1] = MAT(m, 2, 1);
r2[2] = MAT(m, 2, 2);
r2[3] = MAT(m, 2, 3);
r2[6] = 1.0f;
r2[4] = r2[5] = r2[7] = 0.0f;
r3[0] = MAT(m, 3, 0);
r3[1] = MAT(m, 3, 1);
r3[2] = MAT(m, 3, 2);
r3[3] = MAT(m, 3, 3);
r3[7] = 1.0f;
r3[4] = r3[5] = r3[6] = 0.0f;
/* choose pivot - or die */
if (Math.Abs(r3[0]) > Math.Abs(r2[0])) {
float[] r = r2;
r2 = r3;
r3 = r;
}
if (Math.Abs(r2[0]) > Math.Abs(r1[0])) {
float[] r = r2;
r2 = r1;
r1 = r;
}
if (Math.Abs(r1[0]) > Math.Abs(r0[0])) {
float[] r = r1;
r1 = r0;
r0 = r;
}
if (0.0 == r0[0])
return 0;
/* eliminate first variable */
m1 = r1[0] / r0[0];
m2 = r2[0] / r0[0];
m3 = r3[0] / r0[0];
s = r0[1];
r1[1] -= m1 * s;
r2[1] -= m2 * s;
r3[1] -= m3 * s;
s = r0[2];
r1[2] -= m1 * s;
r2[2] -= m2 * s;
r3[2] -= m3 * s;
s = r0[3];
r1[3] -= m1 * s;
r2[3] -= m2 * s;
r3[3] -= m3 * s;
s = r0[4];
if (s != 0.0) {
r1[4] -= m1 * s;
r2[4] -= m2 * s;
r3[4] -= m3 * s;
}
s = r0[5];
if (s != 0.0) {
r1[5] -= m1 * s;
r2[5] -= m2 * s;
r3[5] -= m3 * s;
}
s = r0[6];
if (s != 0.0) {
r1[6] -= m1 * s;
r2[6] -= m2 * s;
r3[6] -= m3 * s;
}
s = r0[7];
if (s != 0.0) {
r1[7] -= m1 * s;
r2[7] -= m2 * s;
r3[7] -= m3 * s;
}
/* choose pivot - or die */
if (Math.Abs(r3[1]) > Math.Abs(r2[1])) {
float[] r = r2;
r2 = r3;
r3 = r;
}
if (Math.Abs(r2[1]) > Math.Abs(r1[1])) {
float[] r = r2;
r2 = r1;
r1 = r;
}
if (0.0 == r1[1])
return 0;
/* eliminate second variable */
m2 = r2[1] / r1[1];
m3 = r3[1] / r1[1];
r2[2] -= m2 * r1[2];
r3[2] -= m3 * r1[2];
r2[3] -= m2 * r1[3];
r3[3] -= m3 * r1[3];
s = r1[4];
if (0.0 != s) {
r2[4] -= m2 * s;
r3[4] -= m3 * s;
}
s = r1[5];
if (0.0 != s) {
r2[5] -= m2 * s;
r3[5] -= m3 * s;
}
s = r1[6];
if (0.0 != s) {
r2[6] -= m2 * s;
r3[6] -= m3 * s;
}
s = r1[7];
if (0.0 != s) {
r2[7] -= m2 * s;
r3[7] -= m3 * s;
}
/* choose pivot - or die */
if (Math.Abs(r3[2]) > Math.Abs(r2[2])) {
float[] r = r2;
r2 = r3;
r3 = r;
}
if (0.0 == r2[2])
return 0;
/* eliminate third variable */
m3 = r3[2] / r2[2];
r3[3] -= m3 * r2[3];
r3[4] -= m3 * r2[4];
r3[5] -= m3 * r2[5];
r3[6] -= m3 * r2[6];
r3[7] -= m3 * r2[7];
/* last check */
if (0.0 == r3[3])
return 0;
s = 1.0f / r3[3]; /* now back substitute row 3 */
r3[4] *= s;
r3[5] *= s;
r3[6] *= s;
r3[7] *= s;
m2 = r2[3]; /* now back substitute row 2 */
s = 1.0f / r2[2];
r2[4] = s * (r2[4] - r3[4] * m2);
r2[5] = s * (r2[5] - r3[5] * m2);
r2[6] = s * (r2[6] - r3[6] * m2);
r2[7] = s * (r2[7] - r3[7] * m2);
m1 = r1[3];
r1[4] -= r3[4] * m1;
r1[5] -= r3[5] * m1;
r1[6] -= r3[6] * m1;
r1[7] -= r3[7] * m1;
m0 = r0[3];
r0[4] -= r3[4] * m0;
r0[5] -= r3[5] * m0;
r0[6] -= r3[6] * m0;
r0[7] -= r3[7] * m0;
m1 = r1[2]; /* now back substitute row 1 */
s = 1.0f / r1[1];
r1[4] = s * (r1[4] - r2[4] * m1);
r1[5] = s * (r1[5] - r2[5] * m1);
r1[6] = s * (r1[6] - r2[6] * m1);
r1[7] = s * (r1[7] - r2[7] * m1);
m0 = r0[2];
r0[4] -= r2[4] * m0;
r0[5] -= r2[5] * m0;
r0[6] -= r2[6] * m0;
r0[7] -= r2[7] * m0;
m0 = r0[1]; /* now back substitute row 0 */
s = 1.0f / r0[0];
r0[4] = s * (r0[4] - r1[4] * m0);
r0[5] = s * (r0[5] - r1[5] * m0);
r0[6] = s * (r0[6] - r1[6] * m0);
r0[7] = s * (r0[7] - r1[7] * m0);
MAT(@out, 0, 0, r0[4]);
MAT(@out, 0, 1, r0[5]);
MAT(@out, 0, 2, r0[6]);
MAT(@out, 0, 3, r0[7]);
MAT(@out, 1, 0, r1[4]);
MAT(@out, 1, 1, r1[5]);
MAT(@out, 1, 2, r1[6]);
MAT(@out, 1, 3, r1[7]);
MAT(@out, 2, 0, r2[4]);
MAT(@out, 2, 1, r2[5]);
MAT(@out, 2, 2, r2[6]);
MAT(@out, 2, 3, r2[7]);
MAT(@out, 3, 0, r3[4]);
MAT(@out, 3, 1, r3[5]);
MAT(@out, 3, 2, r3[6]);
MAT(@out, 3, 3, r3[7]);
return 1;
}
static float MAT(float[] m, int r, int c) {
return m[(c) * 4 + (r)];
}
static void MAT(float[] m, int r, int c, float v) {
m[(c) * 4 + (r)] = v;
}
public static float[] build_4x4_rotation_matrix(float a, float x, float y, float z) {
float[] matrix = new float[16];
a = (float) (a * Math.PI / 180.0); // convert to radians
float s = (float) Math.Sin(a);
float c = (float) Math.Cos(a);
float t = 1.0f - c;
float tx = t * x;
float ty = t * y;
float tz = t * z;
float sz = s * z;
float sy = s * y;
float sx = s * x;
matrix[0] = tx * x + c;
matrix[1] = tx * y + sz;
matrix[2] = tx * z - sy;
matrix[3] = 0;
matrix[4] = tx * y - sz;
matrix[5] = ty * y + c;
matrix[6] = ty * z + sx;
matrix[7] = 0;
matrix[8] = tx * z + sy;
matrix[9] = ty * z - sx;
matrix[10] = tz * z + c;
matrix[11] = 0;
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 0;
matrix[15] = 1;
return matrix;
}
public static float[] mul(float[] left, float[] right) {
float m00 = left[0] * right[0] + left[4] * right[1] + left[8] * right[2] + left[12] * right[3];
float m01 = left[1] * right[0] + left[5] * right[1] + left[9] * right[2] + left[13] * right[3];
float m02 = left[2] * right[0] + left[6] * right[1] + left[10] * right[2] + left[14] * right[3];
float m03 = left[3] * right[0] + left[7] * right[1] + left[11] * right[2] + left[15] * right[3];
float m10 = left[0] * right[4] + left[4] * right[5] + left[8] * right[6] + left[12] * right[7];
float m11 = left[1] * right[4] + left[5] * right[5] + left[9] * right[6] + left[13] * right[7];
float m12 = left[2] * right[4] + left[6] * right[5] + left[10] * right[6] + left[14] * right[7];
float m13 = left[3] * right[4] + left[7] * right[5] + left[11] * right[6] + left[15] * right[7];
float m20 = left[0] * right[8] + left[4] * right[9] + left[8] * right[10] + left[12] * right[11];
float m21 = left[1] * right[8] + left[5] * right[9] + left[9] * right[10] + left[13] * right[11];
float m22 = left[2] * right[8] + left[6] * right[9] + left[10] * right[10] + left[14] * right[11];
float m23 = left[3] * right[8] + left[7] * right[9] + left[11] * right[10] + left[15] * right[11];
float m30 = left[0] * right[12] + left[4] * right[13] + left[8] * right[14] + left[12] * right[15];
float m31 = left[1] * right[12] + left[5] * right[13] + left[9] * right[14] + left[13] * right[15];
float m32 = left[2] * right[12] + left[6] * right[13] + left[10] * right[14] + left[14] * right[15];
float m33 = left[3] * right[12] + left[7] * right[13] + left[11] * right[14] + left[15] * right[15];
float[] dest = new float[16];
dest[0] = m00;
dest[1] = m01;
dest[2] = m02;
dest[3] = m03;
dest[4] = m10;
dest[5] = m11;
dest[6] = m12;
dest[7] = m13;
dest[8] = m20;
dest[9] = m21;
dest[10] = m22;
dest[11] = m23;
dest[12] = m30;
dest[13] = m31;
dest[14] = m32;
dest[15] = m33;
return dest;
}
}

View File

@ -0,0 +1,123 @@
using Silk.NET.OpenGL;
namespace DotRecast.Recast.Demo.Draw;
public class LegacyOpenGLDraw : OpenGLDraw
{
private GL _gl;
public void fog(bool state) {
// if (state) {
// _gl.Enable(GL_FOG);
// } else {
// _gl.Disable(GL_FOG);
// }
}
public void init(GL gl)
{
_gl = gl;
// // Fog.
// float fogDistance = 1000f;
// float fogColor[] = { 0.32f, 0.31f, 0.30f, 1.0f };
// glEnable(GL_FOG);
// glFogi(GL_FOG_MODE, GL_LINEAR);
// glFogf(GL_FOG_START, fogDistance * 0.1f);
// glFogf(GL_FOG_END, fogDistance * 1.25f);
// glFogfv(GL_FOG_COLOR, fogColor);
// glDepthFunc(GL_LEQUAL);
}
public void clear() {
// glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// glEnable(GL_BLEND);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// glDisable(GL_TEXTURE_2D);
// glEnable(GL_DEPTH_TEST);
// glEnable(GL_CULL_FACE);
}
public void projectionMatrix(float[] matrix) {
// glMatrixMode(GL_PROJECTION);
// glLoadMatrixf(matrix);
}
public void viewMatrix(float[] matrix) {
// glMatrixMode(GL_MODELVIEW);
// glLoadMatrixf(matrix);
}
public void begin(DebugDrawPrimitives prim, float size) {
// switch (prim) {
// case POINTS:
// glPointSize(size);
// glBegin(GL_POINTS);
// break;
// case LINES:
// glLineWidth(size);
// glBegin(GL_LINES);
// break;
// case TRIS:
// glBegin(GL_TRIANGLES);
// break;
// case QUADS:
// glBegin(GL_QUADS);
// break;
// }
}
public void vertex(float[] pos, int color) {
// glColor4ubv(color);
// glVertex3fv(pos);
}
public void vertex(float x, float y, float z, int color) {
// glColor4ubv(color);
// glVertex3f(x, y, z);
}
public void vertex(float[] pos, int color, float[] uv) {
// glColor4ubv(color);
// glTexCoord2fv(uv);
// glVertex3fv(pos);
}
public void vertex(float x, float y, float z, int color, float u, float v) {
// glColor4ubv(color);
// glTexCoord2f(u, v);
// glVertex3f(x, y, z);
}
private void glColor4ubv(int color) {
// glColor4ub((byte) (color & 0xFF), (byte) ((color >> 8) & 0xFF), (byte) ((color >> 16) & 0xFF),
// (byte) ((color >> 24) & 0xFF));
}
public void depthMask(bool state) {
// glDepthMask(state);
}
public void texture(GLCheckerTexture g_tex, bool state) {
// if (state) {
// glEnable(GL_TEXTURE_2D);
// g_tex.bind();
// } else {
// glDisable(GL_TEXTURE_2D);
// }
}
public void end() {
// glEnd();
// glLineWidth(1.0f);
// glPointSize(1.0f);
}
public void fog(float start, float end) {
// glFogf(GL_FOG_START, start);
// glFogf(GL_FOG_END, end);
}
}

View File

@ -0,0 +1,264 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.PlatformAbstractions;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Draw;
public class ModernOpenGLDraw : OpenGLDraw {
private GL _gl;
private uint program;
private int uniformTexture;
private int uniformProjectionMatrix;
private uint vbo;
private uint ebo;
private uint vao;
private DebugDrawPrimitives currentPrim;
private float fogStart;
private float fogEnd;
private bool fogEnabled;
private int uniformViewMatrix;
private readonly List<OpenGLVertex> vertices = new();
private GLCheckerTexture _texture;
private float[] _viewMatrix;
private float[] _projectionMatrix;
private int uniformUseTexture;
private int uniformFog;
private int uniformFogStart;
private int uniformFogEnd;
public void init(GL gl)
{
_gl = gl;
string NK_SHADER_VERSION = PlatformID.MacOSX == Environment.OSVersion.Platform ? "#version 150\n" : "#version 300 es\n";
string vertex_shader = NK_SHADER_VERSION + "uniform mat4 ProjMtx;\n"//
+ "uniform mat4 ViewMtx;\n"//
+ "in vec3 Position;\n"//
+ "in vec2 TexCoord;\n"//
+ "in vec4 Color;\n"//
+ "out vec2 Frag_UV;\n"//
+ "out vec4 Frag_Color;\n"//
+ "out float Frag_Depth;\n"//
+ "void main() {\n"//
+ " Frag_UV = TexCoord;\n"//
+ " Frag_Color = Color;\n"//
+ " vec4 VSPosition = ViewMtx * vec4(Position, 1);\n"//
+ " Frag_Depth = -VSPosition.z;\n"//
+ " gl_Position = ProjMtx * VSPosition;\n"//
+ "}\n";
string fragment_shader = NK_SHADER_VERSION + "precision mediump float;\n"//
+ "uniform sampler2D Texture;\n"//
+ "uniform float UseTexture;\n"//
+ "uniform float EnableFog;\n"//
+ "uniform float FogStart;\n"//
+ "uniform float FogEnd;\n"//
+ "const vec4 FogColor = vec4(0.3f, 0.3f, 0.32f, 1.0f);\n"//
+ "in vec2 Frag_UV;\n"//
+ "in vec4 Frag_Color;\n"//
+ "in float Frag_Depth;\n"//
+ "out vec4 Out_Color;\n"//
+ "void main(){\n"//
+ " 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"//
+ "}\n";
program = _gl.CreateProgram();
uint vert_shdr = _gl.CreateShader(GLEnum.VertexShader);
uint frag_shdr = _gl.CreateShader(GLEnum.FragmentShader);
_gl.ShaderSource(vert_shdr, vertex_shader);
_gl.ShaderSource(frag_shdr, fragment_shader);
_gl.CompileShader(vert_shdr);
_gl.CompileShader(frag_shdr);
gl.GetShader(vert_shdr, GLEnum.CompileStatus, out var status);
if (status != (int) GLEnum.True) {
throw new InvalidOperationException();
}
gl.GetShader(frag_shdr, GLEnum.CompileStatus, out status);
if (status != (int) GLEnum.True) {
throw new InvalidOperationException();
}
_gl.AttachShader(program, vert_shdr);
_gl.AttachShader(program, frag_shdr);
_gl.LinkProgram(program);
_gl.GetProgram(program, GLEnum.LinkStatus, out status);
if (status != (int) GLEnum.True) {
throw new InvalidOperationException();
}
uniformTexture = _gl.GetUniformLocation(program, "Texture");
uniformUseTexture = _gl.GetUniformLocation(program, "UseTexture");
uniformFog = _gl.GetUniformLocation(program, "EnableFog");
uniformFogStart = _gl.GetUniformLocation(program, "FogStart");
uniformFogEnd = _gl.GetUniformLocation(program, "FogEnd");
uniformProjectionMatrix = _gl.GetUniformLocation(program, "ProjMtx");
uniformViewMatrix = _gl.GetUniformLocation(program, "ViewMtx");
uint attrib_pos = (uint) _gl.GetAttribLocation(program, "Position");
uint attrib_uv = (uint) _gl.GetAttribLocation(program, "TexCoord");
uint attrib_col = (uint) _gl.GetAttribLocation(program, "Color");
// buffer setup
_gl.GenBuffers(1, out vbo);
_gl.GenBuffers(1, out ebo);
_gl.GenVertexArrays(1, out vao);
_gl.BindVertexArray(vao);
_gl.BindBuffer(GLEnum.ArrayBuffer, vbo);
_gl.BindBuffer(GLEnum.ElementArrayBuffer, ebo);
_gl.EnableVertexAttribArray(attrib_pos);
_gl.EnableVertexAttribArray(attrib_uv);
_gl.EnableVertexAttribArray(attrib_col);
// _gl.VertexAttribPointer(attrib_pos, 3, GLEnum.Float, false, 24, 0);
// _gl.VertexAttribPointer(attrib_uv, 2, GLEnum.Float, false, 24, 12);
// _gl.VertexAttribPointer(attrib_col, 4, GLEnum.UnsignedByte, true, 24, 20);
_gl.VertexAttribP3(attrib_pos, GLEnum.Float, false, 0);
_gl.VertexAttribP2(attrib_uv, GLEnum.Float, false, 12);
_gl.VertexAttribP4(attrib_col, GLEnum.UnsignedByte, true, 20);
_gl.BindTexture(GLEnum.Texture2D, 0);
_gl.BindBuffer(GLEnum.ArrayBuffer, 0);
_gl.BindBuffer(GLEnum.ElementArrayBuffer, 0);
_gl.BindVertexArray(0);
}
public void clear() {
_gl.ClearColor(0.3f, 0.3f, 0.32f, 1.0f);
_gl.Clear((uint)GLEnum.ColorBufferBit | (uint)GLEnum.DepthBufferBit);
_gl.Enable(GLEnum.Blend);
_gl.BlendFunc(GLEnum.SrcAlpha, GLEnum.OneMinusSrcAlpha);
_gl.Disable(GLEnum.Texture2D);
_gl.Enable(GLEnum.DepthTest);
_gl.Enable(GLEnum.CullFace);
}
public void begin(DebugDrawPrimitives prim, float size) {
currentPrim = prim;
vertices.Clear();
_gl.LineWidth(size);
_gl.PointSize(size);
}
public void end() {
// if (vertices.isEmpty()) {
// return;
// }
// glUseProgram(program);
// glUniform1i(uniformTexture, 0);
// glUniformMatrix4fv(uniformViewMatrix, false, viewMatrix);
// glUniformMatrix4fv(uniformProjectionMatrix, false, projectionMatrix);
// glUniform1f(uniformFogStart, fogStart);
// glUniform1f(uniformFogEnd, fogEnd);
// glUniform1f(uniformFog, fogEnabled ? 1.0f : 0.0f);
// glBindVertexArray(vao);
// glBindBuffer(GL_ARRAY_BUFFER, vbo);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
// // glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_BUFFER, GL_STREAM_DRAW);
// // glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_BUFFER, GL_STREAM_DRAW);
//
// int vboSize = vertices.size() * 24;
// int eboSize = currentPrim == DebugDrawPrimitives.QUADS ? vertices.size() * 6 : vertices.size() * 4;
//
// glBufferData(GL_ARRAY_BUFFER, vboSize, GL_STREAM_DRAW);
// glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboSize, GL_STREAM_DRAW);
// // load draw vertices & elements directly into vertex + element buffer
// ByteBuffer verts = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY, vboSize, null);
// ByteBuffer elems = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY, eboSize, null);
// vertices.forEach(v => v.store(verts));
// if (currentPrim == DebugDrawPrimitives.QUADS) {
// for (int i = 0; i < vertices.size(); i += 4) {
// elems.putInt(i);
// elems.putInt(i + 1);
// elems.putInt(i + 2);
// elems.putInt(i);
// elems.putInt(i + 2);
// elems.putInt(i + 3);
// }
//
// } else {
// for (int i = 0; i < vertices.size(); i++) {
// elems.putInt(i);
// }
// }
//
// glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
// glUnmapBuffer(GL_ARRAY_BUFFER);
// if (texture != null) {
// texture.bind();
// glUniform1f(uniformUseTexture, 1.0f);
// } else {
// glUniform1f(uniformUseTexture, 0.0f);
// }
//
// switch (currentPrim) {
// case POINTS:
// glDrawElements(GL_POINTS, vertices.size(), GL_UNSIGNED_INT, 0);
// break;
// case LINES:
// glDrawElements(GL_LINES, vertices.size(), GL_UNSIGNED_INT, 0);
// break;
// case TRIS:
// glDrawElements(GL_TRIANGLES, vertices.size(), GL_UNSIGNED_INT, 0);
// break;
// case QUADS:
// glDrawElements(GL_TRIANGLES, vertices.size() * 6 / 4, GL_UNSIGNED_INT, 0);
// break;
// default:
// break;
// }
//
// glUseProgram(0);
// glBindBuffer(GL_ARRAY_BUFFER, 0);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// glBindVertexArray(0);
// vertices.clear();
// glLineWidth(1.0f);
// glPointSize(1.0f);
}
public void vertex(float x, float y, float z, int color) {
vertices.Add(new OpenGLVertex(x, y, z, color));
}
public void vertex(float[] pos, int color) {
vertices.Add(new OpenGLVertex(pos, color));
}
public void vertex(float[] pos, int color, float[] uv) {
vertices.Add(new OpenGLVertex(pos, uv, color));
}
public void vertex(float x, float y, float z, int color, float u, float v) {
vertices.Add(new OpenGLVertex(x, y, z, u, v, color));
}
public void depthMask(bool state) {
_gl.DepthMask(state);
}
public void texture(GLCheckerTexture g_tex, bool state) {
_texture = state ? g_tex : null;
if (_texture != null) {
_texture.bind();
}
}
public void projectionMatrix(float[] projectionMatrix) {
this._projectionMatrix = projectionMatrix;
}
public void viewMatrix(float[] viewMatrix) {
this._viewMatrix = viewMatrix;
}
public void fog(float start, float end) {
fogStart = start;
fogEnd = end;
}
public void fog(bool state) {
fogEnabled = state;
}
}

View File

@ -0,0 +1,254 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.Linq;
using DotRecast.Detour;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Geom;
using DotRecast.Recast.Demo.Settings;
namespace DotRecast.Recast.Demo.Draw;
public class NavMeshRenderer {
private readonly RecastDebugDraw debugDraw;
private readonly int navMeshDrawFlags = RecastDebugDraw.DRAWNAVMESH_OFFMESHCONS
| RecastDebugDraw.DRAWNAVMESH_CLOSEDLIST;
public NavMeshRenderer(RecastDebugDraw debugDraw) {
this.debugDraw = debugDraw;
}
public RecastDebugDraw getDebugDraw() {
return debugDraw;
}
public void render(Sample sample) {
if (sample == null) {
return;
}
NavMeshQuery navQuery = sample.getNavMeshQuery();
DemoInputGeomProvider geom = sample.getInputGeom();
IList<RecastBuilderResult> rcBuilderResults = sample.getRecastResults();
NavMesh navMesh = sample.getNavMesh();
SettingsUI settingsUI = sample.getSettingsUI();
debugDraw.fog(true);
debugDraw.depthMask(true);
DrawMode drawMode = settingsUI.getDrawMode();
float texScale = 1.0f / (settingsUI.getCellSize() * 10.0f);
float m_agentMaxSlope = settingsUI.getAgentMaxSlope();
if (drawMode != DrawMode.DRAWMODE_NAVMESH_TRANS) {
// Draw mesh
if (geom != null) {
debugDraw.debugDrawTriMeshSlope(geom.vertices, geom.faces, geom.normals, m_agentMaxSlope, texScale);
drawOffMeshConnections(geom, false);
}
}
debugDraw.fog(false);
debugDraw.depthMask(false);
if (geom != null) {
drawGeomBounds(geom);
}
if (navMesh != null && navQuery != null
&& (drawMode == DrawMode.DRAWMODE_NAVMESH || drawMode == DrawMode.DRAWMODE_NAVMESH_TRANS
|| drawMode == DrawMode.DRAWMODE_NAVMESH_BVTREE || drawMode == DrawMode.DRAWMODE_NAVMESH_NODES
|| drawMode == DrawMode.DRAWMODE_NAVMESH_INVIS
|| drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS)) {
if (drawMode != DrawMode.DRAWMODE_NAVMESH_INVIS) {
debugDraw.debugDrawNavMeshWithClosedList(navMesh, navQuery, navMeshDrawFlags);
}
if (drawMode == DrawMode.DRAWMODE_NAVMESH_BVTREE) {
debugDraw.debugDrawNavMeshBVTree(navMesh);
}
if (drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS) {
debugDraw.debugDrawNavMeshPortals(navMesh);
}
if (drawMode == DrawMode.DRAWMODE_NAVMESH_NODES) {
debugDraw.debugDrawNavMeshNodes(navQuery);
debugDraw.debugDrawNavMeshPolysWithFlags(navMesh, SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED,
DebugDraw.duRGBA(0, 0, 0, 128));
}
}
debugDraw.depthMask(true);
foreach (RecastBuilderResult rcBuilderResult in rcBuilderResults) {
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT) {
debugDraw.debugDrawCompactHeightfieldSolid(rcBuilderResult.getCompactHeightfield());
}
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_DISTANCE) {
debugDraw.debugDrawCompactHeightfieldDistance(rcBuilderResult.getCompactHeightfield());
}
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_REGIONS) {
debugDraw.debugDrawCompactHeightfieldRegions(rcBuilderResult.getCompactHeightfield());
}
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS) {
debugDraw.fog(true);
debugDraw.debugDrawHeightfieldSolid(rcBuilderResult.getSolidHeightfield());
debugDraw.fog(false);
}
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS_WALKABLE) {
debugDraw.fog(true);
debugDraw.debugDrawHeightfieldWalkable(rcBuilderResult.getSolidHeightfield());
debugDraw.fog(false);
}
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_RAW_CONTOURS) {
debugDraw.depthMask(false);
debugDraw.debugDrawRawContours(rcBuilderResult.getContourSet(), 1f);
debugDraw.depthMask(true);
}
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_BOTH_CONTOURS) {
debugDraw.depthMask(false);
debugDraw.debugDrawRawContours(rcBuilderResult.getContourSet(), 0.5f);
debugDraw.debugDrawContours(rcBuilderResult.getContourSet());
debugDraw.depthMask(true);
}
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_CONTOURS) {
debugDraw.depthMask(false);
debugDraw.debugDrawContours(rcBuilderResult.getContourSet());
debugDraw.depthMask(true);
}
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_REGION_CONNECTIONS) {
debugDraw.debugDrawCompactHeightfieldRegions(rcBuilderResult.getCompactHeightfield());
debugDraw.depthMask(false);
if (rcBuilderResult.getContourSet() != null) {
debugDraw.debugDrawRegionConnections(rcBuilderResult.getContourSet());
}
debugDraw.depthMask(true);
}
if (rcBuilderResult.getMesh() != null && drawMode == DrawMode.DRAWMODE_POLYMESH) {
debugDraw.depthMask(false);
debugDraw.debugDrawPolyMesh(rcBuilderResult.getMesh());
debugDraw.depthMask(true);
}
if (rcBuilderResult.getMeshDetail() != null && drawMode == DrawMode.DRAWMODE_POLYMESH_DETAIL) {
debugDraw.depthMask(false);
debugDraw.debugDrawPolyMeshDetail(rcBuilderResult.getMeshDetail());
debugDraw.depthMask(true);
}
}
if (geom != null) {
drawConvexVolumes(geom);
}
}
private void drawGeomBounds(DemoInputGeomProvider geom) {
// Draw bounds
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
debugDraw.debugDrawBoxWire(bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2],
DebugDraw.duRGBA(255, 255, 255, 128), 1.0f);
debugDraw.begin(DebugDrawPrimitives.POINTS, 5.0f);
debugDraw.vertex(bmin[0], bmin[1], bmin[2], DebugDraw.duRGBA(255, 255, 255, 128));
debugDraw.end();
}
public void drawOffMeshConnections(DemoInputGeomProvider geom, bool hilight) {
int conColor = DebugDraw.duRGBA(192, 0, 128, 192);
int baseColor = DebugDraw.duRGBA(0, 0, 0, 64);
debugDraw.depthMask(false);
debugDraw.begin(DebugDrawPrimitives.LINES, 2.0f);
foreach (DemoOffMeshConnection con in geom.getOffMeshConnections()) {
float[] v = con.verts;
debugDraw.vertex(v[0], v[1], v[2], baseColor);
debugDraw.vertex(v[0], v[1] + 0.2f, v[2], baseColor);
debugDraw.vertex(v[3], v[4], v[5], baseColor);
debugDraw.vertex(v[3], v[4] + 0.2f, v[5], baseColor);
debugDraw.appendCircle(v[0], v[1] + 0.1f, v[2], con.radius, baseColor);
debugDraw.appendCircle(v[3], v[4] + 0.1f, v[5], con.radius, baseColor);
if (hilight) {
debugDraw.appendArc(v[0], v[1], v[2], v[3], v[4], v[5], 0.25f, con.bidir ? 0.6f : 0.0f, 0.6f, conColor);
}
}
debugDraw.end();
debugDraw.depthMask(true);
}
void drawConvexVolumes(DemoInputGeomProvider geom) {
debugDraw.depthMask(false);
debugDraw.begin(DebugDrawPrimitives.TRIS);
foreach (ConvexVolume vol in geom.convexVolumes()) {
int col = DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 32);
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3) {
float[] va = new float[] { vol.verts[k], vol.verts[k + 1], vol.verts[k + 2] };
float[] vb = new float[] { vol.verts[j], vol.verts[j + 1], vol.verts[j + 2] };
debugDraw.vertex(vol.verts[0], vol.hmax, vol.verts[2], col);
debugDraw.vertex(vb[0], vol.hmax, vb[2], col);
debugDraw.vertex(va[0], vol.hmax, va[2], col);
debugDraw.vertex(va[0], vol.hmin, va[2], DebugDraw.duDarkenCol(col));
debugDraw.vertex(va[0], vol.hmax, va[2], col);
debugDraw.vertex(vb[0], vol.hmax, vb[2], col);
debugDraw.vertex(va[0], vol.hmin, va[2], DebugDraw.duDarkenCol(col));
debugDraw.vertex(vb[0], vol.hmax, vb[2], col);
debugDraw.vertex(vb[0], vol.hmin, vb[2], DebugDraw.duDarkenCol(col));
}
}
debugDraw.end();
debugDraw.begin(DebugDrawPrimitives.LINES, 2.0f);
foreach (ConvexVolume vol in geom.convexVolumes()) {
int col = DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 220);
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3) {
float[] va = new float[] { vol.verts[k], vol.verts[k + 1], vol.verts[k + 2] };
float[] vb = new float[] { vol.verts[j], vol.verts[j + 1], vol.verts[j + 2] };
debugDraw.vertex(va[0], vol.hmin, va[2], DebugDraw.duDarkenCol(col));
debugDraw.vertex(vb[0], vol.hmin, vb[2], DebugDraw.duDarkenCol(col));
debugDraw.vertex(va[0], vol.hmax, va[2], col);
debugDraw.vertex(vb[0], vol.hmax, vb[2], col);
debugDraw.vertex(va[0], vol.hmin, va[2], DebugDraw.duDarkenCol(col));
debugDraw.vertex(va[0], vol.hmax, va[2], col);
}
}
debugDraw.end();
debugDraw.begin(DebugDrawPrimitives.POINTS, 3.0f);
foreach (ConvexVolume vol in geom.convexVolumes()) {
int col = DebugDraw
.duDarkenCol(DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 220));
for (int j = 0; j < vol.verts.Length; j += 3) {
debugDraw.vertex(vol.verts[j + 0], vol.verts[j + 1] + 0.1f, vol.verts[j + 2], col);
debugDraw.vertex(vol.verts[j + 0], vol.hmin, vol.verts[j + 2], col);
debugDraw.vertex(vol.verts[j + 0], vol.hmax, vol.verts[j + 2], col);
}
}
debugDraw.end();
debugDraw.depthMask(true);
}
}

View File

@ -0,0 +1,35 @@
using Silk.NET.OpenGL;
namespace DotRecast.Recast.Demo.Draw;
public interface OpenGLDraw {
void init(GL gl);
void clear();
void begin(DebugDrawPrimitives prim, float size);
void end();
void vertex(float x, float y, float z, int color);
void vertex(float[] pos, int color);
void vertex(float[] pos, int color, float[] uv);
void vertex(float x, float y, float z, int color, float u, float v);
void fog(bool state);
void depthMask(bool state);
void texture(GLCheckerTexture g_tex, bool state);
void projectionMatrix(float[] projectionMatrix);
void viewMatrix(float[] viewMatrix);
void fog(float start, float end);
}

View File

@ -0,0 +1,43 @@
using DotRecast.Core;
namespace DotRecast.Recast.Demo.Draw;
public class OpenGLVertex {
private readonly float x;
private readonly float y;
private readonly float z;
private readonly int color;
private readonly float u;
private readonly float v;
public OpenGLVertex(float[] pos, float[] uv, int color) :
this(pos[0], pos[1], pos[2], uv[0], uv[1], color) {
}
public OpenGLVertex(float[] pos, int color) :
this(pos[0], pos[1], pos[2], 0f, 0f, color) {
}
public OpenGLVertex(float x, float y, float z, int color) :
this(x, y, z, 0f, 0f, color) {
}
public OpenGLVertex(float x, float y, float z, float u, float v, int color) {
this.x = x;
this.y = y;
this.z = z;
this.u = u;
this.v = v;
this.color = color;
}
public void store(ByteBuffer buffer) {
buffer.putFloat(x);
buffer.putFloat(y);
buffer.putFloat(z);
buffer.putFloat(u);
buffer.putFloat(v);
buffer.putInt(color);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,263 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using DotRecast.Recast.Geom;
namespace DotRecast.Recast.Demo.Geom;
public class ChunkyTriMesh {
private class BoundsItem {
public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2];
public int i;
}
private class CompareItemX : IComparer<BoundsItem> {
public int Compare(BoundsItem a, BoundsItem b) {
return a.bmin[0].CompareTo(b.bmin[0]);
}
}
private class CompareItemY : IComparer<BoundsItem> {
public int Compare(BoundsItem a, BoundsItem b) {
return a.bmin[1].CompareTo(b.bmin[1]);
}
}
List<ChunkyTriMeshNode> nodes;
int ntris;
int maxTrisPerChunk;
private void calcExtends(BoundsItem[] items, int imin, int imax, float[] bmin, float[] bmax) {
bmin[0] = items[imin].bmin[0];
bmin[1] = items[imin].bmin[1];
bmax[0] = items[imin].bmax[0];
bmax[1] = items[imin].bmax[1];
for (int i = imin + 1; i < imax; ++i) {
BoundsItem it = items[i];
if (it.bmin[0] < bmin[0]) {
bmin[0] = it.bmin[0];
}
if (it.bmin[1] < bmin[1]) {
bmin[1] = it.bmin[1];
}
if (it.bmax[0] > bmax[0]) {
bmax[0] = it.bmax[0];
}
if (it.bmax[1] > bmax[1]) {
bmax[1] = it.bmax[1];
}
}
}
private int longestAxis(float x, float y) {
return y > x ? 1 : 0;
}
private void subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<ChunkyTriMeshNode> nodes,
int[] inTris) {
int inum = imax - imin;
ChunkyTriMeshNode node = new ChunkyTriMeshNode();
nodes.Add(node);
if (inum <= trisPerChunk) {
// Leaf
calcExtends(items, imin, imax, node.bmin, 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, node.bmin, node.bmax);
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1]);
if (axis == 0) {
Array.Sort(items, imin, imax - imax, new CompareItemX());
// Sort along x-axis
} else if (axis == 1) {
Array.Sort(items, imin, imax - imin, new CompareItemY());
// 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 ChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk) {
int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;
nodes = new(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[0] = it.bmax[0] = verts[tris[t] * 3 + 0];
it.bmin[1] = it.bmax[1] = verts[tris[t] * 3 + 2];
for (int j = 1; j < 3; ++j) {
int v = tris[t + j] * 3;
if (verts[v] < it.bmin[0]) {
it.bmin[0] = verts[v];
}
if (verts[v + 2] < it.bmin[1]) {
it.bmin[1] = verts[v + 2];
}
if (verts[v] > it.bmax[0]) {
it.bmax[0] = verts[v];
}
if (verts[v + 2] > it.bmax[1]) {
it.bmax[1] = verts[v + 2];
}
}
}
subdivide(items, 0, ntris, trisPerChunk, nodes, tris);
// Calc max tris per node.
maxTrisPerChunk = 0;
foreach (ChunkyTriMeshNode node in nodes) {
bool isLeaf = node.i >= 0;
if (!isLeaf) {
continue;
}
if (node.tris.Length / 3 > maxTrisPerChunk) {
maxTrisPerChunk = node.tris.Length / 3;
}
}
}
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax) {
// Traverse tree
List<ChunkyTriMeshNode> ids = new();
int i = 0;
while (i < nodes.Count) {
ChunkyTriMeshNode 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;
}
private bool checkOverlapRect(float[] amin, float[] amax, float[] bmin, float[] bmax) {
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
return overlap;
}
public List<ChunkyTriMeshNode> getChunksOverlappingSegment(float[] p, float[] q) {
// Traverse tree
List<ChunkyTriMeshNode> ids = new();
int i = 0;
while (i < nodes.Count) {
ChunkyTriMeshNode 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(float[] p, float[] q, float[] bmin, float[] bmax) {
float EPSILON = 1e-6f;
float tmin = 0;
float tmax = 1;
float[] d = new float[2];
d[0] = q[0] - p[0];
d[1] = q[1] - p[1];
for (int i = 0; i < 2; i++) {
if (Math.Abs(d[i]) < EPSILON) {
// Ray is parallel to slab. No hit if origin not within slab
if (p[i] < bmin[i] || p[i] > bmax[i])
return false;
} else {
// Compute intersection t value of ray with near and far plane of slab
float ood = 1.0f / d[i];
float t1 = (bmin[i] - p[i]) * ood;
float t2 = (bmax[i] - p[i]) * ood;
if (t1 > t2) {
float tmp = t1;
t1 = t2;
t2 = tmp;
}
if (t1 > tmin)
tmin = t1;
if (t2 < tmax)
tmax = t2;
if (tmin > tmax)
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,185 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using DotRecast.Recast.Geom;
namespace DotRecast.Recast.Demo.Geom;
public class DemoInputGeomProvider : InputGeomProvider {
public readonly float[] vertices;
public readonly int[] faces;
public readonly float[] normals;
private readonly float[] bmin;
private readonly float[] bmax;
private readonly List<ConvexVolume> _convexVolumes = new();
private readonly List<DemoOffMeshConnection> offMeshConnections = new();
private readonly ChunkyTriMesh chunkyTriMesh;
public DemoInputGeomProvider(List<float> vertexPositions, List<int> meshFaces) :
this(mapVertices(vertexPositions), mapFaces(meshFaces)) {
}
private static int[] mapFaces(List<int> meshFaces) {
int[] faces = new int[meshFaces.Count];
for (int i = 0; i < faces.Length; i++) {
faces[i] = meshFaces[i];
}
return faces;
}
private static float[] mapVertices(List<float> vertexPositions) {
float[] vertices = new float[vertexPositions.Count];
for (int i = 0; i < vertices.Length; i++) {
vertices[i] = vertexPositions[i];
}
return vertices;
}
public DemoInputGeomProvider(float[] vertices, int[] faces) {
this.vertices = vertices;
this.faces = faces;
normals = new float[faces.Length];
calculateNormals();
bmin = new float[3];
bmax = new float[3];
RecastVectors.copy(bmin, vertices, 0);
RecastVectors.copy(bmax, vertices, 0);
for (int i = 1; i < vertices.Length / 3; i++) {
RecastVectors.min(bmin, vertices, i * 3);
RecastVectors.max(bmax, vertices, i * 3);
}
chunkyTriMesh = new ChunkyTriMesh(vertices, faces, faces.Length / 3, 256);
}
public float[] getMeshBoundsMin() {
return bmin;
}
public float[] getMeshBoundsMax() {
return bmax;
}
public void calculateNormals() {
for (int i = 0; i < faces.Length; i += 3) {
int v0 = faces[i] * 3;
int v1 = faces[i + 1] * 3;
int v2 = faces[i + 2] * 3;
float[] e0 = new float[3], e1 = new float[3];
for (int j = 0; j < 3; ++j) {
e0[j] = vertices[v1 + j] - vertices[v0 + j];
e1[j] = vertices[v2 + j] - vertices[v0 + j];
}
normals[i] = e0[1] * e1[2] - e0[2] * e1[1];
normals[i + 1] = e0[2] * e1[0] - e0[0] * e1[2];
normals[i + 2] = e0[0] * e1[1] - e0[1] * e1[0];
float d = (float) Math.Sqrt(normals[i] * normals[i] + normals[i + 1] * normals[i + 1] + normals[i + 2] * normals[i + 2]);
if (d > 0) {
d = 1.0f / d;
normals[i] *= d;
normals[i + 1] *= d;
normals[i + 2] *= d;
}
}
}
public IList<ConvexVolume> convexVolumes() {
return _convexVolumes;
}
public IEnumerable<TriMesh> meshes() {
return ImmutableArray.Create(new TriMesh(vertices, faces));
}
public List<DemoOffMeshConnection> getOffMeshConnections() {
return offMeshConnections;
}
public void addOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags) {
offMeshConnections.Add(new DemoOffMeshConnection(start, end, radius, bidir, area, flags));
}
public void removeOffMeshConnections(Predicate<DemoOffMeshConnection> filter) {
//offMeshConnections.retainAll(offMeshConnections.stream().filter(c -> !filter.test(c)).collect(toList()));
offMeshConnections.RemoveAll(filter); // TODO : 확인 필요
}
public float? raycastMesh(float[] src, float[] dst) {
// Prune hit ray.
float[] btminmax = Intersections.intersectSegmentAABB(src, dst, bmin, bmax);
if (null == btminmax) {
return null;
}
float btmin = btminmax[0];
float btmax = btminmax[1];
float[] p = new float[2], q = new float[2];
p[0] = src[0] + (dst[0] - src[0]) * btmin;
p[1] = src[2] + (dst[2] - src[2]) * btmin;
q[0] = src[0] + (dst[0] - src[0]) * btmax;
q[1] = src[2] + (dst[2] - src[2]) * btmax;
List<ChunkyTriMeshNode> chunks = chunkyTriMesh.getChunksOverlappingSegment(p, q);
if (0 == chunks.Count) {
return null;
}
float tmin = 1.0f;
bool hit = false;
foreach (ChunkyTriMeshNode chunk in chunks) {
int[] tris = chunk.tris;
for (int j = 0; j < chunk.tris.Length; j += 3) {
float[] v1 = new float[] { vertices[tris[j] * 3], vertices[tris[j] * 3 + 1],
vertices[tris[j] * 3 + 2] };
float[] v2 = new float[] { vertices[tris[j + 1] * 3], vertices[tris[j + 1] * 3 + 1],
vertices[tris[j + 1] * 3 + 2] };
float[] v3 = new float[] { vertices[tris[j + 2] * 3], vertices[tris[j + 2] * 3 + 1],
vertices[tris[j + 2] * 3 + 2] };
float? t = Intersections.intersectSegmentTriangle(src, dst, v1, v2, v3);
if (null != t) {
if (t.Value < tmin) {
tmin = t.Value;
}
hit = true;
}
}
}
return hit ? tmin : null;
}
public void addConvexVolume(float[] verts, float minh, float maxh, AreaModification areaMod) {
ConvexVolume volume = new ConvexVolume();
volume.verts = verts;
volume.hmin = minh;
volume.hmax = maxh;
volume.areaMod = areaMod;
_convexVolumes.Add(volume);
}
public void clearConvexVolumes() {
_convexVolumes.Clear();
}
}

View File

@ -0,0 +1,43 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast.Demo.Geom;
public class DemoOffMeshConnection {
public readonly float[] verts;
public readonly float radius;
public readonly bool bidir;
public readonly int area;
public readonly int flags;
public DemoOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags) {
verts = new float[6];
verts[0] = start[0];
verts[1] = start[1];
verts[2] = start[2];
verts[3] = end[0];
verts[4] = end[1];
verts[5] = end[2];
this.radius = radius;
this.bidir = bidir;
this.area = area;
this.flags = flags;
}
}

View File

@ -0,0 +1,113 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Core;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Recast.Demo.Geom;
public class Intersections {
public static float? intersectSegmentTriangle(float[] sp, float[] sq, float[] a, float[] b, float[] c) {
float v, w;
float[] ab = vSub(b, a);
float[] ac = vSub(c, a);
float[] qp = vSub(sp, sq);
// Compute triangle normal. Can be precalculated or cached if
// intersecting multiple segments against the same triangle
float[] norm = DemoMath.vCross(ab, ac);
// Compute denominator d. If d <= 0, segment is parallel to or points
// away from triangle, so exit early
float d = DemoMath.vDot(qp, norm);
if (d <= 0.0f) {
return null;
}
// Compute intersection t value of pq with plane of triangle. A ray
// intersects iff 0 <= t. Segment intersects iff 0 <= t <= 1. Delay
// dividing by d until intersection has been found to pierce triangle
float[] ap = vSub(sp, a);
float t = DemoMath.vDot(ap, norm);
if (t < 0.0f) {
return null;
}
if (t > d) {
return null; // For segment; exclude this code line for a ray test
}
// Compute barycentric coordinate components and test if within bounds
float[] e = DemoMath.vCross(qp, ap);
v = DemoMath.vDot(ac, e);
if (v < 0.0f || v > d) {
return null;
}
w = -DemoMath.vDot(ab, e);
if (w < 0.0f || v + w > d) {
return null;
}
// Segment/ray intersects triangle. Perform delayed division
t /= d;
return t;
}
public static float[] intersectSegmentAABB(float[] sp, float[] sq, float[] amin, float[] amax) {
float EPS = 1e-6f;
float[] d = new float[3];
d[0] = sq[0] - sp[0];
d[1] = sq[1] - sp[1];
d[2] = sq[2] - sp[2];
float tmin = 0.0f;
float tmax = 1.0f;
for (int i = 0; i < 3; i++) {
if (Math.Abs(d[i]) < EPS) {
if (sp[i] < amin[i] || sp[i] > amax[i]) {
return null;
}
} else {
float ood = 1.0f / d[i];
float t1 = (amin[i] - sp[i]) * ood;
float t2 = (amax[i] - sp[i]) * ood;
if (t1 > t2) {
float tmp = t1;
t1 = t2;
t2 = tmp;
}
if (t1 > tmin) {
tmin = t1;
}
if (t2 < tmax) {
tmax = t2;
}
if (tmin > tmax) {
return null;
}
}
}
return new float[] { tmin, tmax };
}
}

View File

@ -0,0 +1,79 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Core;
using DotRecast.Detour;
namespace DotRecast.Recast.Demo.Geom;
/**
* Simple helper to find an intersection between a ray and a nav mesh
*/
public class NavMeshRaycast {
public static float? raycast(NavMesh mesh, float[] src, float[]dst) {
for (int t = 0; t < mesh.getMaxTiles(); ++t) {
MeshTile tile = mesh.getTile(t);
if (tile != null && tile.data != null) {
float? intersection = raycast(tile, src, dst);
if (null != intersection) {
return intersection;
}
}
}
return null;
}
private static float? raycast(MeshTile tile, float[] sp, float[]sq) {
for (int i = 0; i < tile.data.header.polyCount; ++i) {
Poly p = tile.data.polys[i];
if (p.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION) {
continue;
}
PolyDetail pd = tile.data.detailMeshes[i];
if (pd != null) {
float[][] verts = ArrayUtils.Of<float>(3, 3);
for (int j = 0; j < pd.triCount; ++j) {
int t = (pd.triBase + j) * 4;
for (int k = 0; k < 3; ++k) {
int v = tile.data.detailTris[t + k];
if (v < p.vertCount) {
verts[k][0] = tile.data.verts[p.verts[v] * 3];
verts[k][1] = tile.data.verts[p.verts[v] * 3 + 1];
verts[k][2] = tile.data.verts[p.verts[v] * 3 + 2];
} else {
verts[k][0] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3];
verts[k][1] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1];
verts[k][2] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2];
}
}
float? intersection = Intersections.intersectSegmentTriangle(sp, sq, verts[0], verts[1], verts[2]);
if (null != intersection) {
return intersection;
}
}
} else {
// FIXME: Use Poly if PolyDetail is unavailable
}
}
return null;
}
}

View File

@ -0,0 +1,44 @@
/*
recast4j copyright (c) 2021 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 DotRecast.Detour;
namespace DotRecast.Recast.Demo.Geom;
public class NavMeshUtils {
public static float[][] getNavMeshBounds(NavMesh mesh) {
float[] bmin = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity };
float[] bmax = new float[] { float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
for (int t = 0; t < mesh.getMaxTiles(); ++t) {
MeshTile tile = mesh.getTile(t);
if (tile != null && tile.data != null) {
for (int i = 0; i < tile.data.verts.Length; i += 3) {
bmin[0] = Math.Min(bmin[0], tile.data.verts[i]);
bmin[1] = Math.Min(bmin[1], tile.data.verts[i + 1]);
bmin[2] = Math.Min(bmin[2], tile.data.verts[i + 2]);
bmax[0] = Math.Max(bmax[0], tile.data.verts[i]);
bmax[1] = Math.Max(bmax[1], tile.data.verts[i + 1]);
bmax[2] = Math.Max(bmax[2], tile.data.verts[i + 2]);
}
}
}
return new float[][] { bmin, bmax };
}
}

View File

@ -0,0 +1,67 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using DotRecast.Core;
namespace DotRecast.Recast.Demo.Geom;
public class PolyMeshRaycast {
public static float? raycast(IList<RecastBuilderResult> results, float[] src, float[] dst) {
foreach (RecastBuilderResult result in results) {
if (result.getMeshDetail() != null) {
float? intersection = raycast(result.getMesh(), result.getMeshDetail(), src, dst);
if (null != intersection) {
return intersection;
}
}
}
return null;
}
private static float? raycast(PolyMesh poly, PolyMeshDetail meshDetail, float[] sp, float[] sq) {
if (meshDetail != null) {
for (int i = 0; i < meshDetail.nmeshes; ++i) {
int m = i * 4;
int bverts = meshDetail.meshes[m];
int btris = meshDetail.meshes[m + 2];
int ntris = meshDetail.meshes[m + 3];
int verts = bverts * 3;
int tris = btris * 4;
for (int j = 0; j < ntris; ++j) {
float[][] vs = ArrayUtils.Of<float>(3, 3);
for (int k = 0; k < 3; ++k) {
vs[k][0] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3];
vs[k][1] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3 + 1];
vs[k][2] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3 + 2];
}
float? intersection = Intersections.intersectSegmentTriangle(sp, sq, vs[0], vs[1], vs[2]);
if (null != intersection) {
return intersection;
}
}
}
} else {
// TODO: check PolyMesh instead
}
return null;
}
}

View File

@ -0,0 +1,687 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Serilog;
using Silk.NET.GLFW;
using Silk.NET.Input;
using Silk.NET.Maths;
using Silk.NET.OpenGL;
using Silk.NET.OpenGL.Extensions.ImGui;
using Silk.NET.Windowing;
using DotRecast.Core;
using DotRecast.Detour;
using DotRecast.Detour.Extras.Unity.Astar;
using DotRecast.Detour.Io;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using DotRecast.Recast.Demo.Settings;
using DotRecast.Recast.Demo.Tools;
using DotRecast.Recast.Demo.UI;
using ImGuiNET;
using Silk.NET.SDL;
using Silk.NET.Windowing.Sdl;
using static DotRecast.Detour.DetourCommon;
using Window = Silk.NET.Windowing.Window;
namespace DotRecast.Recast.Demo;
public class RecastDemo : MouseListener
{
private readonly ILogger logger = Log.ForContext<RecastDemo>();
private NuklearUI nuklearUI;
private IWindow window;
private IInputContext _input;
private ImGuiController _imgui;
private GL _gl;
private int width = 1000;
private int height = 900;
private readonly string title = "DotRecast Demo";
//private readonly RecastDebugDraw dd;
private readonly NavMeshRenderer renderer;
private bool building = false;
private float timeAcc = 0;
private float camr = 1000;
private readonly SoloNavMeshBuilder soloNavMeshBuilder = new SoloNavMeshBuilder();
private readonly TileNavMeshBuilder tileNavMeshBuilder = new TileNavMeshBuilder();
//private Sample sample;
private bool processHitTest = false;
private bool processHitTestShift;
private int modState;
private readonly float[] mousePos = new float[2];
private bool mouseOverMenu;
private bool pan;
private bool movedDuringPan;
private bool rotate;
private bool movedDuringRotate;
private float scrollZoom;
private readonly float[] origMousePos = new float[2];
private readonly float[] origCameraEulers = new float[2];
private readonly float[] origCameraPos = new float[3];
private readonly float[] cameraEulers = { 45, -45 };
private readonly float[] cameraPos = { 0, 0, 0 };
private readonly float[] rayStart = new float[3];
private readonly float[] rayEnd = new float[3];
private bool markerPositionSet;
private readonly float[] markerPosition = new float[3];
private ToolsUI toolsUI;
private SettingsUI settingsUI;
private long prevFrameTime;
public RecastDemo()
{
// dd = new RecastDebugDraw();
// renderer = new NavMeshRenderer(dd);
}
public void start()
{
window = CreateWindow();
window.Run();
}
private Mouse createMouse(IInputContext input)
{
Mouse mouse = new Mouse(input);
mouse.addListener(this);
return mouse;
}
public void scroll(double xoffset, double yoffset)
{
if (yoffset < 0)
{
// wheel down
if (!mouseOverMenu)
{
scrollZoom += 1.0f;
}
}
else
{
if (!mouseOverMenu)
{
scrollZoom -= 1.0f;
}
}
// float[] modelviewMatrix = dd.viewMatrix(cameraPos, cameraEulers);
// cameraPos[0] += scrollZoom * 2.0f * modelviewMatrix[2];
// cameraPos[1] += scrollZoom * 2.0f * modelviewMatrix[6];
// cameraPos[2] += scrollZoom * 2.0f * modelviewMatrix[10];
scrollZoom = 0;
}
public void position(double x, double y)
{
mousePos[0] = (float)x;
mousePos[1] = (float)y;
int dx = (int)(mousePos[0] - origMousePos[0]);
int dy = (int)(mousePos[1] - origMousePos[1]);
if (rotate)
{
cameraEulers[0] = origCameraEulers[0] + dy * 0.25f;
cameraEulers[1] = origCameraEulers[1] + dx * 0.25f;
if (dx * dx + dy * dy > 3 * 3)
{
movedDuringRotate = true;
}
}
// if (pan)
// {
// float[] modelviewMatrix = dd.viewMatrix(cameraPos, cameraEulers);
// cameraPos[0] = origCameraPos[0];
// cameraPos[1] = origCameraPos[1];
// cameraPos[2] = origCameraPos[2];
//
// cameraPos[0] -= 0.1f * dx * modelviewMatrix[0];
// cameraPos[1] -= 0.1f * dx * modelviewMatrix[4];
// cameraPos[2] -= 0.1f * dx * modelviewMatrix[8];
//
// cameraPos[0] += 0.1f * dy * modelviewMatrix[1];
// cameraPos[1] += 0.1f * dy * modelviewMatrix[5];
// cameraPos[2] += 0.1f * dy * modelviewMatrix[9];
// if (dx * dx + dy * dy > 3 * 3)
// {
// movedDuringPan = true;
// }
// }
}
public void button(int button, int mods, bool down)
{
modState = mods;
if (down)
{
if (button == 1)
{
if (!mouseOverMenu)
{
// Rotate view
rotate = true;
movedDuringRotate = false;
origMousePos[0] = mousePos[0];
origMousePos[1] = mousePos[1];
origCameraEulers[0] = cameraEulers[0];
origCameraEulers[1] = cameraEulers[1];
}
}
else if (button == 2)
{
if (!mouseOverMenu)
{
// Pan view
pan = true;
movedDuringPan = false;
origMousePos[0] = mousePos[0];
origMousePos[1] = mousePos[1];
origCameraPos[0] = cameraPos[0];
origCameraPos[1] = cameraPos[1];
origCameraPos[2] = cameraPos[2];
}
}
}
else
{
// Handle mouse clicks here.
if (button == 1)
{
rotate = false;
if (!mouseOverMenu)
{
if (!movedDuringRotate)
{
processHitTest = true;
processHitTestShift = true;
}
}
}
else if (button == 0)
{
if (!mouseOverMenu)
{
processHitTest = true;
//processHitTestShift = (mods & Keys.GLFW_MOD_SHIFT) != 0 ? true : false;
//processHitTestShift = (mods & Keys.) != 0 ? true : false;
}
}
else if (button == 2)
{
pan = false;
}
}
}
private IWindow CreateWindow()
{
SdlWindowing.Use();
//var glfw = GlfwProvider.GLFW.Value;
// glfw.SetErrorCallback(ErrorCallback);
// if (!glfw.Init())
// {
// throw new InvalidOperationException("Unable to initialize GLFW");
// }
// glfw.DefaultWindowHints();
// glfw.WindowHint(WindowHintBool.Visible, false);
// glfw.WindowHint(WindowHintBool.Resizable, false);
// glfw.WindowHint(WindowHintBool.SrgbCapable, true);
// glfw.WindowHint(WindowHintInt.RedBits, 8);
// glfw.WindowHint(WindowHintInt.GreenBits, 8);
// glfw.WindowHint(WindowHintInt.BlueBits, 8);
// glfw.WindowHint(WindowHintInt.Samples, 4);
// glfw.WindowHint(WindowHintBool.DoubleBuffer, true);
// glfw.WindowHint(WindowHintInt.DepthBits, 24);
//
// glfw.WindowHint(WindowHintInt.ContextVersionMajor, 3);
// glfw.WindowHint(WindowHintInt.ContextVersionMinor, 2);
// glfw.WindowHint(WindowHintBool.OpenGLForwardCompat, true);
// glfw.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Core);
// glfw.WindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
// glfw.WindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
var monitor = Window.Platforms.First().GetMainMonitor();
// // if (monitors.limit() > 1) {
// // monitor = monitors[1];
// // }
var resolution = monitor.VideoMode.Resolution.Value;
float aspect = 16.0f / 9.0f;
width = Math.Min(resolution.X, (int)(resolution.Y * aspect)) - 100;
height = resolution.Y - 100;
// glfwWindowHint(GLFW_RED_BITS, mode.redBits());
// glfwWindowHint(GLFW_GREEN_BITS, mode.greenBits());
// glfwWindowHint(GLFW_BLUE_BITS, mode.blueBits());
// glfwWindowHint(GLFW_REFRESH_RATE, mode.refreshRate());
var options = WindowOptions.Default;
options.Title = title;
options.Size = new Vector2D<int>(width, height);
options.Position = new Vector2D<int>((resolution.X - width) / 2, (resolution.Y - height) / 2);
window = Window.Create(options);
if (window == null)
{
throw new Exception("Failed to create the GLFW window");
}
window.Load += OnWindowOnLoad;
window.Update += OnWindowOnUpdate;
window.Render += OnWindowOnRender;
// // -- move somewhere else:
// glfw.SetWindowPos(window, (mode->Width - width) / 2, (mode->Height - height) / 2);
// // glfwSetWindowMonitor(window.getWindow(), monitor, 0, 0, mode.width(), mode.height(), mode.refreshRate());
// glfw.ShowWindow(window);
// glfw.MakeContextCurrent(window);
//}
//glfw.SwapInterval(1);
return window;
}
private DemoInputGeomProvider loadInputMesh(byte[] stream)
{
DemoInputGeomProvider geom = DemoObjImporter.load(stream);
//sample = new Sample(geom, ImmutableArray<RecastBuilderResult>.Empty, null, settingsUI, dd);
toolsUI.setEnabled(true);
return geom;
}
private void loadNavMesh(FileStream file, string filename)
{
NavMesh mesh = null;
if (filename.EndsWith(".zip") || filename.EndsWith(".bytes"))
{
UnityAStarPathfindingImporter importer = new UnityAStarPathfindingImporter();
mesh = importer.load(file)[0];
}
else if (filename.EndsWith(".bin") || filename.EndsWith(".navmesh"))
{
MeshSetReader reader = new MeshSetReader();
using (var fis = new BinaryReader(file))
{
mesh = reader.read(fis, 6);
}
}
if (mesh != null)
{
//sample = new Sample(null, ImmutableArray<RecastBuilderResult>.Empty, mesh, settingsUI, dd);
toolsUI.setEnabled(true);
}
}
private void OnWindowOnLoad()
{
_input = window.CreateInput();
_gl = window.CreateOpenGL();
_imgui = new ImGuiController(_gl, window, _input);
//dd.init(_gl, camr);
// // if (capabilities.OpenGL43) {
// // GL43.glDebugMessageControl(GL43.GL_DEBUG_SOURCE_API, GL43.GL_DEBUG_TYPE_OTHER,
// // GL43.GL_DEBUG_SEVERITY_NOTIFICATION,
// // (int[]) null, false);
// // } else if (capabilities.GL_ARB_debug_output) {
// // ARBDebugOutput.glDebugMessageControlARB(ARBDebugOutput.GL_DEBUG_SOURCE_API_ARB,
// // ARBDebugOutput.GL_DEBUG_TYPE_OTHER_ARB, ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB, (int[]) null, false);
// // }
var vendor = _gl.GetStringS(GLEnum.Vendor);
logger.Debug(vendor);
var version = _gl.GetStringS(GLEnum.Version);
logger.Debug(version);
var renderGl = _gl.GetStringS(GLEnum.Renderer);
logger.Debug(renderGl);
var glslString = _gl.GetStringS(GLEnum.ShadingLanguageVersion);
logger.Debug(glslString);
window.CreateInput();
settingsUI = new SettingsUI();
toolsUI = new ToolsUI(
new TestNavmeshTool(),
new OffMeshConnectionTool(),
new ConvexVolumeTool(),
new CrowdTool(),
new JumpLinkBuilderTool(),
new DynamicUpdateTool());
nuklearUI = new NuklearUI(window, _input, settingsUI, toolsUI);
DemoInputGeomProvider geom = loadInputMesh(Loader.ToBytes("nav_test.obj"));
//sample = new Sample(geom, ImmutableArray<RecastBuilderResult>.Empty, null, settingsUI, dd);
}
private void OnWindowOnUpdate(double dt)
{
/*
* try (MemoryStack stack = stackPush()) { int[] w = stack.mallocInt(1); int[] h =
* stack.mallocInt(1); glfwGetWindowSize(win, w, h); width = w[0]; height = h[0]; }
*/
// if (sample.getInputGeom() != null)
// {
// float[] bmin = sample.getInputGeom().getMeshBoundsMin();
// float[] bmax = sample.getInputGeom().getMeshBoundsMax();
// int[] voxels = Recast.calcGridSize(bmin, bmax, settingsUI.getCellSize());
// settingsUI.setVoxels(voxels);
// settingsUI.setTiles(tileNavMeshBuilder.getTiles(sample.getInputGeom(), settingsUI.getCellSize(), settingsUI.getTileSize()));
// settingsUI.setMaxTiles(tileNavMeshBuilder.getMaxTiles(sample.getInputGeom(), settingsUI.getCellSize(), settingsUI.getTileSize()));
// settingsUI.setMaxPolys(tileNavMeshBuilder.getMaxPolysPerTile(sample.getInputGeom(), settingsUI.getCellSize(), settingsUI.getTileSize()));
// }
nuklearUI.inputBegin();
window.DoEvents();
nuklearUI.inputEnd(window);
long time = Stopwatch.GetTimestamp() / 1000;
//float dt = (time - prevFrameTime) / 1000000.0f;
prevFrameTime = time;
// Update sample simulation.
float SIM_RATE = 20;
float DELTA_TIME = 1.0f / SIM_RATE;
timeAcc = clamp((float)(timeAcc + dt), -1.0f, 1.0f);
int simIter = 0;
// while (timeAcc > DELTA_TIME)
// {
// timeAcc -= DELTA_TIME;
// if (simIter < 5 && sample != null)
// {
// toolsUI.handleUpdate(DELTA_TIME);
// }
//
// simIter++;
// }
// Set the viewport.
// glViewport(0, 0, width, height);
int[] viewport = new int[] { 0, 0, width, height };
// glGetIntegerv(GL_VIEWPORT, viewport);
// Clear the screen
// dd.clear();
// float[] projectionMatrix = dd.projectionMatrix(50f, (float)width / (float)height, 1.0f, camr);
// float[] modelviewMatrix = dd.viewMatrix(cameraPos, cameraEulers);
//mouseOverMenu = nuklearUI.layout(window, 0, 0, width, height, (int)mousePos[0], (int)mousePos[1]);
if (settingsUI.isMeshInputTrigerred())
{
// aFilterPatterns.put(stack.UTF8("*.obj"));
// aFilterPatterns.flip();
// string filename = TinyFileDialogs.tinyfd_openFileDialog("Open Mesh File", "", aFilterPatterns,
// "Mesh File (*.obj)", false);
// if (filename != null) {
// try (InputStream stream = new FileInputStream(filename)) {
// sample.update(loadInputMesh(stream), null, null);
// } catch (IOException e) {
// Console.WriteLine(e).printStackTrace();
// }
// }
}
else if (settingsUI.isNavMeshInputTrigerred())
{
// try (MemoryStack stack = stackPush()) {
// PointerBuffer aFilterPatterns = stack.mallocPointer(4);
// aFilterPatterns.put(stack.UTF8("*.bin"));
// aFilterPatterns.put(stack.UTF8("*.zip"));
// aFilterPatterns.put(stack.UTF8("*.bytes"));
// aFilterPatterns.put(stack.UTF8("*.navmesh"));
// aFilterPatterns.flip();
// string filename = TinyFileDialogs.tinyfd_openFileDialog("Open Nav Mesh File", "", aFilterPatterns,
// "Nav Mesh File", false);
// if (filename != null) {
// File file = new File(filename);
// if (file.exists()) {
// try {
// loadNavMesh(file, filename);
// geom = null;
// } catch (Exception e) {
// Console.WriteLine(e);
// }
// }
// }
// }
}
// else if (settingsUI.isBuildTriggered() && sample.getInputGeom() != null)
// {
// if (!building)
// {
// float m_cellSize = settingsUI.getCellSize();
// float m_cellHeight = settingsUI.getCellHeight();
// float m_agentHeight = settingsUI.getAgentHeight();
// float m_agentRadius = settingsUI.getAgentRadius();
// float m_agentMaxClimb = settingsUI.getAgentMaxClimb();
// float m_agentMaxSlope = settingsUI.getAgentMaxSlope();
// int m_regionMinSize = settingsUI.getMinRegionSize();
// int m_regionMergeSize = settingsUI.getMergedRegionSize();
// float m_edgeMaxLen = settingsUI.getEdgeMaxLen();
// float m_edgeMaxError = settingsUI.getEdgeMaxError();
// int m_vertsPerPoly = settingsUI.getVertsPerPoly();
// float m_detailSampleDist = settingsUI.getDetailSampleDist();
// float m_detailSampleMaxError = settingsUI.getDetailSampleMaxError();
// int m_tileSize = settingsUI.getTileSize();
// long t = Stopwatch.GetTimestamp();
//
// Tuple<IList<RecastBuilderResult>, NavMesh> buildResult;
// if (settingsUI.isTiled())
// {
// buildResult = tileNavMeshBuilder.build(sample.getInputGeom(), settingsUI.getPartitioning(), m_cellSize,
// m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize,
// m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist,
// m_detailSampleMaxError, settingsUI.isFilterLowHangingObstacles(), settingsUI.isFilterLedgeSpans(),
// settingsUI.isFilterWalkableLowHeightSpans(), m_tileSize);
// }
// else
// {
// buildResult = soloNavMeshBuilder.build(sample.getInputGeom(), settingsUI.getPartitioning(), m_cellSize,
// m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize,
// m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist,
// m_detailSampleMaxError, settingsUI.isFilterLowHangingObstacles(), settingsUI.isFilterLedgeSpans(),
// settingsUI.isFilterWalkableLowHeightSpans());
// }
//
// sample.update(sample.getInputGeom(), buildResult.Item1, buildResult.Item2);
// sample.setChanged(false);
// settingsUI.setBuildTime((Stopwatch.GetTimestamp() - t) / 1_000_000);
// toolsUI.setSample(sample);
// }
// }
else
{
building = false;
}
if (!mouseOverMenu)
{
// GLU.glhUnProjectf(mousePos[0], viewport[3] - 1 - mousePos[1], 0.0f, modelviewMatrix, projectionMatrix, viewport,
// rayStart);
// GLU.glhUnProjectf(mousePos[0], viewport[3] - 1 - mousePos[1], 1.0f, modelviewMatrix, projectionMatrix, viewport,
// rayEnd);
// Hit test mesh.
// DemoInputGeomProvider inputGeom = sample.getInputGeom();
// if (processHitTest && sample != null)
// {
// float? hit = null;
// if (inputGeom != null)
// {
// hit = inputGeom.raycastMesh(rayStart, rayEnd);
// }
//
// if (!hit.HasValue && sample.getNavMesh() != null)
// {
// hit = NavMeshRaycast.raycast(sample.getNavMesh(), rayStart, rayEnd);
// }
//
// if (!hit.HasValue && sample.getRecastResults() != null)
// {
// hit = PolyMeshRaycast.raycast(sample.getRecastResults(), rayStart, rayEnd);
// }
//
// float[] rayDir = new float[] { rayEnd[0] - rayStart[0], rayEnd[1] - rayStart[1], rayEnd[2] - rayStart[2] };
// Tool rayTool = toolsUI.getTool();
// vNormalize(rayDir);
// if (rayTool != null)
// {
// rayTool.handleClickRay(rayStart, rayDir, processHitTestShift);
// }
// // TODO : 잠시 주석
// // if (hit.HasValue) {
// // float hitTime = hit.Value;
// // if ((modState & GLFW_MOD_CONTROL) != 0) {
// // // Marker
// // markerPositionSet = true;
// // markerPosition[0] = rayStart[0] + (rayEnd[0] - rayStart[0]) * hitTime;
// // markerPosition[1] = rayStart[1] + (rayEnd[1] - rayStart[1]) * hitTime;
// // markerPosition[2] = rayStart[2] + (rayEnd[2] - rayStart[2]) * hitTime;
// // } else {
// // float[] pos = new float[3];
// // pos[0] = rayStart[0] + (rayEnd[0] - rayStart[0]) * hitTime;
// // pos[1] = rayStart[1] + (rayEnd[1] - rayStart[1]) * hitTime;
// // pos[2] = rayStart[2] + (rayEnd[2] - rayStart[2]) * hitTime;
// // if (rayTool != null) {
// // rayTool.handleClick(rayStart, pos, processHitTestShift);
// // }
// // }
// // } else {
// // if ((modState & GLFW_MOD_CONTROL) != 0) {
// // // Marker
// // markerPositionSet = false;
// // }
// // }
// }
processHitTest = false;
}
// if (sample.isChanged())
// {
// float[] bmin = null;
// float[] bmax = null;
// if (sample.getInputGeom() != null)
// {
// bmin = sample.getInputGeom().getMeshBoundsMin();
// bmax = sample.getInputGeom().getMeshBoundsMax();
// }
// else if (sample.getNavMesh() != null)
// {
// float[][] bounds = NavMeshUtils.getNavMeshBounds(sample.getNavMesh());
// bmin = bounds[0];
// bmax = bounds[1];
// }
// else if (0 < sample.getRecastResults().Count)
// {
// foreach (RecastBuilderResult result in sample.getRecastResults())
// {
// if (result.getSolidHeightfield() != null)
// {
// if (bmin == null)
// {
// bmin = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity };
// bmax = new float[] { float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
// }
//
// for (int i = 0; i < 3; i++)
// {
// bmin[i] = Math.Min(bmin[i], result.getSolidHeightfield().bmin[i]);
// bmax[i] = Math.Max(bmax[i], result.getSolidHeightfield().bmax[i]);
// }
// }
// }
// }
//
// if (bmin != null && bmax != null)
// {
// camr = (float)(Math.Sqrt(
// DemoMath.sqr(bmax[0] - bmin[0]) + DemoMath.sqr(bmax[1] - bmin[1]) + DemoMath.sqr(bmax[2] - bmin[2]))
// / 2);
// cameraPos[0] = (bmax[0] + bmin[0]) / 2 + camr;
// cameraPos[1] = (bmax[1] + bmin[1]) / 2 + camr;
// cameraPos[2] = (bmax[2] + bmin[2]) / 2 + camr;
// camr *= 3;
// cameraEulers[0] = 45;
// cameraEulers[1] = -45;
// }
//
// sample.setChanged(false);
// toolsUI.setSample(sample);
// }
// dd.fog(camr * 0.1f, camr * 1.25f);
// renderer.render(sample);
// Tool tool = toolsUI.getTool();
// if (tool != null)
// {
// tool.handleRender(renderer);
// }
//
// dd.fog(false);
_imgui.Update((float)dt);
}
private unsafe void OnWindowOnRender(double dt)
{
_gl.Clear(ClearBufferMask.ColorBufferBit);
// Render GUI
//mouseOverMenu = nuklearUI.layout(window, 0, 0, width, height, (int)mousePos[0], (int)mousePos[1]);
//nuklearUI.render();
ImGui.Button("hello");
ImGui.Button("world");
_imgui.Render();
}
public static void Main(string[] args)
{
var demo = new RecastDemo();
demo.start();
}
private static void ErrorCallback(Silk.NET.GLFW.ErrorCode code, string message)
{
Console.WriteLine($"GLFW error [{code}]: {message}");
}
}

View File

@ -0,0 +1,86 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using DotRecast.Detour;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using DotRecast.Recast.Demo.Settings;
namespace DotRecast.Recast.Demo;
public class Sample {
private DemoInputGeomProvider inputGeom;
private NavMesh navMesh;
private NavMeshQuery navMeshQuery;
private readonly SettingsUI settingsUI;
private IList<RecastBuilderResult> recastResults;
private bool changed;
public Sample(DemoInputGeomProvider inputGeom, IList<RecastBuilderResult> recastResults, NavMesh navMesh,
SettingsUI settingsUI, RecastDebugDraw debugDraw) {
this.inputGeom = inputGeom;
this.recastResults = recastResults;
this.navMesh = navMesh;
this.settingsUI = settingsUI;
setQuery(navMesh);
changed = true;
}
private void setQuery(NavMesh navMesh) {
navMeshQuery = navMesh != null ? new NavMeshQuery(navMesh) : null;
}
public DemoInputGeomProvider getInputGeom() {
return inputGeom;
}
public IList<RecastBuilderResult> getRecastResults() {
return recastResults;
}
public NavMesh getNavMesh() {
return navMesh;
}
public SettingsUI getSettingsUI() {
return settingsUI;
}
public NavMeshQuery getNavMeshQuery() {
return navMeshQuery;
}
public bool isChanged() {
return changed;
}
public void setChanged(bool changed) {
this.changed = changed;
}
public void update(DemoInputGeomProvider geom, IList<RecastBuilderResult> recastResults, NavMesh navMesh) {
inputGeom = geom;
this.recastResults = recastResults;
this.navMesh = navMesh;
setQuery(navMesh);
changed = true;
}
}

View File

@ -0,0 +1,336 @@
/*
recast4j copyright (c) 2015-2019 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 DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.UI;
using ImGuiNET;
using Silk.NET.OpenGL.Extensions.ImGui;
using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Settings;
public class SettingsUI : NuklearUIModule {
private readonly float[] cellSize = new[] { 0.3f };
private readonly float[] cellHeight = new[] { 0.2f };
private readonly float[] agentHeight = new[] { 2f };
private readonly float[] agentRadius = new[] { 0.6f };
private readonly float[] agentMaxClimb = new[] { 0.9f };
private readonly float[] agentMaxSlope = new[] { 45f };
private readonly int[] minRegionSize = new[] { 8 };
private readonly int[] mergedRegionSize = new[] { 20 };
private PartitionType partitioning = PartitionType.WATERSHED;
private bool filterLowHangingObstacles = true;
private bool filterLedgeSpans = true;
private bool filterWalkableLowHeightSpans = true;
private readonly float[] edgeMaxLen = new[] { 12f };
private readonly float[] edgeMaxError = new[] { 1.3f };
private readonly int[] vertsPerPoly = new[] { 6 };
private readonly float[] detailSampleDist = new[] { 6f };
private readonly float[] detailSampleMaxError = new[] { 1f };
private bool tiled = false;
private readonly int[] tileSize = new[] { 32 };
// public readonly NkColor white = NkColor.create();
// public readonly NkColor background = NkColor.create();
// public readonly NkColor transparent = NkColor.create();
private bool buildTriggered;
private long buildTime;
private readonly int[] voxels = new int[2];
private readonly int[] tiles = new int[2];
private int maxTiles;
private int maxPolys;
private DrawMode drawMode = DrawMode.DRAWMODE_NAVMESH;
private bool meshInputTrigerred;
private bool navMeshInputTrigerred;
public bool layout(IWindow i, int x, int y, int width, int height, int mouseX, int mouseY) {
bool mouseInside = false;
// nk_rgb(255, 255, 255, white);
// nk_rgba(0, 0, 0, 192, background);
// nk_rgba(255, 0, 0, 0, transparent);
// try (MemoryStack stack = stackPush()) {
// ctx.style().text().color().set(white);
// ctx.style().option().text_normal().set(white);
// ctx.style().property().label_normal().set(white);
// ctx.style().window().background().set(background);
// NkStyleItem styleItem = NkStyleItem.mallocStack(stack);
// nk_style_item_color(background, styleItem);
// ctx.style().window().fixed_background().set(styleItem);
// nk_style_item_color(white, styleItem);
// ctx.style().option().cursor_hover().set(styleItem);
// ctx.style().option().cursor_normal().set(styleItem);
// nk_style_item_color(transparent, styleItem);
// ctx.style().tab().node_minimize_button().normal().set(styleItem);
// ctx.style().tab().node_minimize_button().active().set(styleItem);
// ctx.style().tab().node_maximize_button().normal().set(styleItem);
// ctx.style().tab().node_maximize_button().active().set(styleItem);
// }
// try (MemoryStack stack = stackPush()) {
// NkRect rect = NkRect.mallocStack(stack);
// if (nk_begin(ctx, "Properties", nk_rect(width - 255, 5, 250, height - 10, rect),
// NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE)) {
//
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Input Mesh", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// meshInputTrigerred = nk_button_text(ctx, "Load Source Geom...");
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Verts: %d Tris: %d", 0, 0), NK_TEXT_ALIGN_RIGHT);
//
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Rasterization", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Cell Size", 0.1f, cellSize, 1f, 0.01f, 0.01f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Cell Height", 0.1f, cellHeight, 1f, 0.01f, 0.01f);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Voxels %d x %d", voxels[0], voxels[1]), NK_TEXT_ALIGN_RIGHT);
//
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Agent", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Height", 0.1f, agentHeight, 5f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Radius", 0.0f, agentRadius, 5f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Climb", 0.1f, agentMaxClimb, 5f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Slope", 0f, agentMaxSlope, 90f, 1f, 1f);
//
// nk_layout_row_dynamic(ctx, 3, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Region", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Min Region Size", 0, minRegionSize, 150, 1, 1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Merged Region Size", 0, mergedRegionSize, 150, 1, 1f);
//
// nk_layout_row_dynamic(ctx, 3, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Partitioning", NK_TEXT_ALIGN_LEFT);
// partitioning = NuklearUIHelper.nk_radio(ctx, PartitionType.values(), partitioning,
// p => p.name().substring(0, 1) + p.name().substring(1).toLowerCase());
//
// nk_layout_row_dynamic(ctx, 3, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Filtering", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// filterLowHangingObstacles = nk_option_text(ctx, "Low Hanging Obstacles", filterLowHangingObstacles);
// nk_layout_row_dynamic(ctx, 20, 1);
// filterLedgeSpans = nk_option_text(ctx, "Ledge Spans", filterLedgeSpans);
// nk_layout_row_dynamic(ctx, 20, 1);
// filterWalkableLowHeightSpans = nk_option_text(ctx, "Walkable Low Height Spans",
// filterWalkableLowHeightSpans);
//
// nk_layout_row_dynamic(ctx, 3, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Polygonization", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Edge Length", 0f, edgeMaxLen, 50f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Edge Error", 0.1f, edgeMaxError, 3f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Vert Per Poly", 3, vertsPerPoly, 12, 1, 1);
//
// nk_layout_row_dynamic(ctx, 3, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Detail Mesh", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Sample Distance", 0f, detailSampleDist, 16f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Sample Error", 0f, detailSampleMaxError, 16f, 0.1f, 0.1f);
//
// nk_layout_row_dynamic(ctx, 3, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Tiling", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// tiled = nk_check_text(ctx, "Enable", tiled);
// if (tiled) {
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Tile Size", 16, tileSize, 1024, 16, 16);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Tiles %d x %d", tiles[0], tiles[1]), NK_TEXT_ALIGN_RIGHT);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Max Tiles %d", maxTiles), NK_TEXT_ALIGN_RIGHT);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Max Polys %d", maxPolys), NK_TEXT_ALIGN_RIGHT);
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Build Time: %d ms", buildTime), NK_TEXT_ALIGN_LEFT);
//
// nk_layout_row_dynamic(ctx, 20, 1);
// buildTriggered = nk_button_text(ctx, "Build");
// nk_layout_row_dynamic(ctx, 3, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// navMeshInputTrigerred = nk_button_text(ctx, "Load Nav Mesh...");
//
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Draw", NK_TEXT_ALIGN_LEFT);
// drawMode = NuklearUIHelper.nk_radio(ctx, DrawMode.values(), drawMode, dm => dm.toString());
//
// nk_window_get_bounds(ctx, rect);
// if (mouseX >= rect.x() && mouseX <= rect.x() + rect.w() && mouseY >= rect.y()
// && mouseY <= rect.y() + rect.h()) {
// mouseInside = true;
// }
// }
// nk_end(ctx);
// }
return mouseInside;
}
public float getCellSize() {
//return cellSize[0];
return 0;
}
public float getCellHeight() {
//return cellHeight[0];
return 0;
}
public float getAgentHeight() {
//return agentHeight[0];
return 0;
}
public float getAgentRadius() {
//return agentRadius[0];
return 0;
}
public float getAgentMaxClimb() {
//return agentMaxClimb[0];
return 0;
}
public float getAgentMaxSlope() {
//return agentMaxSlope[0];
return 0;
}
public int getMinRegionSize() {
//return minRegionSize[0];
return 0;
}
public int getMergedRegionSize() {
//return mergedRegionSize[0];
return 0;
}
public PartitionType getPartitioning() {
return partitioning;
}
public bool isBuildTriggered() {
return buildTriggered;
}
public bool isFilterLowHangingObstacles() {
return filterLowHangingObstacles;
}
public bool isFilterLedgeSpans() {
return filterLedgeSpans;
}
public bool isFilterWalkableLowHeightSpans() {
return filterWalkableLowHeightSpans;
}
public void setBuildTime(long buildTime) {
this.buildTime = buildTime;
}
public DrawMode getDrawMode() {
return drawMode;
}
public float getEdgeMaxLen() {
return edgeMaxLen[0];
}
public float getEdgeMaxError() {
return edgeMaxError[0];
}
public int getVertsPerPoly() {
return vertsPerPoly[0];
}
public float getDetailSampleDist() {
return detailSampleDist[0];
}
public float getDetailSampleMaxError() {
return detailSampleMaxError[0];
}
public void setVoxels(int[] voxels) {
this.voxels[0] = voxels[0];
this.voxels[1] = voxels[1];
}
public bool isTiled() {
return tiled;
}
public int getTileSize() {
return tileSize[0];
}
public void setTiles(int[] tiles) {
this.tiles[0] = tiles[0];
this.tiles[1] = tiles[1];
}
public void setMaxTiles(int maxTiles) {
this.maxTiles = maxTiles;
}
public void setMaxPolys(int maxPolys) {
this.maxPolys = maxPolys;
}
public bool isMeshInputTrigerred() {
return meshInputTrigerred;
}
public bool isNavMeshInputTrigerred() {
return navMeshInputTrigerred;
}
}

View File

@ -0,0 +1,204 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using Silk.NET.Windowing;
using DotRecast.Core;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class ConvexVolumeTool : Tool {
private Sample sample;
private AreaModification areaType = SampleAreaModifications.SAMPLE_AREAMOD_GRASS;
private readonly float[] boxHeight = new[] { 6f };
private readonly float[] boxDescent = new[] { 1f };
private readonly float[] polyOffset = new[] { 0f };
private readonly List<float> pts = new();
private readonly List<int> hull = new();
public override void setSample(Sample m_sample) {
sample = m_sample;
}
public override void handleClick(float[] s, float[] p, bool shift) {
DemoInputGeomProvider geom = sample.getInputGeom();
if (geom == null) {
return;
}
if (shift) {
// Delete
int nearestIndex = -1;
IList<ConvexVolume> vols = geom.convexVolumes();
for (int i = 0; i < vols.Count; ++i) {
if (PolyUtils.pointInPoly(vols[i].verts, p) && p[1] >= vols[i].hmin
&& p[1] <= vols[i].hmax) {
nearestIndex = i;
}
}
// If end point close enough, delete it.
if (nearestIndex != -1) {
geom.convexVolumes().RemoveAt(nearestIndex);
}
} else {
// Create
// If clicked on that last pt, create the shape.
if (pts.Count > 0 && DemoMath.vDistSqr(p,
new float[] { pts[pts.Count - 3], pts[pts.Count - 2], pts[pts.Count - 1] },
0) < 0.2f * 0.2f) {
if (hull.Count > 2) {
// Create shape.
float[] verts = new float[hull.Count * 3];
for (int i = 0; i < hull.Count; ++i) {
verts[i * 3] = pts[hull[i] * 3];
verts[i * 3 + 1] = pts[hull[i] * 3 + 1];
verts[i * 3 + 2] = pts[hull[i] * 3 + 2];
}
float minh = float.MaxValue, maxh = 0;
for (int i = 0; i < hull.Count; ++i) {
minh = Math.Min(minh, verts[i * 3 + 1]);
}
minh -= boxDescent[0];
maxh = minh + boxHeight[0];
if (polyOffset[0] > 0.01f) {
float[] offset = new float[verts.Length * 2];
int noffset = PolyUtils.offsetPoly(verts, hull.Count, polyOffset[0], offset,
offset.Length);
if (noffset > 0) {
geom.addConvexVolume(ArrayUtils.CopyOf(offset, 0, noffset * 3), minh, maxh, areaType);
}
} else {
geom.addConvexVolume(verts, minh, maxh, areaType);
}
}
pts.Clear();
hull.Clear();
} else {
// Add new point
pts.Add(p[0]);
pts.Add(p[1]);
pts.Add(p[2]);
// Update hull.
if (pts.Count > 3) {
hull.Clear();
hull.AddRange(ConvexUtils.convexhull(pts));
} else {
hull.Clear();
}
}
}
}
public override void handleRender(NavMeshRenderer renderer) {
RecastDebugDraw dd = renderer.getDebugDraw();
// Find height extent of the shape.
float minh = float.MaxValue, maxh = 0;
for (int i = 0; i < pts.Count; i += 3) {
minh = Math.Min(minh, pts[i + 1]);
}
minh -= boxDescent[0];
maxh = minh + boxHeight[0];
dd.begin(POINTS, 4.0f);
for (int i = 0; i < pts.Count; i += 3) {
int col = duRGBA(255, 255, 255, 255);
if (i == pts.Count - 3) {
col = duRGBA(240, 32, 16, 255);
}
dd.vertex(pts[i + 0], pts[i + 1] + 0.1f, pts[i + 2], col);
}
dd.end();
dd.begin(LINES, 2.0f);
for (int i = 0, j = hull.Count - 1; i < hull.Count; j = i++) {
int vi = hull[j] * 3;
int vj = hull[i] * 3;
dd.vertex(pts[vj + 0], minh, pts[vj + 2], duRGBA(255, 255, 255, 64));
dd.vertex(pts[vi + 0], minh, pts[vi + 2], duRGBA(255, 255, 255, 64));
dd.vertex(pts[vj + 0], maxh, pts[vj + 2], duRGBA(255, 255, 255, 64));
dd.vertex(pts[vi + 0], maxh, pts[vi + 2], duRGBA(255, 255, 255, 64));
dd.vertex(pts[vj + 0], minh, pts[vj + 2], duRGBA(255, 255, 255, 64));
dd.vertex(pts[vj + 0], maxh, pts[vj + 2], duRGBA(255, 255, 255, 64));
}
dd.end();
}
public override void layout(IWindow ctx) {
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Shape Height", 0.1f, boxHeight, 20f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Shape Descent", 0.1f, boxDescent, 20f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Poly Offset", 0.1f, polyOffset, 10f, 0.1f, 0.1f);
// nk_label(ctx, "Area Type", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Ground", areaType == SampleAreaModifications.SAMPLE_AREAMOD_GROUND)) {
// areaType = SampleAreaModifications.SAMPLE_AREAMOD_GROUND;
// }
// if (nk_option_label(ctx, "Water", areaType == SampleAreaModifications.SAMPLE_AREAMOD_WATER)) {
// areaType = SampleAreaModifications.SAMPLE_AREAMOD_WATER;
// }
// if (nk_option_label(ctx, "Road", areaType == SampleAreaModifications.SAMPLE_AREAMOD_ROAD)) {
// areaType = SampleAreaModifications.SAMPLE_AREAMOD_ROAD;
// }
// if (nk_option_label(ctx, "Door", areaType == SampleAreaModifications.SAMPLE_AREAMOD_DOOR)) {
// areaType = SampleAreaModifications.SAMPLE_AREAMOD_DOOR;
// }
// if (nk_option_label(ctx, "Grass", areaType == SampleAreaModifications.SAMPLE_AREAMOD_GRASS)) {
// areaType = SampleAreaModifications.SAMPLE_AREAMOD_GRASS;
// }
// if (nk_option_label(ctx, "Jump", areaType == SampleAreaModifications.SAMPLE_AREAMOD_JUMP)) {
// areaType = SampleAreaModifications.SAMPLE_AREAMOD_JUMP;
// }
// if (nk_button_text(ctx, "Clear Shape")) {
// hull.clear();
// pts.clear();
// }
// if (nk_button_text(ctx, "Remove All")) {
// hull.clear();
// pts.clear();
// DemoInputGeomProvider geom = sample.getInputGeom();
// if (geom != null) {
// geom.clearConvexVolumes();
// }
// }
}
public override string getName() {
return "Create Convex Volumes";
}
public override void handleUpdate(float dt) {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,393 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using System.Diagnostics;
using DotRecast.Core;
using DotRecast.Detour;
using DotRecast.Detour.Crowd;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using Silk.NET.Windowing;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
namespace DotRecast.Recast.Demo.Tools;
public class CrowdProfilingTool {
private readonly Func<CrowdAgentParams> agentParamsSupplier;
private readonly int[] expandSimOptions = new[] { 1 };
private readonly int[] expandCrowdOptions = new[] { 1 };
private readonly int[] agents = new[] { 1000 };
private readonly int[] randomSeed = new[] { 270 };
private readonly int[] numberOfZones = new[] { 4 };
private readonly float[] zoneRadius = new[] { 20f };
private readonly float[] percentMobs = new[] { 80f };
private readonly float[] percentTravellers = new[] { 15f };
private readonly int[] pathQueueSize = new[] { 32 };
private readonly int[] maxIterations = new[] { 300 };
private Crowd crowd;
private NavMesh navMesh;
private CrowdConfig config;
private NavMeshQuery.FRand rnd;
private readonly List<FindRandomPointResult> zones = new();
private long crowdUpdateTime;
public CrowdProfilingTool(Func<CrowdAgentParams> agentParamsSupplier) {
this.agentParamsSupplier = agentParamsSupplier;
}
public void layout(IWindow ctx) {
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// if (nk_tree_state_push(ctx, 0, "Simulation Options", expandSimOptions)) {
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Agents", 0, agents, 10000, 1, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Random Seed", 0, randomSeed, 1024, 1, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Number of Zones", 0, numberOfZones, 10, 1, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Zone Radius", 0, zoneRadius, 100, 1, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Mobs %", 0, percentMobs, 100, 1, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Travellers %", 0, percentTravellers, 100, 1, 1);
// nk_tree_state_pop(ctx);
// }
// if (nk_tree_state_push(ctx, 0, "Crowd Options", expandCrowdOptions)) {
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Path Queue Size", 0, pathQueueSize, 1024, 1, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Max Iterations", 0, maxIterations, 4000, 1, 1);
// nk_tree_state_pop(ctx);
// }
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_button_text(ctx, "Start")) {
// if (navMesh != null) {
// rnd = new NavMeshQuery.FRand(randomSeed[0]);
// createCrowd();
// createZones();
// NavMeshQuery navquery = new NavMeshQuery(navMesh);
// QueryFilter filter = new DefaultQueryFilter();
// for (int i = 0; i < agents[0]; i++) {
// float tr = rnd.frand();
// AgentType type = AgentType.MOB;
// float mobsPcnt = percentMobs[0] / 100f;
// if (tr > mobsPcnt) {
// tr = rnd.frand();
// float travellerPcnt = percentTravellers[0] / 100f;
// if (tr > travellerPcnt) {
// type = AgentType.VILLAGER;
// } else {
// type = AgentType.TRAVELLER;
// }
// }
// float[] pos = null;
// switch (type) {
// case MOB:
// pos = getMobPosition(navquery, filter, pos);
// break;
// case VILLAGER:
// pos = getVillagerPosition(navquery, filter, pos);
// break;
// case TRAVELLER:
// pos = getVillagerPosition(navquery, filter, pos);
// break;
// }
// if (pos != null) {
// addAgent(pos, type);
// }
// }
// }
// }
// if (crowd != null) {
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Max time to enqueue request: %.3f s", crowd.telemetry().maxTimeToEnqueueRequest()),
// NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Max time to find path: %.3f s", crowd.telemetry().maxTimeToFindPath()),
// NK_TEXT_ALIGN_LEFT);
// List<Tuple<string, long>> timings = crowd.telemetry().executionTimings().entrySet().stream()
// .map(e => Tuple.Create(e.getKey(), e.getValue())).sorted((t1, t2) => long.compare(t2.Item2, t1.Item2))
// .collect(toList());
// foreach (Tuple<string, long> e in timings) {
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("%s: %d us", e.Item1, e.Item2 / 1_000), NK_TEXT_ALIGN_LEFT);
// }
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Update Time: %d ms", crowdUpdateTime), NK_TEXT_ALIGN_LEFT);
// }
}
private float[] getMobPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos) {
Result<FindRandomPointResult> result = navquery.findRandomPoint(filter, rnd);
if (result.succeeded()) {
pos = result.result.getRandomPt();
}
return pos;
}
private float[] getVillagerPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos) {
if (0 < zones.Count) {
int zone = (int) (rnd.frand() * zones.Count);
Result<FindRandomPointResult> result = navquery.findRandomPointWithinCircle(zones[zone].getRandomRef(),
zones[zone].getRandomPt(), zoneRadius[0], filter, rnd);
if (result.succeeded()) {
pos = result.result.getRandomPt();
}
}
return pos;
}
private void createZones() {
zones.Clear();
QueryFilter filter = new DefaultQueryFilter();
NavMeshQuery navquery = new NavMeshQuery(navMesh);
for (int i = 0; i < numberOfZones[0]; i++) {
float zoneSeparation = zoneRadius[0] * zoneRadius[0] * 16;
for (int k = 0; k < 100; k++) {
Result<FindRandomPointResult> result = navquery.findRandomPoint(filter, rnd);
if (result.succeeded()) {
bool valid = true;
foreach (FindRandomPointResult zone in zones) {
if (DemoMath.vDistSqr(zone.getRandomPt(), result.result.getRandomPt(), 0) < zoneSeparation) {
valid = false;
break;
}
}
if (valid) {
zones.Add(result.result);
break;
}
}
}
}
}
private void createCrowd() {
crowd = new Crowd(config, navMesh, __ => new DefaultQueryFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 10f, 1f, 1f, 2f, 1.5f }));
ObstacleAvoidanceQuery.ObstacleAvoidanceParams option = new ObstacleAvoidanceQuery.ObstacleAvoidanceParams(crowd.getObstacleAvoidanceParams(0));
// Low (11)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 1;
crowd.setObstacleAvoidanceParams(0, option);
// Medium (22)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 2;
crowd.setObstacleAvoidanceParams(1, option);
// Good (45)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 2;
option.adaptiveDepth = 3;
crowd.setObstacleAvoidanceParams(2, option);
// High (66)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 3;
option.adaptiveDepth = 3;
crowd.setObstacleAvoidanceParams(3, option);
}
public void update(float dt) {
long startTime = Stopwatch.GetTimestamp();
if (crowd != null) {
crowd.config().pathQueueSize = pathQueueSize[0];
crowd.config().maxFindPathIterations = maxIterations[0];
crowd.update(dt, null);
}
long endTime = Stopwatch.GetTimestamp();
if (crowd != null) {
NavMeshQuery navquery = new NavMeshQuery(navMesh);
QueryFilter filter = new DefaultQueryFilter();
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (needsNewTarget(ag)) {
AgentData agentData = (AgentData) ag.option.userData;
switch (agentData.type) {
case AgentType.MOB:
moveMob(navquery, filter, ag, agentData);
break;
case AgentType.VILLAGER:
moveVillager(navquery, filter, ag, agentData);
break;
case AgentType.TRAVELLER:
moveTraveller(navquery, filter, ag, agentData);
break;
}
}
}
}
crowdUpdateTime = (endTime - startTime) / 1_000_000;
}
private void moveMob(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
// Move somewhere
Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter);
if (nearestPoly.succeeded()) {
Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(),
agentData.home, zoneRadius[0] * 2f, filter, rnd);
if (result.succeeded()) {
crowd.requestMoveTarget(ag, result.result.getRandomRef(), result.result.getRandomPt());
}
}
}
private void moveVillager(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
// Move somewhere close
Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter);
if (nearestPoly.succeeded()) {
Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(),
agentData.home, zoneRadius[0] * 0.2f, filter, rnd);
if (result.succeeded()) {
crowd.requestMoveTarget(ag, result.result.getRandomRef(), result.result.getRandomPt());
}
}
}
private void moveTraveller(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
// Move to another zone
List<FindRandomPointResult> potentialTargets = new();
foreach (FindRandomPointResult zone in zones) {
if (DemoMath.vDistSqr(zone.getRandomPt(), ag.npos, 0) > zoneRadius[0] * zoneRadius[0]) {
potentialTargets.Add(zone);
}
}
if (0 < potentialTargets.Count) {
potentialTargets.Shuffle();
crowd.requestMoveTarget(ag, potentialTargets[0].getRandomRef(), potentialTargets[0].getRandomPt());
}
}
private bool needsNewTarget(CrowdAgent ag) {
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_NONE
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_FAILED) {
return true;
}
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_VALID) {
float dx = ag.targetPos[0] - ag.npos[0];
float dy = ag.targetPos[1] - ag.npos[1];
float dz = ag.targetPos[2] - ag.npos[2];
return dx * dx + dy * dy + dz * dz < 0.3f;
}
return false;
}
public void setup(float maxAgentRadius, NavMesh nav) {
navMesh = nav;
if (nav != null) {
config = new CrowdConfig(maxAgentRadius);
}
}
public void handleRender(NavMeshRenderer renderer) {
RecastDebugDraw dd = renderer.getDebugDraw();
dd.depthMask(false);
if (crowd != null) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
float radius = ag.option.radius;
float[] pos = ag.npos;
dd.debugDrawCircle(pos[0], pos[1], pos[2], radius, duRGBA(0, 0, 0, 32), 2.0f);
}
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
AgentData agentData = (AgentData) ag.option.userData;
float height = ag.option.height;
float radius = ag.option.radius;
float[] pos = ag.npos;
int col = duRGBA(220, 220, 220, 128);
if (agentData.type == AgentType.TRAVELLER) {
col = duRGBA(100, 160, 100, 128);
}
if (agentData.type == AgentType.VILLAGER) {
col = duRGBA(120, 80, 160, 128);
}
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
col = duLerpCol(col, duRGBA(255, 255, 32, 128), 128);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_PATH)
col = duLerpCol(col, duRGBA(255, 64, 32, 128), 128);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
col = duRGBA(255, 32, 16, 128);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
col = duLerpCol(col, duRGBA(64, 255, 0, 128), 128);
dd.debugDrawCylinder(pos[0] - radius, pos[1] + radius * 0.1f, pos[2] - radius, pos[0] + radius, pos[1] + height,
pos[2] + radius, col);
}
}
dd.depthMask(true);
}
private CrowdAgent addAgent(float[] p, AgentType type) {
CrowdAgentParams ap = agentParamsSupplier.Invoke();
ap.userData = new AgentData(type, p);
return crowd.addAgent(p, ap);
}
public enum AgentType {
VILLAGER, TRAVELLER, MOB,
}
private class AgentData {
public readonly AgentType type;
public readonly float[] home = new float[3];
public AgentData(AgentType type, float[] home) {
this.type = type;
RecastVectors.copy(this.home, home);
}
}
public void updateAgentParams(int updateFlags, int obstacleAvoidanceType, float separationWeight) {
if (crowd != null) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
CrowdAgentParams option = new CrowdAgentParams();
option.radius = ag.option.radius;
option.height = ag.option.height;
option.maxAcceleration = ag.option.maxAcceleration;
option.maxSpeed = ag.option.maxSpeed;
option.collisionQueryRange = ag.option.collisionQueryRange;
option.pathOptimizationRange = ag.option.pathOptimizationRange;
option.queryFilterType = ag.option.queryFilterType;
option.userData = ag.option.userData;
option.updateFlags = updateFlags;
option.obstacleAvoidanceType = obstacleAvoidanceType;
option.separationWeight = separationWeight;
crowd.updateAgentParameters(ag, option);
}
}
}
}

View File

@ -0,0 +1,735 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2021 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.Collections.Generic;
using System.Diagnostics;
using Silk.NET.Windowing;
using DotRecast.Detour;
using DotRecast.Detour.Crowd;
using DotRecast.Detour.Crowd.Tracking;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class CrowdTool : Tool {
private enum ToolMode {
CREATE, MOVE_TARGET, SELECT, TOGGLE_POLYS, PROFILING
}
private readonly CrowdToolParams toolParams = new CrowdToolParams();
private Sample sample;
private NavMesh m_nav;
private Crowd crowd;
private readonly CrowdProfilingTool profilingTool;
private readonly CrowdAgentDebugInfo m_agentDebug = new CrowdAgentDebugInfo();
private static readonly int AGENT_MAX_TRAIL = 64;
private class AgentTrail {
public float[] trail = new float[AGENT_MAX_TRAIL * 3];
public int htrail;
};
private readonly Dictionary<long, AgentTrail> m_trails = new();
private float[] m_targetPos;
private long m_targetRef;
private ToolMode m_mode = ToolMode.CREATE;
private long crowdUpdateTime;
public CrowdTool() {
m_agentDebug.vod = new ObstacleAvoidanceDebugData(2048);
profilingTool = new CrowdProfilingTool(getAgentParams);
}
public override void setSample(Sample psample) {
if (sample != psample) {
sample = psample;
}
NavMesh nav = sample.getNavMesh();
if (nav != null && m_nav != nav) {
m_nav = nav;
CrowdConfig config = new CrowdConfig(sample.getSettingsUI().getAgentRadius());
crowd = new Crowd(config, nav, __ => new DefaultQueryFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 10f, 1f, 1f, 2f, 1.5f }));
// Setup local avoidance option to different qualities.
// Use mostly default settings, copy from dtCrowd.
ObstacleAvoidanceQuery.ObstacleAvoidanceParams option = new ObstacleAvoidanceQuery.ObstacleAvoidanceParams(crowd.getObstacleAvoidanceParams(0));
// Low (11)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 1;
crowd.setObstacleAvoidanceParams(0, option);
// Medium (22)
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 2;
crowd.setObstacleAvoidanceParams(1, option);
// Good (45)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 2;
option.adaptiveDepth = 3;
crowd.setObstacleAvoidanceParams(2, option);
// High (66)
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 3;
option.adaptiveDepth = 3;
crowd.setObstacleAvoidanceParams(3, option);
profilingTool.setup(sample.getSettingsUI().getAgentRadius(), m_nav);
}
}
public override void handleClick(float[] s, float[] p, bool shift) {
if (m_mode == ToolMode.PROFILING) {
return;
}
if (crowd == null) {
return;
}
if (m_mode == ToolMode.CREATE) {
if (shift) {
// Delete
CrowdAgent ahit = hitTestAgents(s, p);
if (ahit != null) {
removeAgent(ahit);
}
} else {
// Add
addAgent(p);
}
} else if (m_mode == ToolMode.MOVE_TARGET) {
setMoveTarget(p, shift);
} else if (m_mode == ToolMode.SELECT) {
// Highlight
CrowdAgent ahit = hitTestAgents(s, p);
hilightAgent(ahit);
} else if (m_mode == ToolMode.TOGGLE_POLYS) {
NavMesh nav = sample.getNavMesh();
NavMeshQuery navquery = sample.getNavMeshQuery();
if (nav != null && navquery != null) {
QueryFilter filter = new DefaultQueryFilter();
float[] halfExtents = crowd.getQueryExtents();
Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter);
long refs = result.result.getNearestRef();
if (refs != 0) {
Result<int> flags = nav.getPolyFlags(refs);
if (flags.succeeded()) {
nav.setPolyFlags(refs, flags.result ^ SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED);
}
}
}
}
}
private void removeAgent(CrowdAgent agent) {
crowd.removeAgent(agent);
if (agent == m_agentDebug.agent) {
m_agentDebug.agent = null;
}
}
private void addAgent(float[] p) {
CrowdAgentParams ap = getAgentParams();
CrowdAgent ag = crowd.addAgent(p, ap);
if (ag != null) {
if (m_targetRef != 0)
crowd.requestMoveTarget(ag, m_targetRef, m_targetPos);
// Init trail
if (!m_trails.TryGetValue(ag.idx, out var trail))
{
trail = new AgentTrail();
m_trails.Add(ag.idx, trail);
}
for (int i = 0; i < AGENT_MAX_TRAIL; ++i) {
trail.trail[i * 3] = p[0];
trail.trail[i * 3 + 1] = p[1];
trail.trail[i * 3 + 2] = p[2];
}
trail.htrail = 0;
}
}
private CrowdAgentParams getAgentParams() {
CrowdAgentParams ap = new CrowdAgentParams();
ap.radius = sample.getSettingsUI().getAgentRadius();
ap.height = sample.getSettingsUI().getAgentHeight();
ap.maxAcceleration = 8.0f;
ap.maxSpeed = 3.5f;
ap.collisionQueryRange = ap.radius * 12.0f;
ap.pathOptimizationRange = ap.radius * 30.0f;
ap.updateFlags = getUpdateFlags();
ap.obstacleAvoidanceType = toolParams.m_obstacleAvoidanceType[0];
ap.separationWeight = toolParams.m_separationWeight[0];
return ap;
}
private CrowdAgent hitTestAgents(float[] s, float[] p) {
CrowdAgent isel = null;
float tsel = float.MaxValue;
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
float[] bmin = new float[3], bmax = new float[3];
getAgentBounds(ag, bmin, bmax);
float[] isect = Intersections.intersectSegmentAABB(s, p, bmin, bmax);
if (null != isect) {
float tmin = isect[0];
if (tmin > 0 && tmin < tsel) {
isel = ag;
tsel = tmin;
}
}
}
return isel;
}
private void getAgentBounds(CrowdAgent ag, float[] bmin, float[] bmax) {
float[] p = ag.npos;
float r = ag.option.radius;
float h = ag.option.height;
bmin[0] = p[0] - r;
bmin[1] = p[1];
bmin[2] = p[2] - r;
bmax[0] = p[0] + r;
bmax[1] = p[1] + h;
bmax[2] = p[2] + r;
}
private void setMoveTarget(float[] p, bool adjust) {
if (sample == null || crowd == null)
return;
// Find nearest point on navmesh and set move request to that location.
NavMeshQuery navquery = sample.getNavMeshQuery();
QueryFilter filter = crowd.getFilter(0);
float[] halfExtents = crowd.getQueryExtents();
if (adjust) {
// Request velocity
if (m_agentDebug.agent != null) {
float[] vel = calcVel(m_agentDebug.agent.npos, p, m_agentDebug.agent.option.maxSpeed);
crowd.requestMoveVelocity(m_agentDebug.agent, vel);
} else {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
float[] vel = calcVel(ag.npos, p, ag.option.maxSpeed);
crowd.requestMoveVelocity(ag, vel);
}
}
} else {
Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter);
m_targetRef = result.result.getNearestRef();
m_targetPos = result.result.getNearestPos();
if (m_agentDebug.agent != null) {
crowd.requestMoveTarget(m_agentDebug.agent, m_targetRef, m_targetPos);
} else {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
crowd.requestMoveTarget(ag, m_targetRef, m_targetPos);
}
}
}
}
private float[] calcVel(float[] pos, float[] tgt, float speed) {
float[] vel = DetourCommon.vSub(tgt, pos);
vel[1] = 0.0f;
DetourCommon.vNormalize(vel);
return DetourCommon.vScale(vel, speed);
}
public override void handleRender(NavMeshRenderer renderer) {
if (m_mode == ToolMode.PROFILING) {
profilingTool.handleRender(renderer);
return;
}
RecastDebugDraw dd = renderer.getDebugDraw();
float rad = sample.getSettingsUI().getAgentRadius();
NavMesh nav = sample.getNavMesh();
if (nav == null || crowd == null)
return;
if (toolParams.m_showNodes && crowd.getPathQueue() != null) {
// NavMeshQuery navquery = crowd.getPathQueue().getNavQuery();
// if (navquery != null) {
// dd.debugDrawNavMeshNodes(navquery);
// }
}
dd.depthMask(false);
// Draw paths
if (toolParams.m_showPath) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (!toolParams.m_showDetailAll && ag != m_agentDebug.agent)
continue;
List<long> path = ag.corridor.getPath();
int npath = ag.corridor.getPathCount();
for (int j = 0; j < npath; ++j) {
dd.debugDrawNavMeshPoly(nav, path[j], duRGBA(255, 255, 255, 24));
}
}
}
if (m_targetRef != 0)
dd.debugDrawCross(m_targetPos[0], m_targetPos[1] + 0.1f, m_targetPos[2], rad, duRGBA(255, 255, 255, 192), 2.0f);
// Occupancy grid.
if (toolParams.m_showGrid) {
float gridy = -float.MaxValue;
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
float[] pos = ag.corridor.getPos();
gridy = Math.Max(gridy, pos[1]);
}
gridy += 1.0f;
dd.begin(QUADS);
ProximityGrid grid = crowd.getGrid();
float cs = grid.getCellSize();
foreach (int[] ic in grid.getItemCounts()) {
int x = ic[0];
int y = ic[1];
int count = ic[2];
if (count != 0) {
int col = duRGBA(128, 0, 0, Math.Min(count * 40, 255));
dd.vertex(x * cs, gridy, y * cs, col);
dd.vertex(x * cs, gridy, y * cs + cs, col);
dd.vertex(x * cs + cs, gridy, y * cs + cs, col);
dd.vertex(x * cs + cs, gridy, y * cs, col);
}
}
dd.end();
}
// Trail
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
AgentTrail trail = m_trails[ag.idx];
float[] pos = ag.npos;
dd.begin(LINES, 3.0f);
float[] prev = new float[3];
float preva = 1;
DetourCommon.vCopy(prev, pos);
for (int j = 0; j < AGENT_MAX_TRAIL - 1; ++j) {
int idx = (trail.htrail + AGENT_MAX_TRAIL - j) % AGENT_MAX_TRAIL;
int v = idx * 3;
float a = 1 - j / (float) AGENT_MAX_TRAIL;
dd.vertex(prev[0], prev[1] + 0.1f, prev[2], 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)));
preva = a;
DetourCommon.vCopy(prev, trail.trail, v);
}
dd.end();
}
// Corners & co
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent)
continue;
float radius = ag.option.radius;
float[] pos = ag.npos;
if (toolParams.m_showCorners) {
if (0 < ag.corners.Count) {
dd.begin(LINES, 2.0f);
for (int j = 0; j < ag.corners.Count; ++j) {
float[] va = j == 0 ? pos : ag.corners[j - 1].getPos();
float[] vb = ag.corners[j].getPos();
dd.vertex(va[0], va[1] + radius, va[2], duRGBA(128, 0, 0, 192));
dd.vertex(vb[0], vb[1] + radius, vb[2], duRGBA(128, 0, 0, 192));
}
if ((ag.corners[ag.corners.Count - 1].getFlags()
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
float[] v = ag.corners[ag.corners.Count - 1].getPos();
dd.vertex(v[0], v[1], v[2], duRGBA(192, 0, 0, 192));
dd.vertex(v[0], v[1] + radius * 2, v[2], duRGBA(192, 0, 0, 192));
}
dd.end();
if (toolParams.m_anticipateTurns) {
/* float dvel[3], pos[3];
calcSmoothSteerDirection(ag.pos, ag.cornerVerts, ag.ncorners, dvel);
pos[0] = ag.pos[0] + dvel[0];
pos[1] = ag.pos[1] + dvel[1];
pos[2] = ag.pos[2] + dvel[2];
float off = ag.radius+0.1f;
float[] tgt = &ag.cornerVerts[0];
float y = ag.pos[1]+off;
dd.begin(DU_DRAW_LINES, 2.0f);
dd.vertex(ag.pos[0],y,ag.pos[2], duRGBA(255,0,0,192));
dd.vertex(pos[0],y,pos[2], duRGBA(255,0,0,192));
dd.vertex(pos[0],y,pos[2], duRGBA(255,0,0,192));
dd.vertex(tgt[0],y,tgt[2], duRGBA(255,0,0,192));
dd.end();*/
}
}
}
if (toolParams.m_showCollisionSegments) {
float[] center = ag.boundary.getCenter();
dd.debugDrawCross(center[0], center[1] + radius, center[2], 0.2f, duRGBA(192, 0, 128, 255), 2.0f);
dd.debugDrawCircle(center[0], center[1] + radius, center[2], ag.option.collisionQueryRange,
duRGBA(192, 0, 128, 128), 2.0f);
dd.begin(LINES, 3.0f);
for (int j = 0; j < ag.boundary.getSegmentCount(); ++j) {
int col = duRGBA(192, 0, 128, 192);
float[] s = ag.boundary.getSegment(j);
float[] s0 = new float[] { s[0], s[1], s[2] };
float[] s3 = new float[] { s[3], s[4], s[5] };
if (DetourCommon.triArea2D(pos, s0, s3) < 0.0f)
col = duDarkenCol(col);
dd.appendArrow(s[0], s[1] + 0.2f, s[2], s[3], s[4] + 0.2f, s[5], 0.0f, 0.3f, col);
}
dd.end();
}
if (toolParams.m_showNeis) {
dd.debugDrawCircle(pos[0], pos[1] + radius, pos[2], ag.option.collisionQueryRange, duRGBA(0, 192, 128, 128),
2.0f);
dd.begin(LINES, 2.0f);
for (int j = 0; j < ag.neis.Count; ++j) {
CrowdAgent nei = ag.neis[j].agent;
if (nei != null) {
dd.vertex(pos[0], pos[1] + radius, pos[2], duRGBA(0, 192, 128, 128));
dd.vertex(nei.npos[0], nei.npos[1] + radius, nei.npos[2], duRGBA(0, 192, 128, 128));
}
}
dd.end();
}
if (toolParams.m_showOpt) {
dd.begin(LINES, 2.0f);
dd.vertex(m_agentDebug.optStart[0], m_agentDebug.optStart[1] + 0.3f, m_agentDebug.optStart[2],
duRGBA(0, 128, 0, 192));
dd.vertex(m_agentDebug.optEnd[0], m_agentDebug.optEnd[1] + 0.3f, m_agentDebug.optEnd[2], duRGBA(0, 128, 0, 192));
dd.end();
}
}
// Agent cylinders.
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
float radius = ag.option.radius;
float[] pos = ag.npos;
int col = duRGBA(0, 0, 0, 32);
if (m_agentDebug.agent == ag)
col = duRGBA(255, 0, 0, 128);
dd.debugDrawCircle(pos[0], pos[1], pos[2], radius, col, 2.0f);
}
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
float height = ag.option.height;
float radius = ag.option.radius;
float[] pos = ag.npos;
int col = duRGBA(220, 220, 220, 128);
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
col = duLerpCol(col, duRGBA(128, 0, 255, 128), 32);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_PATH)
col = duLerpCol(col, duRGBA(128, 0, 255, 128), 128);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
col = duRGBA(255, 32, 16, 128);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
col = duLerpCol(col, duRGBA(64, 255, 0, 128), 128);
dd.debugDrawCylinder(pos[0] - radius, pos[1] + radius * 0.1f, pos[2] - radius, pos[0] + radius, pos[1] + height,
pos[2] + radius, col);
}
if (toolParams.m_showVO) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent)
continue;
// Draw detail about agent sela
ObstacleAvoidanceDebugData vod = m_agentDebug.vod;
float dx = ag.npos[0];
float dy = ag.npos[1] + ag.option.height;
float dz = ag.npos[2];
dd.debugDrawCircle(dx, dy, dz, ag.option.maxSpeed, duRGBA(255, 255, 255, 64), 2.0f);
dd.begin(QUADS);
for (int j = 0; j < vod.getSampleCount(); ++j) {
float[] p = vod.getSampleVelocity(j);
float sr = vod.getSampleSize(j);
float pen = vod.getSamplePenalty(j);
float pen2 = vod.getSamplePreferredSidePenalty(j);
int col = duLerpCol(duRGBA(255, 255, 255, 220), duRGBA(128, 96, 0, 220), (int) (pen * 255));
col = duLerpCol(col, duRGBA(128, 0, 0, 220), (int) (pen2 * 128));
dd.vertex(dx + p[0] - sr, dy, dz + p[2] - sr, col);
dd.vertex(dx + p[0] - sr, dy, dz + p[2] + sr, col);
dd.vertex(dx + p[0] + sr, dy, dz + p[2] + sr, col);
dd.vertex(dx + p[0] + sr, dy, dz + p[2] - sr, col);
}
dd.end();
}
}
// Velocity stuff.
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
float radius = ag.option.radius;
float height = ag.option.height;
float[] pos = ag.npos;
float[] vel = ag.vel;
float[] dvel = ag.dvel;
int col = duRGBA(220, 220, 220, 192);
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
col = duLerpCol(col, duRGBA(128, 0, 255, 192), 48);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_PATH)
col = duLerpCol(col, duRGBA(128, 0, 255, 192), 128);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
col = duRGBA(255, 32, 16, 192);
else if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
col = duLerpCol(col, duRGBA(64, 255, 0, 192), 128);
dd.debugDrawCircle(pos[0], pos[1] + height, pos[2], radius, col, 2.0f);
dd.debugDrawArrow(pos[0], pos[1] + height, pos[2], pos[0] + dvel[0], pos[1] + height + dvel[1], pos[2] + dvel[2],
0.0f, 0.4f, duRGBA(0, 192, 255, 192), m_agentDebug.agent == ag ? 2.0f : 1.0f);
dd.debugDrawArrow(pos[0], pos[1] + height, pos[2], pos[0] + vel[0], pos[1] + height + vel[1], pos[2] + vel[2], 0.0f,
0.4f, duRGBA(0, 0, 0, 160), 2.0f);
}
dd.depthMask(true);
}
public override void handleUpdate(float dt) {
updateTick(dt);
}
private void updateTick(float dt) {
if (m_mode == ToolMode.PROFILING) {
profilingTool.update(dt);
return;
}
if (crowd == null)
return;
NavMesh nav = sample.getNavMesh();
if (nav == null)
return;
long startTime = Stopwatch.GetTimestamp();
crowd.update(dt, m_agentDebug);
long endTime = Stopwatch.GetTimestamp();
// Update agent trails
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
AgentTrail trail = m_trails[ag.idx];
// Update agent movement trail.
trail.htrail = (trail.htrail + 1) % AGENT_MAX_TRAIL;
trail.trail[trail.htrail * 3] = ag.npos[0];
trail.trail[trail.htrail * 3 + 1] = ag.npos[1];
trail.trail[trail.htrail * 3 + 2] = ag.npos[2];
}
m_agentDebug.vod.normalizeSamples();
// m_crowdSampleCount.addSample((float) crowd.getVelocitySampleCount());
crowdUpdateTime = (endTime - startTime) / 1_000_000;
}
private void hilightAgent(CrowdAgent agent) {
m_agentDebug.agent = agent;
}
public override void layout(IWindow ctx) {
// ToolMode previousToolMode = m_mode;
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Create Agents", m_mode == ToolMode.CREATE)) {
// m_mode = ToolMode.CREATE;
// }
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Move Target", m_mode == ToolMode.MOVE_TARGET)) {
// m_mode = ToolMode.MOVE_TARGET;
// }
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Select Agent", m_mode == ToolMode.SELECT)) {
// m_mode = ToolMode.SELECT;
// }
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Toggle Polys", m_mode == ToolMode.TOGGLE_POLYS)) {
// m_mode = ToolMode.TOGGLE_POLYS;
// }
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Profiling", m_mode == ToolMode.PROFILING)) {
// m_mode = ToolMode.PROFILING;
// }
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// if (nk_tree_state_push(ctx, 0, "Options", toolParams.m_expandOptions)) {
// bool m_optimizeVis = toolParams.m_optimizeVis;
// bool m_optimizeTopo = toolParams.m_optimizeTopo;
// bool m_anticipateTurns = toolParams.m_anticipateTurns;
// bool m_obstacleAvoidance = toolParams.m_obstacleAvoidance;
// bool m_separation = toolParams.m_separation;
// int m_obstacleAvoidanceType = toolParams.m_obstacleAvoidanceType[0];
// float m_separationWeight = toolParams.m_separationWeight[0];
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_optimizeVis = nk_option_text(ctx, "Optimize Visibility", toolParams.m_optimizeVis);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_optimizeTopo = nk_option_text(ctx, "Optimize Topology", toolParams.m_optimizeTopo);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_anticipateTurns = nk_option_text(ctx, "Anticipate Turns", toolParams.m_anticipateTurns);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_obstacleAvoidance = nk_option_text(ctx, "Obstacle Avoidance", toolParams.m_obstacleAvoidance);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Avoidance Quality", 0, toolParams.m_obstacleAvoidanceType, 3, 1, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_separation = nk_option_text(ctx, "Separation", toolParams.m_separation);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Separation Weight", 0f, toolParams.m_separationWeight, 20f, 0.01f, 0.01f);
// if (m_optimizeVis != toolParams.m_optimizeVis || m_optimizeTopo != toolParams.m_optimizeTopo
// || m_anticipateTurns != toolParams.m_anticipateTurns || m_obstacleAvoidance != toolParams.m_obstacleAvoidance
// || m_separation != toolParams.m_separation
// || m_obstacleAvoidanceType != toolParams.m_obstacleAvoidanceType[0]
// || m_separationWeight != toolParams.m_separationWeight[0]) {
// updateAgentParams();
// }
// nk_tree_state_pop(ctx);
// }
// if (m_mode == ToolMode.PROFILING) {
// profilingTool.layout(ctx);
// }
// if (m_mode != ToolMode.PROFILING) {
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// if (nk_tree_state_push(ctx, 0, "Selected Debug Draw", toolParams.m_expandSelectedDebugDraw)) {
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_showCorners = nk_option_text(ctx, "Show Corners", toolParams.m_showCorners);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_showCollisionSegments = nk_option_text(ctx, "Show Collision Segs", toolParams.m_showCollisionSegments);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_showPath = nk_option_text(ctx, "Show Path", toolParams.m_showPath);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_showVO = nk_option_text(ctx, "Show VO", toolParams.m_showVO);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_showOpt = nk_option_text(ctx, "Show Path Optimization", toolParams.m_showOpt);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_showNeis = nk_option_text(ctx, "Show Neighbours", toolParams.m_showNeis);
// nk_tree_state_pop(ctx);
// }
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// if (nk_tree_state_push(ctx, 0, "Debug Draw", toolParams.m_expandDebugDraw)) {
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_showGrid = nk_option_text(ctx, "Show Prox Grid", toolParams.m_showGrid);
// nk_layout_row_dynamic(ctx, 20, 1);
// toolParams.m_showNodes = nk_option_text(ctx, "Show Nodes", toolParams.m_showNodes);
// nk_tree_state_pop(ctx);
// }
// nk_layout_row_dynamic(ctx, 2, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Update Time: %d ms", crowdUpdateTime), NK_TEXT_ALIGN_LEFT);
// }
}
private void updateAgentParams() {
if (crowd == null) {
return;
}
int updateFlags = getUpdateFlags();
profilingTool.updateAgentParams(updateFlags, toolParams.m_obstacleAvoidanceType[0], toolParams.m_separationWeight[0]);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
CrowdAgentParams option = new CrowdAgentParams();
option.radius = ag.option.radius;
option.height = ag.option.height;
option.maxAcceleration = ag.option.maxAcceleration;
option.maxSpeed = ag.option.maxSpeed;
option.collisionQueryRange = ag.option.collisionQueryRange;
option.pathOptimizationRange = ag.option.pathOptimizationRange;
option.obstacleAvoidanceType = ag.option.obstacleAvoidanceType;
option.queryFilterType = ag.option.queryFilterType;
option.userData = ag.option.userData;
option.updateFlags = updateFlags;
option.obstacleAvoidanceType = toolParams.m_obstacleAvoidanceType[0];
option.separationWeight = toolParams.m_separationWeight[0];
crowd.updateAgentParameters(ag, option);
}
}
private int getUpdateFlags() {
int updateFlags = 0;
if (toolParams.m_anticipateTurns) {
updateFlags |= CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS;
}
if (toolParams.m_optimizeVis) {
updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS;
}
if (toolParams.m_optimizeTopo) {
updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
}
if (toolParams.m_obstacleAvoidance) {
updateFlags |= CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
}
if (toolParams.m_separation) {
updateFlags |= CrowdAgentParams.DT_CROWD_SEPARATION;
}
return updateFlags;
}
public override string getName() {
return "Crowd";
}
}

View File

@ -0,0 +1,46 @@
/*
recast4j copyright (c) 2020-2021 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.
*/
namespace DotRecast.Recast.Demo.Tools;
public class CrowdToolParams {
public readonly int[] m_expandSelectedDebugDraw = new[] { 1 };
public bool m_showCorners;
public bool m_showCollisionSegments;
public bool m_showPath;
public bool m_showVO;
public bool m_showOpt;
public bool m_showNeis;
public readonly int[] m_expandDebugDraw = new[] { 0 };
public bool m_showLabels;
public bool m_showGrid;
public bool m_showNodes;
public bool m_showPerfGraph;
public bool m_showDetailAll;
public readonly int[] m_expandOptions = new[] { 1 };
public bool m_anticipateTurns = true;
public bool m_optimizeVis = true;
public bool m_optimizeTopo = true;
public bool m_obstacleAvoidance = true;
public readonly int[] m_obstacleAvoidanceType = new[] { 3 };
public bool m_separation;
public readonly float[] m_separationWeight = new[] { 2f };
}

View File

@ -0,0 +1,14 @@
using System;
using System.IO;
using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Tools;
public static class DemoObjImporter
{
public static DemoInputGeomProvider load(byte[] chunk) {
var context = ObjImporter.loadContext(chunk);
return new DemoInputGeomProvider(context.vertexPositions, context.meshFaces);
}
}

View File

@ -0,0 +1,675 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using DotRecast.Core;
using DotRecast.Detour.Dynamic;
using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using DotRecast.Recast.Demo.Tools.Gizmos;
using DotRecast.Recast.Demo.UI;
using Silk.NET.Windowing;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Recast.Demo.Tools;
public class DynamicUpdateTool : Tool {
private enum ToolMode {
BUILD, COLLIDERS, RAYCAST
}
private enum ColliderShape {
SPHERE, CAPSULE, BOX, CYLINDER, COMPOSITE, CONVEX, TRIMESH_BRIDGE, TRIMESH_HOUSE
}
private Sample sample;
private ToolMode mode = ToolMode.BUILD;
private readonly float[] cellSize = new[] { 0.3f };
private PartitionType partitioning = PartitionType.WATERSHED;
private bool filterLowHangingObstacles = true;
private bool filterLedgeSpans = true;
private bool filterWalkableLowHeightSpans = true;
private readonly float[] walkableHeight = new[] { 2f };
private readonly float[] walkableRadius = new[] { 0.6f };
private readonly float[] walkableClimb = new[] { 0.9f };
private readonly float[] walkableSlopeAngle = new[] { 45f };
private readonly float[] minRegionArea = new[] { 6f };
private readonly float[] regionMergeSize = new[] { 36f };
private readonly float[] maxEdgeLen = new[] { 12f };
private readonly float[] maxSimplificationError = new[] { 1.3f };
private readonly int[] vertsPerPoly = new[] { 6 };
private bool buildDetailMesh = true;
private bool compression = true;
private readonly float[] detailSampleDist = new[] { 6f };
private readonly float[] detailSampleMaxError = new[] { 1f };
private bool showColliders = false;
private long buildTime;
private long raycastTime;
private ColliderShape colliderShape = ColliderShape.SPHERE;
private DynamicNavMesh dynaMesh;
private readonly TaskFactory executor;
private readonly Dictionary<long, Collider> colliders = new();
private readonly Dictionary<long, ColliderGizmo> colliderGizmos = new();
private readonly Random random = Random.Shared;
private readonly DemoInputGeomProvider bridgeGeom;
private readonly DemoInputGeomProvider houseGeom;
private readonly DemoInputGeomProvider convexGeom;
private bool sposSet;
private bool eposSet;
private float[] spos;
private float[] epos;
private bool raycastHit;
private float[] raycastHitPos;
public DynamicUpdateTool()
{
executor = Task.Factory;
bridgeGeom = DemoObjImporter.load(Loader.ToBytes("bridge.obj"));
houseGeom = DemoObjImporter.load(Loader.ToBytes("house.obj"));
convexGeom = DemoObjImporter.load(Loader.ToBytes("convex.obj"));
}
public override void setSample(Sample sample) {
this.sample = sample;
}
public override void handleClick(float[] s, float[] p, bool shift) {
if (mode == ToolMode.COLLIDERS) {
if (!shift) {
Tuple<Collider, ColliderGizmo> colliderWithGizmo = null;
if (dynaMesh != null) {
if (colliderShape == ColliderShape.SPHERE) {
colliderWithGizmo = sphereCollider(p);
} else if (colliderShape == ColliderShape.CAPSULE) {
colliderWithGizmo = capsuleCollider(p);
} else if (colliderShape == ColliderShape.BOX) {
colliderWithGizmo = boxCollider(p);
} else if (colliderShape == ColliderShape.CYLINDER) {
colliderWithGizmo = cylinderCollider(p);
} else if (colliderShape == ColliderShape.COMPOSITE) {
colliderWithGizmo = compositeCollider(p);
} else if (colliderShape == ColliderShape.TRIMESH_BRIDGE) {
colliderWithGizmo = trimeshBridge(p);
} else if (colliderShape == ColliderShape.TRIMESH_HOUSE) {
colliderWithGizmo = trimeshHouse(p);
} else if (colliderShape == ColliderShape.CONVEX) {
colliderWithGizmo = convexTrimesh(p);
}
}
if (colliderWithGizmo != null) {
long id = dynaMesh.addCollider(colliderWithGizmo.Item1);
colliders.Add(id, colliderWithGizmo.Item1);
colliderGizmos.Add(id, colliderWithGizmo.Item2);
}
}
}
if (mode == ToolMode.RAYCAST) {
if (shift) {
sposSet = true;
spos = ArrayUtils.CopyOf(p, p.Length);
} else {
eposSet = true;
epos = ArrayUtils.CopyOf(p, p.Length);
}
if (sposSet && eposSet && dynaMesh != null) {
float[] sp = { spos[0], spos[1] + 1.3f, spos[2] };
float[] ep = { epos[0], epos[1] + 1.3f, epos[2] };
long t1 = Stopwatch.GetTimestamp();
float? hitPos = dynaMesh.voxelQuery().raycast(sp, ep);
long t2 = Stopwatch.GetTimestamp();
raycastTime = (t2 - t1) / 1_000_000L;
raycastHit = hitPos.HasValue;
raycastHitPos = hitPos.HasValue
? new float[] { sp[0] + hitPos.Value * (ep[0] - sp[0]), sp[1] + hitPos.Value * (ep[1] - sp[1]), sp[2] + hitPos.Value * (ep[2] - sp[2]) }
: ep;
}
}
}
private Tuple<Collider, ColliderGizmo> sphereCollider(float[] p) {
float radius = 1 + (float)random.NextDouble() * 10;
return Tuple.Create<Collider, ColliderGizmo>(
new SphereCollider(p, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb),
GizmoFactory.sphere(p, radius));
}
private Tuple<Collider, ColliderGizmo> capsuleCollider(float[] p) {
float radius = 0.4f + (float)random.NextDouble() * 4f;
float[] a = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) };
vNormalize(a);
float len = 1f + (float)random.NextDouble() * 20f;
a[0] *= len;
a[1] *= len;
a[2] *= len;
float[] start = new float[] { p[0], p[1], p[2] };
float[] end = new float[] { p[0] + a[0], p[1] + a[1], p[2] + a[2] };
return Tuple.Create<Collider, ColliderGizmo>(new CapsuleCollider(start, end, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER,
dynaMesh.config.walkableClimb), GizmoFactory.capsule(start, end, radius));
}
private Tuple<Collider, ColliderGizmo> boxCollider(float[] p) {
float[] extent = new float[] { 0.5f + (float)random.NextDouble() * 6f, 0.5f + (float)random.NextDouble() * 6f,
0.5f + (float)random.NextDouble() * 6f };
float[] forward = new float[] { (1f - 2 * (float)random.NextDouble()), 0, (1f - 2 * (float)random.NextDouble()) };
float[] up = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) };
float[][] halfEdges = BoxCollider.getHalfEdges(up, forward, extent);
return Tuple.Create<Collider, ColliderGizmo>(
new BoxCollider(p, halfEdges, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb),
GizmoFactory.box(p, halfEdges));
}
private Tuple<Collider, ColliderGizmo> cylinderCollider(float[] p) {
float radius = 0.7f + (float)random.NextDouble() * 4f;
float[] a = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) };
vNormalize(a);
float len = 2f + (float)random.NextDouble() * 20f;
a[0] *= len;
a[1] *= len;
a[2] *= len;
float[] start = new float[] { p[0], p[1], p[2] };
float[] end = new float[] { p[0] + a[0], p[1] + a[1], p[2] + a[2] };
return Tuple.Create<Collider, ColliderGizmo>(new CylinderCollider(start, end, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER,
dynaMesh.config.walkableClimb), GizmoFactory.cylinder(start, end, radius));
}
private Tuple<Collider, ColliderGizmo> compositeCollider(float[] p) {
float[] baseExtent = new float[] { 5, 3, 8 };
float[] baseCenter = new float[] { p[0], p[1] + 3, p[2] };
float[] baseUp = new float[] { 0, 1, 0 };
float[] forward = new float[] { (1f - 2 * (float)random.NextDouble()), 0, (1f - 2 * (float)random.NextDouble()) };
vNormalize(forward);
float[] side = DemoMath.vCross(forward, baseUp);
BoxCollider @base = new BoxCollider(baseCenter, BoxCollider.getHalfEdges(baseUp, forward, baseExtent),
SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb);
float[] roofExtent = new float[] { 4.5f, 4.5f, 8f };
float[] rx = GLU.build_4x4_rotation_matrix(45, forward[0], forward[1], forward[2]);
float[] roofUp = mulMatrixVector(new float[3], rx, baseUp);
float[] roofCenter = new float[] { p[0], p[1] + 6, p[2] };
BoxCollider roof = new BoxCollider(roofCenter, BoxCollider.getHalfEdges(roofUp, forward, roofExtent),
SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb);
float[] trunkStart = new float[] { baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1],
baseCenter[2] - forward[2] * 15 + side[2] * 6 };
float[] trunkEnd = new float[] { trunkStart[0], trunkStart[1] + 10, trunkStart[2] };
CapsuleCollider trunk = new CapsuleCollider(trunkStart, trunkEnd, 0.5f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD,
dynaMesh.config.walkableClimb);
float[] crownCenter = new float[] { baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1] + 10,
baseCenter[2] - forward[2] * 15 + side[2] * 6 };
SphereCollider crown = new SphereCollider(crownCenter, 4f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS,
dynaMesh.config.walkableClimb);
CompositeCollider collider = new CompositeCollider(@base, roof, trunk, crown);
ColliderGizmo baseGizmo = GizmoFactory.box(baseCenter, BoxCollider.getHalfEdges(baseUp, forward, baseExtent));
ColliderGizmo roofGizmo = GizmoFactory.box(roofCenter, BoxCollider.getHalfEdges(roofUp, forward, roofExtent));
ColliderGizmo trunkGizmo = GizmoFactory.capsule(trunkStart, trunkEnd, 0.5f);
ColliderGizmo crownGizmo = GizmoFactory.sphere(crownCenter, 4f);
ColliderGizmo gizmo = GizmoFactory.composite(baseGizmo, roofGizmo, trunkGizmo, crownGizmo);
return Tuple.Create<Collider, ColliderGizmo>(collider, gizmo);
}
private Tuple<Collider, ColliderGizmo> trimeshBridge(float[] p) {
return trimeshCollider(p, bridgeGeom);
}
private Tuple<Collider, ColliderGizmo> trimeshHouse(float[] p) {
return trimeshCollider(p, houseGeom);
}
private Tuple<Collider, ColliderGizmo> convexTrimesh(float[] p) {
float[] verts = transformVertices(p, convexGeom, 360);
ConvexTrimeshCollider collider = new ConvexTrimeshCollider(verts, convexGeom.faces,
SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb * 10);
return Tuple.Create<Collider, ColliderGizmo>(collider, GizmoFactory.trimesh(verts, convexGeom.faces));
}
private Tuple<Collider, ColliderGizmo> trimeshCollider(float[] p, DemoInputGeomProvider geom) {
float[] verts = transformVertices(p, geom, 0);
TrimeshCollider collider = new TrimeshCollider(verts, geom.faces, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD,
dynaMesh.config.walkableClimb * 10);
return Tuple.Create<Collider, ColliderGizmo>(collider, GizmoFactory.trimesh(verts, geom.faces));
}
private float[] transformVertices(float[] p, DemoInputGeomProvider geom, float ax) {
float[] rx = GLU.build_4x4_rotation_matrix((float)random.NextDouble() * ax, 1, 0, 0);
float[] ry = GLU.build_4x4_rotation_matrix((float)random.NextDouble() * 360, 0, 1, 0);
float[] m = GLU.mul(rx, ry);
float[] verts = new float[geom.vertices.Length];
float[] v = new float[3];
float[] vr = new float[3];
for (int i = 0; i < geom.vertices.Length; i += 3) {
v[0] = geom.vertices[i];
v[1] = geom.vertices[i + 1];
v[2] = geom.vertices[i + 2];
mulMatrixVector(vr, m, v);
vr[0] += p[0];
vr[1] += p[1] - 0.1f;
vr[2] += p[2];
verts[i] = vr[0];
verts[i + 1] = vr[1];
verts[i + 2] = vr[2];
}
return verts;
}
private float[] mulMatrixVector(float[] resultvector, float[] matrix, float[] pvector) {
resultvector[0] = matrix[0] * pvector[0] + matrix[4] * pvector[1] + matrix[8] * pvector[2];
resultvector[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2];
resultvector[2] = matrix[2] * pvector[0] + matrix[6] * pvector[1] + matrix[10] * pvector[2];
return resultvector;
}
public override void handleClickRay(float[] start, float[] dir, bool shift) {
if (mode == ToolMode.COLLIDERS) {
if (shift) {
foreach (var e in colliders) {
if (hit(start, dir, e.Value.bounds())) {
dynaMesh.removeCollider(e.Key);
colliders.Remove(e.Key);
colliderGizmos.Remove(e.Key);
break;
}
}
}
}
}
private bool hit(float[] point, float[] dir, float[] bounds) {
float cx = 0.5f * (bounds[0] + bounds[3]);
float cy = 0.5f * (bounds[1] + bounds[4]);
float cz = 0.5f * (bounds[2] + bounds[5]);
float dx = 0.5f * (bounds[3] - bounds[0]);
float dy = 0.5f * (bounds[4] - bounds[1]);
float dz = 0.5f * (bounds[5] - bounds[2]);
float rSqr = dx * dx + dy * dy + dz * dz;
float mx = point[0] - cx;
float my = point[1] - cy;
float mz = point[2] - cz;
float c = mx * mx + my * my + mz * mz - rSqr;
if (c <= 0.0f) {
return true;
}
float b = mx * dir[0] + my * dir[1] + mz * dir[2];
if (b > 0.0f) {
return false;
}
float disc = b * b - c;
return disc >= 0.0f;
}
public override void handleRender(NavMeshRenderer renderer) {
if (mode == ToolMode.COLLIDERS) {
if (showColliders) {
colliderGizmos.Values.forEach(g => g.render(renderer.getDebugDraw()));
}
}
if (mode == ToolMode.RAYCAST) {
RecastDebugDraw dd = renderer.getDebugDraw();
int startCol = duRGBA(128, 25, 0, 192);
int endCol = duRGBA(51, 102, 0, 129);
if (sposSet) {
drawAgent(dd, spos, startCol);
}
if (eposSet) {
drawAgent(dd, epos, endCol);
}
dd.depthMask(false);
if (raycastHitPos != null) {
int spathCol = raycastHit ? duRGBA(128, 32, 16, 220) : duRGBA(64, 128, 240, 220);
dd.begin(LINES, 2.0f);
dd.vertex(spos[0], spos[1] + 1.3f, spos[2], spathCol);
dd.vertex(raycastHitPos[0], raycastHitPos[1], raycastHitPos[2], spathCol);
dd.end();
}
dd.depthMask(true);
}
}
private void drawAgent(RecastDebugDraw dd, float[] pos, int col) {
float r = sample.getSettingsUI().getAgentRadius();
float h = sample.getSettingsUI().getAgentHeight();
float c = sample.getSettingsUI().getAgentMaxClimb();
dd.depthMask(false);
// Agent dimensions.
dd.debugDrawCylinderWire(pos[0] - r, pos[1] + 0.02f, pos[2] - r, pos[0] + r, pos[1] + h, pos[2] + r, col, 2.0f);
dd.debugDrawCircle(pos[0], pos[1] + c, pos[2], r, duRGBA(0, 0, 0, 64), 1.0f);
int colb = duRGBA(0, 0, 0, 196);
dd.begin(LINES);
dd.vertex(pos[0], pos[1] - c, pos[2], colb);
dd.vertex(pos[0], pos[1] + c, pos[2], colb);
dd.vertex(pos[0] - r / 2, pos[1] + 0.02f, pos[2], colb);
dd.vertex(pos[0] + r / 2, pos[1] + 0.02f, pos[2], colb);
dd.vertex(pos[0], pos[1] + 0.02f, pos[2] - r / 2, colb);
dd.vertex(pos[0], pos[1] + 0.02f, pos[2] + r / 2, colb);
dd.end();
dd.depthMask(true);
}
public override void handleUpdate(float dt) {
if (dynaMesh != null) {
updateDynaMesh();
}
}
private void updateDynaMesh() {
long t = Stopwatch.GetTimestamp();
try
{
bool updated = dynaMesh.update(executor).Result;
if (updated) {
buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000;
sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh());
sample.setChanged(false);
}
} catch (Exception e) {
Console.WriteLine(e);
}
}
public override void layout(IWindow ctx) {
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Build", mode == ToolMode.BUILD)) {
// mode = ToolMode.BUILD;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Colliders", mode == ToolMode.COLLIDERS)) {
// mode = ToolMode.COLLIDERS;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Raycast", mode == ToolMode.RAYCAST)) {
// mode = ToolMode.RAYCAST;
// }
//
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// if (mode == ToolMode.BUILD) {
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_button_text(ctx, "Load Voxels...")) {
// load();
// }
// if (dynaMesh != null) {
// nk_layout_row_dynamic(ctx, 18, 1);
// compression = nk_check_text(ctx, "Compression", compression);
// if (nk_button_text(ctx, "Save Voxels...")) {
// save();
// }
// }
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Rasterization", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 18, 2);
// nk_label(ctx, "Cell Size", NK_TEXT_ALIGN_LEFT);
// nk_label(ctx, string.format("%.2f", cellSize[0]), NK_TEXT_ALIGN_RIGHT);
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Agent", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Height", 0f, walkableHeight, 5f, 0.01f, 0.01f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Radius", 0f, walkableRadius, 10f, 0.01f, 0.01f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Climb", 0f, walkableClimb, 10f, 0.01f, 0.01f);
// nk_layout_row_dynamic(ctx, 18, 2);
// nk_label(ctx, "Max Slope", NK_TEXT_ALIGN_LEFT);
// nk_label(ctx, string.format("%.0f", walkableSlopeAngle[0]), NK_TEXT_ALIGN_RIGHT);
//
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Partitioning", NK_TEXT_ALIGN_LEFT);
// partitioning = NuklearUIHelper.nk_radio(ctx, PartitionType.values(), partitioning,
// p => p.name().substring(0, 1) + p.name().substring(1).toLowerCase());
//
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Filtering", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 18, 1);
// filterLowHangingObstacles = nk_option_text(ctx, "Low Hanging Obstacles", filterLowHangingObstacles);
// nk_layout_row_dynamic(ctx, 18, 1);
// filterLedgeSpans = nk_option_text(ctx, "Ledge Spans", filterLedgeSpans);
// nk_layout_row_dynamic(ctx, 18, 1);
// filterWalkableLowHeightSpans = nk_option_text(ctx, "Walkable Low Height Spans", filterWalkableLowHeightSpans);
//
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Region", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Min Region Size", 0, minRegionArea, 150, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Merged Region Size", 0, regionMergeSize, 400, 0.1f, 0.1f);
//
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Polygonization", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Edge Length", 0f, maxEdgeLen, 50f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Edge Error", 0.1f, maxSimplificationError, 10f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_int(ctx, "Verts Per Poly", 3, vertsPerPoly, 12, 1, 1);
//
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Detail Mesh", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// buildDetailMesh = nk_check_text(ctx, "Enable", buildDetailMesh);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Sample Distance", 0f, detailSampleDist, 16f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Sample Error", 0f, detailSampleMaxError, 16f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_button_text(ctx, "Build")) {
// if (dynaMesh != null) {
// buildDynaMesh();
// sample.setChanged(false);
// }
// }
// }
// if (mode == ToolMode.COLLIDERS) {
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Colliders", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// showColliders = nk_check_text(ctx, "Show", showColliders);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Sphere", colliderShape == ColliderShape.SPHERE)) {
// colliderShape = ColliderShape.SPHERE;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Capsule", colliderShape == ColliderShape.CAPSULE)) {
// colliderShape = ColliderShape.CAPSULE;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Box", colliderShape == ColliderShape.BOX)) {
// colliderShape = ColliderShape.BOX;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Cylinder", colliderShape == ColliderShape.CYLINDER)) {
// colliderShape = ColliderShape.CYLINDER;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Composite", colliderShape == ColliderShape.COMPOSITE)) {
// colliderShape = ColliderShape.COMPOSITE;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Convex Trimesh", colliderShape == ColliderShape.CONVEX)) {
// colliderShape = ColliderShape.CONVEX;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Trimesh Bridge", colliderShape == ColliderShape.TRIMESH_BRIDGE)) {
// colliderShape = ColliderShape.TRIMESH_BRIDGE;
// }
// nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Trimesh House", colliderShape == ColliderShape.TRIMESH_HOUSE)) {
// colliderShape = ColliderShape.TRIMESH_HOUSE;
// }
// }
// nk_layout_row_dynamic(ctx, 2, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// if (mode == ToolMode.RAYCAST) {
// nk_label(ctx, string.format("Raycast Time: %d ms", raycastTime), NK_TEXT_ALIGN_LEFT);
// if (sposSet) {
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Start: %.3f, %.3f, %.3f", spos[0], spos[1] + 1.3f, spos[2]), NK_TEXT_ALIGN_LEFT);
// }
// if (eposSet) {
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("End: %.3f, %.3f, %.3f", epos[0], epos[1] + 1.3f, epos[2]), NK_TEXT_ALIGN_LEFT);
// }
// if (raycastHit) {
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, string.format("Hit: %.3f, %.3f, %.3f", raycastHitPos[0], raycastHitPos[1], raycastHitPos[2]),
// NK_TEXT_ALIGN_LEFT);
// }
// } else {
// nk_label(ctx, string.format("Build Time: %d ms", buildTime), NK_TEXT_ALIGN_LEFT);
// }
}
private void load() {
// try (MemoryStack stack = stackPush()) {
// PointerBuffer aFilterPatterns = stack.mallocPointer(1);
// aFilterPatterns.put(stack.UTF8("*.voxels"));
// aFilterPatterns.flip();
// string filename = TinyFileDialogs.tinyfd_openFileDialog("Open Voxel File", "", aFilterPatterns, "Voxel File", false);
// if (filename != null) {
// load(filename);
// }
// }
}
private void load(string filename) {
// File file = new File(filename);
// if (file.exists()) {
// VoxelFileReader reader = new VoxelFileReader();
// try (FileInputStream fis = new FileInputStream(file)) {
// VoxelFile voxelFile = reader.read(fis);
// dynaMesh = new DynamicNavMesh(voxelFile);
// dynaMesh.config.keepIntermediateResults = true;
// updateUI();
// buildDynaMesh();
// colliders.clear();
// } catch (Exception e) {
// Console.WriteLine(e);
// dynaMesh = null;
// }
// }
}
private void save() {
// try (MemoryStack stack = stackPush()) {
// PointerBuffer aFilterPatterns = stack.mallocPointer(1);
// aFilterPatterns.put(stack.UTF8("*.voxels"));
// aFilterPatterns.flip();
// string filename = TinyFileDialogs.tinyfd_saveFileDialog("Save Voxel File", "", aFilterPatterns, "Voxel File");
// if (filename != null) {
// save(filename);
// }
// }
}
private void save(string filename) {
// File file = new File(filename);
// try (FileOutputStream fos = new FileOutputStream(file)) {
// VoxelFile voxelFile = VoxelFile.from(dynaMesh);
// VoxelFileWriter writer = new VoxelFileWriter();
// writer.write(fos, voxelFile, compression);
// } catch (Exception e) {
// Console.WriteLine(e);
// }
}
private void buildDynaMesh() {
configDynaMesh();
long t = Stopwatch.GetTimestamp();
try
{
var _ = dynaMesh.build(executor).Result;
} catch (Exception e) {
Console.WriteLine(e);
}
buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000;
sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh());
}
private void configDynaMesh() {
dynaMesh.config.partitionType = partitioning;
dynaMesh.config.walkableHeight = walkableHeight[0];
dynaMesh.config.walkableSlopeAngle = walkableSlopeAngle[0];
dynaMesh.config.walkableRadius = walkableRadius[0];
dynaMesh.config.walkableClimb = walkableClimb[0];
dynaMesh.config.filterLowHangingObstacles = filterLowHangingObstacles;
dynaMesh.config.filterLedgeSpans = filterLedgeSpans;
dynaMesh.config.filterWalkableLowHeightSpans = filterWalkableLowHeightSpans;
dynaMesh.config.minRegionArea = minRegionArea[0];
dynaMesh.config.regionMergeArea = regionMergeSize[0];
dynaMesh.config.maxEdgeLen = maxEdgeLen[0];
dynaMesh.config.maxSimplificationError = maxSimplificationError[0];
dynaMesh.config.vertsPerPoly = vertsPerPoly[0];
dynaMesh.config.buildDetailMesh = buildDetailMesh;
dynaMesh.config.detailSampleDistance = detailSampleDist[0];
dynaMesh.config.detailSampleMaxError = detailSampleMaxError[0];
}
private void updateUI() {
cellSize[0] = dynaMesh.config.cellSize;
partitioning = dynaMesh.config.partitionType;
walkableHeight[0] = dynaMesh.config.walkableHeight;
walkableSlopeAngle[0] = dynaMesh.config.walkableSlopeAngle;
walkableRadius[0] = dynaMesh.config.walkableRadius;
walkableClimb[0] = dynaMesh.config.walkableClimb;
minRegionArea[0] = dynaMesh.config.minRegionArea;
regionMergeSize[0] = dynaMesh.config.regionMergeArea;
maxEdgeLen[0] = dynaMesh.config.maxEdgeLen;
maxSimplificationError[0] = dynaMesh.config.maxSimplificationError;
vertsPerPoly[0] = dynaMesh.config.vertsPerPoly;
buildDetailMesh = dynaMesh.config.buildDetailMesh;
detailSampleDist[0] = dynaMesh.config.detailSampleDistance;
detailSampleMaxError[0] = dynaMesh.config.detailSampleMaxError;
filterLowHangingObstacles = dynaMesh.config.filterLowHangingObstacles;
filterLedgeSpans = dynaMesh.config.filterLedgeSpans;
filterWalkableLowHeightSpans = dynaMesh.config.filterWalkableLowHeightSpans;
}
public override string getName() {
return "Dynamic Updates";
}
}

View File

@ -0,0 +1,68 @@
using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class BoxGizmo : ColliderGizmo {
private static readonly int[] TRIANLGES = { 0, 1, 2, 0, 2, 3, 4, 7, 6, 4, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2,
2, 6, 7, 2, 7, 3, 4, 0, 3, 4, 3, 7 };
private static readonly float[][] VERTS = {
new[] { -1f, -1f, -1f, },
new[] { 1f, -1f, -1f, },
new[] { 1f, -1f, 1f, },
new[] { -1f, -1f, 1f, },
new[] { -1f, 1f, -1f, },
new[] { 1f, 1f, -1f, },
new[] { 1f, 1f, 1f, },
new[] { -1f, 1f, 1f, },
};
private readonly float[] vertices = new float[8 * 3];
private readonly float[] center;
private readonly float[][] halfEdges;
public BoxGizmo(float[] center, float[] extent, float[] forward, float[] up) :
this(center, BoxCollider.getHalfEdges(up, forward, extent))
{
}
public BoxGizmo(float[] center, float[][] halfEdges) {
this.center = center;
this.halfEdges = halfEdges;
for (int i = 0; i < 8; ++i) {
float s0 = (i & 1) != 0 ? 1f : -1f;
float s1 = (i & 2) != 0 ? 1f : -1f;
float s2 = (i & 4) != 0 ? 1f : -1f;
vertices[i * 3 + 0] = center[0] + s0 * halfEdges[0][0] + s1 * halfEdges[1][0] + s2 * halfEdges[2][0];
vertices[i * 3 + 1] = center[1] + s0 * halfEdges[0][1] + s1 * halfEdges[1][1] + s2 * halfEdges[2][1];
vertices[i * 3 + 2] = center[2] + s0 * halfEdges[0][2] + s1 * halfEdges[1][2] + s2 * halfEdges[2][2];
}
}
public void render(RecastDebugDraw debugDraw) {
float[] trX = new float[] { halfEdges[0][0], halfEdges[1][0], halfEdges[2][0] };
float[] trY = new float[] { halfEdges[0][1], halfEdges[1][1], halfEdges[2][1] };
float[] trZ = new float[] { halfEdges[0][2], halfEdges[1][2], halfEdges[2][2] };
float[] vertices = new float[8 * 3];
for (int i = 0; i < 8; i++) {
vertices[i * 3 + 0] = RecastVectors.dot(VERTS[i], trX) + center[0];
vertices[i * 3 + 1] = RecastVectors.dot(VERTS[i], trY) + center[1];
vertices[i * 3 + 2] = RecastVectors.dot(VERTS[i], trZ) + center[2];
}
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < 12; i++) {
int col = DebugDraw.duRGBA(200, 200, 50, 160);
if (i == 4 || i == 5 || i == 8 || i == 9) {
col = DebugDraw.duRGBA(160, 160, 40, 160);
} else if (i > 4) {
col = DebugDraw.duRGBA(120, 120, 30, 160);
}
for (int j = 0; j < 3; j++) {
int v = TRIANLGES[i * 3 + j] * 3;
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
}
}
debugDraw.end();
}
}

View File

@ -0,0 +1,80 @@
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Recast.RecastVectors;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class CapsuleGizmo : ColliderGizmo {
private readonly float[] vertices;
private readonly int[] triangles;
private readonly float[] center;
private readonly float[] gradient;
public CapsuleGizmo(float[] start, float[] end, float radius) {
center = new float[] { 0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
0.5f * (start[2] + end[2]) };
float[] axis = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
float[][] normals = new float[3][];
normals[1] = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
normalize(normals[1]);
normals[0] = getSideVector(axis);
normals[2] = new float[3];
cross(normals[2], normals[0], normals[1]);
normalize(normals[2]);
triangles = generateSphericalTriangles();
float[] trX = new float[] { normals[0][0], normals[1][0], normals[2][0] };
float[] trY = new float[] { normals[0][1], normals[1][1], normals[2][1] };
float[] trZ = new float[] { normals[0][2], normals[1][2], normals[2][2] };
float[] spVertices = generateSphericalVertices();
float halfLength = 0.5f * vLen(axis);
vertices = new float[spVertices.Length];
gradient = new float[spVertices.Length / 3];
float[] v = new float[3];
for (int i = 0; i < spVertices.Length; i += 3) {
float offset = (i >= spVertices.Length / 2) ? -halfLength : halfLength;
float x = radius * spVertices[i];
float y = radius * spVertices[i + 1] + offset;
float z = radius * spVertices[i + 2];
vertices[i] = x * trX[0] + y * trX[1] + z * trX[2] + center[0];
vertices[i + 1] = x * trY[0] + y * trY[1] + z * trY[2] + center[1];
vertices[i + 2] = x * trZ[0] + y * trZ[1] + z * trZ[2] + center[2];
v[0] = vertices[i] - center[0];
v[1] = vertices[i + 1] - center[1];
v[2] = vertices[i + 2] - center[2];
normalize(v);
gradient[i / 3] = clamp(0.57735026f * (v[0] + v[1] + v[2]), -1, 1);
}
}
private float[] getSideVector(float[] axis) {
float[] side = { 1, 0, 0 };
if (axis[0] > 0.8) {
side = new float[] { 0, 0, 1 };
}
float[] forward = new float[3];
cross(forward, side, axis);
cross(side, axis, forward);
normalize(side);
return side;
}
public void render(RecastDebugDraw debugDraw) {
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) {
for (int j = 0; j < 3; j++) {
int v = triangles[i + j] * 3;
float c = gradient[triangles[i + j]];
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
(int) (127 * (1 + c)));
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
}
}
debugDraw.end();
}
}

View File

@ -0,0 +1,8 @@
using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public interface ColliderGizmo {
void render(RecastDebugDraw debugDraw);
}

View File

@ -0,0 +1,17 @@
using DotRecast.Core;
using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class CompositeGizmo : ColliderGizmo {
private readonly ColliderGizmo[] gizmos;
public CompositeGizmo(params ColliderGizmo[] gizmos) {
this.gizmos = gizmos;
}
public void render(RecastDebugDraw debugDraw) {
gizmos.forEach(g => g.render(debugDraw));
}
}

View File

@ -0,0 +1,82 @@
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Recast.RecastVectors;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class CylinderGizmo : ColliderGizmo {
private readonly float[] vertices;
private readonly int[] triangles;
private readonly float[] center;
private readonly float[] gradient;
public CylinderGizmo(float[] start, float[] end, float radius) {
center = new float[] { 0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
0.5f * (start[2] + end[2]) };
float[] axis = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
float[][] normals = new float[3][];
normals[1] = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
normalize(normals[1]);
normals[0] = getSideVector(axis);
normals[2] = new float[3];
cross(normals[2], normals[0], normals[1]);
normalize(normals[2]);
triangles = generateCylindricalTriangles();
float[] trX = new float[] { normals[0][0], normals[1][0], normals[2][0] };
float[] trY = new float[] { normals[0][1], normals[1][1], normals[2][1] };
float[] trZ = new float[] { normals[0][2], normals[1][2], normals[2][2] };
vertices = generateCylindricalVertices();
float halfLength = 0.5f * vLen(axis);
gradient = new float[vertices.Length / 3];
float[] v = new float[3];
for (int i = 0; i < vertices.Length; i += 3) {
float offset = (i >= vertices.Length / 2) ? -halfLength : halfLength;
float x = radius * vertices[i];
float y = vertices[i + 1] + offset;
float z = radius * vertices[i + 2];
vertices[i] = x * trX[0] + y * trX[1] + z * trX[2] + center[0];
vertices[i + 1] = x * trY[0] + y * trY[1] + z * trY[2] + center[1];
vertices[i + 2] = x * trZ[0] + y * trZ[1] + z * trZ[2] + center[2];
if (i < vertices.Length / 4 || i >= 3 * vertices.Length / 4) {
gradient[i / 3] = 1;
} else {
v[0] = vertices[i] - center[0];
v[1] = vertices[i + 1] - center[1];
v[2] = vertices[i + 2] - center[2];
normalize(v);
gradient[i / 3] = clamp(0.57735026f * (v[0] + v[1] + v[2]), -1, 1);
}
}
}
private float[] getSideVector(float[] axis) {
float[] side = { 1, 0, 0 };
if (axis[0] > 0.8) {
side = new float[] { 0, 0, 1 };
}
float[] forward = new float[3];
cross(forward, side, axis);
cross(side, axis, forward);
normalize(side);
return side;
}
public void render(RecastDebugDraw debugDraw) {
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) {
for (int j = 0; j < 3; j++) {
int v = triangles[i + j] * 3;
float c = gradient[triangles[i + j]];
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
(int) (127 * (1 + c)));
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
}
}
debugDraw.end();
}
}

View File

@ -0,0 +1,28 @@
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public static class GizmoFactory
{
public static ColliderGizmo box(float[] center, float[][] halfEdges) {
return new BoxGizmo(center, halfEdges);
}
public static ColliderGizmo sphere(float[] center, float radius) {
return new SphereGizmo(center, radius);
}
public static ColliderGizmo capsule(float[] start, float[] end, float radius) {
return new CapsuleGizmo(start, end, radius);
}
public static ColliderGizmo cylinder(float[] start, float[] end, float radius) {
return new CylinderGizmo(start, end, radius);
}
public static ColliderGizmo trimesh(float[] verts, int[] faces) {
return new TrimeshGizmo(verts, faces);
}
public static ColliderGizmo composite(params ColliderGizmo[] gizmos) {
return new CompositeGizmo(gizmos);
}
}

View File

@ -0,0 +1,162 @@
using System;
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class GizmoHelper {
private static readonly int SEGMENTS = 16;
private static readonly int RINGS = 8;
private static float[] sphericalVertices;
public static float[] generateSphericalVertices() {
if (sphericalVertices == null) {
sphericalVertices = generateSphericalVertices(SEGMENTS, RINGS);
}
return sphericalVertices;
}
private static float[] generateSphericalVertices(int segments, int rings) {
float[] vertices = new float[6 + 3 * (segments + 1) * (rings + 1)];
// top
int vi = 0;
vertices[vi++] = 0;
vertices[vi++] = 1;
vertices[vi++] = 0;
for (int r = 0; r <= rings; r++) {
double theta = Math.PI * (r + 1) / (rings + 2);
vi = generateRingVertices(segments, vertices, vi, theta);
}
// bottom
vertices[vi++] = 0;
vertices[vi++] = -1;
vertices[vi++] = 0;
return vertices;
}
public static float[] generateCylindricalVertices() {
return generateCylindricalVertices(SEGMENTS);
}
private static float[] generateCylindricalVertices(int segments) {
float[] vertices = new float[3 * (segments + 1) * 4];
int vi = 0;
for (int r = 0; r < 4; r++) {
vi = generateRingVertices(segments, vertices, vi, Math.PI * 0.5);
}
return vertices;
}
private static int generateRingVertices(int segments, float[] vertices, int vi, double theta) {
double cosTheta = Math.Cos(theta);
double sinTheta = Math.Sin(theta);
for (int p = 0; p <= segments; p++) {
double phi = 2 * Math.PI * p / segments;
double cosPhi = Math.Cos(phi);
double sinPhi = Math.Sin(phi);
vertices[vi++] = (float) (sinTheta * cosPhi);
vertices[vi++] = (float) cosTheta;
vertices[vi++] = (float) (sinTheta * sinPhi);
}
return vi;
}
public static int[] generateSphericalTriangles() {
return generateSphericalTriangles(SEGMENTS, RINGS);
}
private static int[] generateSphericalTriangles(int segments, int rings) {
int[] triangles = new int[6 * (segments + rings * (segments + 1))];
int ti = generateSphereUpperCapTriangles(segments, triangles, 0);
ti = generateRingTriangles(segments, rings, triangles, 1, ti);
generateSphereLowerCapTriangles(segments, rings, triangles, ti);
return triangles;
}
public static int generateRingTriangles(int segments, int rings, int[] triangles, int vertexOffset, int ti) {
for (int r = 0; r < rings; r++) {
for (int p = 0; p < segments; p++) {
int current = p + r * (segments + 1) + vertexOffset;
int next = p + 1 + r * (segments + 1) + vertexOffset;
int currentBottom = p + (r + 1) * (segments + 1) + vertexOffset;
int nextBottom = p + 1 + (r + 1) * (segments + 1) + vertexOffset;
triangles[ti++] = current;
triangles[ti++] = next;
triangles[ti++] = nextBottom;
triangles[ti++] = current;
triangles[ti++] = nextBottom;
triangles[ti++] = currentBottom;
}
}
return ti;
}
private static int generateSphereUpperCapTriangles(int segments, int[] triangles, int ti) {
for (int p = 0; p < segments; p++) {
triangles[ti++] = p + 2;
triangles[ti++] = p + 1;
triangles[ti++] = 0;
}
return ti;
}
private static void generateSphereLowerCapTriangles(int segments, int rings, int[] triangles, int ti) {
int lastVertex = 1 + (segments + 1) * (rings + 1);
for (int p = 0; p < segments; p++) {
triangles[ti++] = lastVertex;
triangles[ti++] = lastVertex - (p + 2);
triangles[ti++] = lastVertex - (p + 1);
}
}
public static int[] generateCylindricalTriangles() {
return generateCylindricalTriangles(SEGMENTS);
}
private static int[] generateCylindricalTriangles(int segments) {
int circleTriangles = segments - 2;
int[] triangles = new int[6 * (circleTriangles + (segments + 1))];
int vi = 0;
int ti = generateCircleTriangles(segments, triangles, vi, 0, false);
ti = generateRingTriangles(segments, 1, triangles, segments + 1, ti);
int vertexCount = (segments + 1) * 4;
ti = generateCircleTriangles(segments, triangles, vertexCount - segments, ti, true);
return triangles;
}
private static int generateCircleTriangles(int segments, int[] triangles, int vi, int ti, bool invert) {
for (int p = 0; p < segments - 2; p++) {
if (invert) {
triangles[ti++] = vi;
triangles[ti++] = vi + p + 1;
triangles[ti++] = vi + p + 2;
} else {
triangles[ti++] = vi + p + 2;
triangles[ti++] = vi + p + 1;
triangles[ti++] = vi;
}
}
return ti;
}
public static int getColorByNormal(float[] vertices, int v0, int v1, int v2) {
float[] e0 = new float[3], e1 = new float[3];
float[] normal = new float[3];
for (int j = 0; j < 3; ++j) {
e0[j] = vertices[v1 + j] - vertices[v0 + j];
e1[j] = vertices[v2 + j] - vertices[v0 + j];
}
normal[0] = e0[1] * e1[2] - e0[2] * e1[1];
normal[1] = e0[2] * e1[0] - e0[0] * e1[2];
normal[2] = e0[0] * e1[1] - e0[1] * e1[0];
RecastVectors.normalize(normal);
float c = clamp(0.57735026f * (normal[0] + normal[1] + normal[2]), -1, 1);
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
(int) (127 * (1 + c)));
return col;
}
}

View File

@ -0,0 +1,38 @@
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class SphereGizmo : ColliderGizmo {
private readonly float[] vertices;
private readonly int[] triangles;
private readonly float radius;
private readonly float[] center;
public SphereGizmo(float[] center, float radius) {
this.center = center;
this.radius = radius;
vertices = generateSphericalVertices();
triangles = generateSphericalTriangles();
}
public void render(RecastDebugDraw debugDraw) {
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) {
for (int j = 0; j < 3; j++) {
int v = triangles[i + j] * 3;
float c = clamp(0.57735026f * (vertices[v] + vertices[v + 1] + vertices[v + 2]), -1, 1);
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
(int) (127 * (1 + c)));
debugDraw.vertex(radius * vertices[v] + center[0], radius * vertices[v + 1] + center[1],
radius * vertices[v + 2] + center[2], col);
}
}
debugDraw.end();
}
}

View File

@ -0,0 +1,28 @@
using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class TrimeshGizmo : ColliderGizmo {
private readonly float[] vertices;
private readonly int[] triangles;
public TrimeshGizmo(float[] vertices, int[] triangles) {
this.vertices = vertices;
this.triangles = triangles;
}
public void render(RecastDebugDraw debugDraw) {
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) {
int v0 = 3 * triangles[i];
int v1 = 3 * triangles[i + 1];
int v2 = 3 * triangles[i + 2];
int col = GizmoHelper.getColorByNormal(vertices, v0, v1, v2);
debugDraw.vertex(vertices[v0], vertices[v0 + 1], vertices[v0 + 2], col);
debugDraw.vertex(vertices[v1], vertices[v1 + 1], vertices[v1 + 2], col);
debugDraw.vertex(vertices[v2], vertices[v2 + 1], vertices[v2 + 2], col);
}
debugDraw.end();
}
}

View File

@ -0,0 +1,427 @@
/*
recast4j copyright (c) 2020-2021 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.Collections.Generic;
using Silk.NET.Windowing;
using DotRecast.Detour.Extras.Jumplink;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class JumpLinkBuilderTool : Tool {
private readonly List<JumpLink> links = new();
private Sample sample;
private JumpLinkBuilder annotationBuilder;
private readonly int selEdge = -1;
private readonly JumpLinkBuilderToolParams option = new JumpLinkBuilderToolParams();
public override void setSample(Sample sample) {
this.sample = sample;
annotationBuilder = null;
}
public override void handleClick(float[] s, float[] p, bool shift) {
}
public override void handleRender(NavMeshRenderer renderer) {
int col0 = duLerpCol(duRGBA(32, 255, 96, 255), duRGBA(255, 255, 255, 255), 200);
int col1 = duRGBA(32, 255, 96, 255);
RecastDebugDraw dd = renderer.getDebugDraw();
dd.depthMask(false);
if ((option.flags & JumpLinkBuilderToolParams.DRAW_WALKABLE_BORDER) != 0) {
if (annotationBuilder != null) {
foreach (Edge[] edges in annotationBuilder.getEdges()) {
dd.begin(LINES, 3.0f);
for (int i = 0; i < edges.Length; ++i) {
int col = duRGBA(0, 96, 128, 255);
if (i == selEdge)
continue;
dd.vertex(edges[i].sp, col);
dd.vertex(edges[i].sq, col);
}
dd.end();
dd.begin(POINTS, 8.0f);
for (int i = 0; i < edges.Length; ++i) {
int col = duRGBA(0, 96, 128, 255);
if (i == selEdge)
continue;
dd.vertex(edges[i].sp, col);
dd.vertex(edges[i].sq, col);
}
dd.end();
if (selEdge >= 0 && selEdge < edges.Length) {
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,192,0,255);
dd.begin(LINES, 3.0f);
dd.vertex(edges[selEdge].sp, col);
dd.vertex(edges[selEdge].sq, col);
dd.end();
dd.begin(POINTS, 8.0f);
dd.vertex(edges[selEdge].sp, col);
dd.vertex(edges[selEdge].sq, col);
dd.end();
}
dd.begin(POINTS, 4.0f);
for (int i = 0; i < edges.Length; ++i) {
int col = duRGBA(190, 190, 190, 255);
dd.vertex(edges[i].sp, col);
dd.vertex(edges[i].sq, col);
}
dd.end();
}
}
}
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANNOTATIONS) != 0) {
dd.begin(QUADS);
foreach (JumpLink link in links) {
for (int j = 0; j < link.nspine - 1; ++j) {
int u = (j * 255) / link.nspine;
int col = duTransCol(duLerpCol(col0, col1, u), 128);
dd.vertex(link.spine1[j * 3], link.spine1[j * 3 + 1], link.spine1[j * 3 + 2], col);
dd.vertex(link.spine1[(j + 1) * 3], link.spine1[(j + 1) * 3 + 1], link.spine1[(j + 1) * 3 + 2],
col);
dd.vertex(link.spine0[(j + 1) * 3], link.spine0[(j + 1) * 3 + 1], link.spine0[(j + 1) * 3 + 2],
col);
dd.vertex(link.spine0[j * 3], link.spine0[j * 3 + 1], link.spine0[j * 3 + 2], col);
}
}
dd.end();
dd.begin(LINES, 3.0f);
foreach (JumpLink link in links) {
for (int j = 0; j < link.nspine - 1; ++j) {
// int u = (j*255)/link.nspine;
int col = duTransCol(duDarkenCol(col1)/*duDarkenCol(duLerpCol(col0,col1,u))*/, 128);
dd.vertex(link.spine0[j * 3], link.spine0[j * 3 + 1], link.spine0[j * 3 + 2], col);
dd.vertex(link.spine0[(j + 1) * 3], link.spine0[(j + 1) * 3 + 1], link.spine0[(j + 1) * 3 + 2],
col);
dd.vertex(link.spine1[j * 3], link.spine1[j * 3 + 1], link.spine1[j * 3 + 2], col);
dd.vertex(link.spine1[(j + 1) * 3], link.spine1[(j + 1) * 3 + 1], link.spine1[(j + 1) * 3 + 2],
col);
}
dd.vertex(link.spine0[0], link.spine0[1], link.spine0[2], duDarkenCol(col1));
dd.vertex(link.spine1[0], link.spine1[1], link.spine1[2], duDarkenCol(col1));
dd.vertex(link.spine0[(link.nspine - 1) * 3], link.spine0[(link.nspine - 1) * 3 + 1],
link.spine0[(link.nspine - 1) * 3 + 2], duDarkenCol(col1));
dd.vertex(link.spine1[(link.nspine - 1) * 3], link.spine1[(link.nspine - 1) * 3 + 1],
link.spine1[(link.nspine - 1) * 3 + 2], duDarkenCol(col1));
}
dd.end();
}
if (annotationBuilder != null) {
foreach (JumpLink link in links) {
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANIM_TRAJECTORY) != 0) {
float r = link.start.height;
int col = duLerpCol(duRGBA(255, 192, 0, 255),
duRGBA(255, 255, 255, 255), 64);
int cola = duTransCol(col, 192);
int colb = duRGBA(255, 255, 255, 255);
// Start segment.
dd.begin(LINES, 3.0f);
dd.vertex(link.start.p, col);
dd.vertex(link.start.q, col);
dd.end();
dd.begin(LINES, 1.0f);
dd.vertex(link.start.p[0], link.start.p[1], link.start.p[2], colb);
dd.vertex(link.start.p[0], link.start.p[1] + r, link.start.p[2], colb);
dd.vertex(link.start.p[0], link.start.p[1] + r, link.start.p[2], colb);
dd.vertex(link.start.q[0], link.start.q[1] + r, link.start.q[2], colb);
dd.vertex(link.start.q[0], link.start.q[1] + r, link.start.q[2], colb);
dd.vertex(link.start.q[0], link.start.q[1], link.start.q[2], colb);
dd.vertex(link.start.q[0], link.start.q[1], link.start.q[2], colb);
dd.vertex(link.start.p[0], link.start.p[1], link.start.p[2], colb);
dd.end();
GroundSegment end = link.end;
r = end.height;
// End segment.
dd.begin(LINES, 3.0f);
dd.vertex(end.p, col);
dd.vertex(end.q, col);
dd.end();
dd.begin(LINES, 1.0f);
dd.vertex(end.p[0], end.p[1], end.p[2], colb);
dd.vertex(end.p[0], end.p[1] + r, end.p[2], colb);
dd.vertex(end.p[0], end.p[1] + r, end.p[2], colb);
dd.vertex(end.q[0], end.q[1] + r, end.q[2], colb);
dd.vertex(end.q[0], end.q[1] + r, end.q[2], colb);
dd.vertex(end.q[0], end.q[1], end.q[2], colb);
dd.vertex(end.q[0], end.q[1], end.q[2], colb);
dd.vertex(end.p[0], end.p[1], end.p[2], colb);
dd.end();
dd.begin(LINES, 4.0f);
drawTrajectory(dd, link, link.start.p, end.p, link.trajectory, cola);
drawTrajectory(dd, link, link.start.q, end.q, link.trajectory, cola);
dd.end();
dd.begin(LINES, 8.0f);
dd.vertex(link.start.p, duDarkenCol(col));
dd.vertex(link.start.q, duDarkenCol(col));
dd.vertex(end.p, duDarkenCol(col));
dd.vertex(end.q, duDarkenCol(col));
dd.end();
int colm = duRGBA(255, 255, 255, 255);
dd.begin(LINES, 3.0f);
dd.vertex(link.start.p, colm);
dd.vertex(link.start.q, colm);
dd.vertex(end.p, colm);
dd.vertex(end.q, colm);
dd.end();
}
if ((option.flags & JumpLinkBuilderToolParams.DRAW_LAND_SAMPLES) != 0) {
dd.begin(POINTS, 8.0f);
for (int i = 0; i < link.start.gsamples.Length; ++i) {
GroundSample s = link.start.gsamples[i];
float u = i / (float) (link.start.gsamples.Length - 1);
float[] spt = vLerp(link.start.p, link.start.q, u);
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,(s->flags & 4)?255:0,0,255);
float off = 0.1f;
if (!s.validHeight) {
off = 0;
col = duRGBA(220, 32, 32, 255);
}
spt[1] = s.p[1] + off;
dd.vertex(spt, col);
}
dd.end();
dd.begin(POINTS, 4.0f);
for (int i = 0; i < link.start.gsamples.Length; ++i) {
GroundSample s = link.start.gsamples[i];
float u = i / (float) (link.start.gsamples.Length - 1);
float[] spt = vLerp(link.start.p, link.start.q, u);
int col = duRGBA(255, 255, 255, 255);
float off = 0;
if (s.validHeight) {
off = 0.1f;
}
spt[1] = s.p[1] + off;
dd.vertex(spt, col);
}
dd.end();
{
GroundSegment end = link.end;
dd.begin(POINTS, 8.0f);
for (int i = 0; i < end.gsamples.Length; ++i) {
GroundSample s = end.gsamples[i];
float u = i / (float) (end.gsamples.Length - 1);
float[] spt = vLerp(end.p, end.q, u);
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,(s->flags & 4)?255:0,0,255);
float off = 0.1f;
if (!s.validHeight) {
off = 0;
col = duRGBA(220, 32, 32, 255);
}
spt[1] = s.p[1] + off;
dd.vertex(spt, col);
}
dd.end();
dd.begin(POINTS, 4.0f);
for (int i = 0; i < end.gsamples.Length; ++i) {
GroundSample s = end.gsamples[i];
float u = i / (float) (end.gsamples.Length - 1);
float[] spt = vLerp(end.p, end.q, u);
int col = duRGBA(255, 255, 255, 255);
float off = 0;
if (s.validHeight) {
off = 0.1f;
}
spt[1] = s.p[1] + off;
dd.vertex(spt, col);
}
dd.end();
}
}
}
}
dd.depthMask(true);
}
private void drawTrajectory(RecastDebugDraw dd, JumpLink link, float[] pa, float[] pb, Trajectory tra, int cola) {
}
public override void handleUpdate(float dt) {
}
public override void layout(IWindow ctx) {
// if (!sample.getRecastResults().isEmpty()) {
//
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Options", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Ground Tolerance", 0f, option.groundTolerance, 2f, 0.05f, 0.01f);
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
//
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Climb Down", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Distance", 0f, option.climbDownDistance, 5f, 0.05f, 0.01f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Min Cliff Height", 0f, option.climbDownMinHeight, 10f, 0.05f, 0.01f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Cliff Height", 0f, option.climbDownMaxHeight, 10f, 0.05f, 0.01f);
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
//
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Jump Down", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Distance", 0f, option.edgeJumpEndDistance, 10f, 0.05f, 0.01f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Jump Height", 0f, option.edgeJumpHeight, 10f, 0.05f, 0.01f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Jump Down", 0f, option.edgeJumpDownMaxHeight, 10f, 0.05f, 0.01f);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Max Jump Up", 0f, option.edgeJumpUpMaxHeight, 10f, 0.05f, 0.01f);
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Mode", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// int buildTypes = 0;
// buildTypes |= nk_option_text(ctx, "Climb Down",
// (option.buildTypes & (1 << JumpLinkType.EDGE_CLIMB_DOWN.ordinal())) != 0)
// ? (1 << JumpLinkType.EDGE_CLIMB_DOWN.ordinal())
// : 0;
// nk_layout_row_dynamic(ctx, 20, 1);
// buildTypes |= nk_option_text(ctx, "Edge Jump",
// (option.buildTypes & (1 << JumpLinkType.EDGE_JUMP.ordinal())) != 0)
// ? (1 << JumpLinkType.EDGE_JUMP.ordinal())
// : 0;
// option.buildTypes = buildTypes;
// bool build = false;
// bool buildOffMeshConnections = false;
// if (nk_button_text(ctx, "Build")) {
// build = true;
// }
// if (nk_button_text(ctx, "Build Off-Mesh Links")) {
// buildOffMeshConnections = true;
// }
// if (build || buildOffMeshConnections) {
// if (annotationBuilder == null) {
// if (sample != null && !sample.getRecastResults().isEmpty()) {
// annotationBuilder = new JumpLinkBuilder(sample.getRecastResults());
// }
// }
// links.clear();
// if (annotationBuilder != null) {
// float cellSize = sample.getSettingsUI().getCellSize();
// float agentHeight = sample.getSettingsUI().getAgentHeight();
// float agentRadius = sample.getSettingsUI().getAgentRadius();
// float agentClimb = sample.getSettingsUI().getAgentMaxClimb();
// float cellHeight = sample.getSettingsUI().getCellHeight();
// if ((buildTypes & (1 << JumpLinkType.EDGE_CLIMB_DOWN.ordinal())) != 0) {
// JumpLinkBuilderConfig config = new JumpLinkBuilderConfig(cellSize, cellHeight, agentRadius,
// agentHeight, agentClimb, option.groundTolerance[0], -agentRadius * 0.2f,
// cellSize + 2 * agentRadius + option.climbDownDistance[0],
// -option.climbDownMaxHeight[0], -option.climbDownMinHeight[0], 0);
// links.addAll(annotationBuilder.build(config, JumpLinkType.EDGE_CLIMB_DOWN));
// }
// if ((buildTypes & (1 << JumpLinkType.EDGE_JUMP.ordinal())) != 0) {
// JumpLinkBuilderConfig config = new JumpLinkBuilderConfig(cellSize, cellHeight, agentRadius,
// agentHeight, agentClimb, option.groundTolerance[0], -agentRadius * 0.2f,
// option.edgeJumpEndDistance[0], -option.edgeJumpDownMaxHeight[0],
// option.edgeJumpUpMaxHeight[0], option.edgeJumpHeight[0]);
// links.addAll(annotationBuilder.build(config, JumpLinkType.EDGE_JUMP));
// }
// if (buildOffMeshConnections) {
// DemoInputGeomProvider geom = sample.getInputGeom();
// if (geom != null) {
// int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP_AUTO;
// geom.removeOffMeshConnections(c => c.area == area);
// links.forEach(l => addOffMeshLink(l, geom, agentRadius));
// }
// }
// }
// }
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 18, 1);
// nk_label(ctx, "Debug Draw Options", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// int newFlags = 0;
// newFlags |= nk_option_text(ctx, "Walkable Border",
// (params.flags & JumpLinkBuilderToolParams.DRAW_WALKABLE_BORDER) != 0)
// ? JumpLinkBuilderToolParams.DRAW_WALKABLE_BORDER
// : 0;
// nk_layout_row_dynamic(ctx, 20, 1);
// newFlags |= nk_option_text(ctx, "Selected Edge",
// (params.flags & JumpLinkBuilderToolParams.DRAW_SELECTED_EDGE) != 0)
// ? JumpLinkBuilderToolParams.DRAW_SELECTED_EDGE
// : 0;
// nk_layout_row_dynamic(ctx, 20, 1);
// newFlags |= nk_option_text(ctx, "Anim Trajectory",
// (params.flags & JumpLinkBuilderToolParams.DRAW_ANIM_TRAJECTORY) != 0)
// ? JumpLinkBuilderToolParams.DRAW_ANIM_TRAJECTORY
// : 0;
// nk_layout_row_dynamic(ctx, 20, 1);
// newFlags |= nk_option_text(ctx, "Land Samples",
// (params.flags & JumpLinkBuilderToolParams.DRAW_LAND_SAMPLES) != 0)
// ? JumpLinkBuilderToolParams.DRAW_LAND_SAMPLES
// : 0;
// nk_layout_row_dynamic(ctx, 20, 1);
// newFlags |= nk_option_text(ctx, "All Annotations",
// (params.flags & JumpLinkBuilderToolParams.DRAW_ANNOTATIONS) != 0)
// ? JumpLinkBuilderToolParams.DRAW_ANNOTATIONS
// : 0;
// params.flags = newFlags;
// }
}
private void addOffMeshLink(JumpLink link, DemoInputGeomProvider geom, float agentRadius) {
int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP_AUTO;
int flags = SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
float[] prev = new float[3];
for (int i = 0; i < link.startSamples.Length; i++) {
float[] p = link.startSamples[i].p;
float[] q = link.endSamples[i].p;
if (i == 0 || vDist2D(prev, p) > agentRadius) {
geom.addOffMeshConnection(p, q, agentRadius, false, area, flags);
prev = p;
}
}
}
public override string getName() {
return "Annotation Builder";
}
}

View File

@ -0,0 +1,45 @@
/*
recast4j copyright (c) 2020-2021 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 DotRecast.Detour.Extras.Jumplink;
namespace DotRecast.Recast.Demo.Tools;
public class JumpLinkBuilderToolParams {
public const int DRAW_WALKABLE_SURFACE = 1 << 0;
public const int DRAW_WALKABLE_BORDER = 1 << 1;
public const int DRAW_SELECTED_EDGE = 1 << 2;
public const int DRAW_ANIM_TRAJECTORY = 1 << 3;
public const int DRAW_LAND_SAMPLES = 1 << 4;
public const int DRAW_COLLISION_SLICES = 1 << 5;
public const int DRAW_ANNOTATIONS = 1 << 6;
public int flags = DRAW_WALKABLE_SURFACE | DRAW_WALKABLE_BORDER | DRAW_SELECTED_EDGE | DRAW_ANIM_TRAJECTORY | DRAW_LAND_SAMPLES | DRAW_ANNOTATIONS;
public readonly float[] groundTolerance = new[] { 0.3f };
public readonly float[] climbDownDistance = new[] { 0.4f };
public readonly float[] climbDownMaxHeight = new[] { 3.2f };
public readonly float[] climbDownMinHeight = new[] { 1.5f };
public readonly float[] edgeJumpEndDistance = new[] { 2f };
public readonly float[] edgeJumpHeight = new[] { 0.4f };
public readonly float[] edgeJumpDownMaxHeight = new[] { 2.5f };
public readonly float[] edgeJumpUpMaxHeight = new[] { 0.3f };
public int buildTypes = (1 << (int)JumpLinkType.EDGE_CLIMB_DOWN) | (1 << (int)JumpLinkType.EDGE_JUMP);
}

View File

@ -0,0 +1,109 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 Silk.NET.Windowing;
using DotRecast.Core;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
namespace DotRecast.Recast.Demo.Tools;
public class OffMeshConnectionTool : Tool {
private Sample sample;
private bool hitPosSet;
private float[] hitPos;
private bool bidir;
public override void setSample(Sample m_sample) {
sample = m_sample;
}
public override void handleClick(float[] s, float[] p, bool shift) {
DemoInputGeomProvider geom = sample.getInputGeom();
if (geom == null) {
return;
}
if (shift) {
// Delete
// Find nearest link end-point
float nearestDist = float.MaxValue;
DemoOffMeshConnection nearestConnection = null;
foreach (DemoOffMeshConnection offMeshCon in geom.getOffMeshConnections()) {
float d = Math.Min(DemoMath.vDistSqr(p, offMeshCon.verts, 0), DemoMath.vDistSqr(p, offMeshCon.verts, 3));
if (d < nearestDist && Math.Sqrt(d) < sample.getSettingsUI().getAgentRadius()) {
nearestDist = d;
nearestConnection = offMeshCon;
}
}
if (nearestConnection != null) {
geom.getOffMeshConnections().Remove(nearestConnection);
}
} else {
// Create
if (!hitPosSet) {
hitPos = ArrayUtils.CopyOf(p, p.Length);
hitPosSet = true;
} else {
int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP;
int flags = SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
geom.addOffMeshConnection(hitPos, p, sample.getSettingsUI().getAgentRadius(), bidir, area, flags);
hitPosSet = false;
}
}
}
public override void handleRender(NavMeshRenderer renderer) {
if (sample == null) {
return;
}
RecastDebugDraw dd = renderer.getDebugDraw();
float s = sample.getSettingsUI().getAgentRadius();
if (hitPosSet) {
dd.debugDrawCross(hitPos[0], hitPos[1] + 0.1f, hitPos[2], s, duRGBA(0, 0, 0, 128), 2.0f);
}
DemoInputGeomProvider geom = sample.getInputGeom();
if (geom != null) {
renderer.drawOffMeshConnections(geom, true);
}
}
public override void layout(IWindow ctx) {
// nk_layout_row_dynamic(ctx, 20, 1);
// bidir = !nk_option_label(ctx, "One Way", !bidir);
// nk_layout_row_dynamic(ctx, 20, 1);
// bidir = nk_option_label(ctx, "Bidirectional", bidir);
}
public override string getName() {
return "Create Off-Mesh Links";
}
public override void handleUpdate(float dt) {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,171 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using DotRecast.Detour;
namespace DotRecast.Recast.Demo.Tools;
public static class PathUtils {
private readonly static int MAX_STEER_POINTS = 3;
public static SteerTarget getSteerTarget(NavMeshQuery navQuery, float[] startPos, float[] endPos,
float minTargetDist, List<long> path) {
// Find steer target.
Result<List<StraightPathItem>> result = navQuery.findStraightPath(startPos, endPos, path, MAX_STEER_POINTS, 0);
if (result.failed()) {
return null;
}
List<StraightPathItem> straightPath = result.result;
float[] steerPoints = new float[straightPath.Count * 3];
for (int i = 0; i < straightPath.Count; i++) {
steerPoints[i * 3] = straightPath[i].getPos()[0];
steerPoints[i * 3 + 1] = straightPath[i].getPos()[1];
steerPoints[i * 3 + 2] = straightPath[i].getPos()[2];
}
// Find vertex far enough to steer to.
int ns = 0;
while (ns < straightPath.Count) {
// Stop at Off-Mesh link or when point is further than slop away.
if (((straightPath[ns].getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
|| !inRange(straightPath[ns].getPos(), startPos, minTargetDist, 1000.0f))
break;
ns++;
}
// Failed to find good point to steer to.
if (ns >= straightPath.Count)
return null;
float[] steerPos = new float[] { straightPath[ns].getPos()[0], startPos[1],
straightPath[ns].getPos()[2] };
int steerPosFlag = straightPath[ns].getFlags();
long steerPosRef = straightPath[ns].getRef();
SteerTarget target = new SteerTarget(steerPos, steerPosFlag, steerPosRef, steerPoints);
return target;
}
public static bool inRange(float[] v1, float[] v2, float r, float h) {
float dx = v2[0] - v1[0];
float dy = v2[1] - v1[1];
float dz = v2[2] - v1[2];
return (dx * dx + dz * dz) < r * r && Math.Abs(dy) < h;
}
public static List<long> fixupCorridor(List<long> path, List<long> visited) {
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = path.Count - 1; i >= 0; --i) {
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found)
break;
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1)
return path;
// Concatenate paths.
// Adjust beginning of the buffer to include the visited.
int req = visited.Count - furthestVisited;
int orig = Math.Min(furthestPath + 1, path.Count);
int size = Math.Max(0, path.Count - orig);
List<long> fixupPath = new();
// Store visited
for (int i = 0; i < req; ++i) {
fixupPath.Add(visited[(visited.Count - 1) - i]);
}
for (int i = 0; i < size; i++) {
fixupPath.Add(path[orig + i]);
}
return fixupPath;
}
// This function checks if the path has a small U-turn, that is,
// a polygon further in the path is adjacent to the first polygon
// in the path. If that happens, a shortcut is taken.
// This can happen if the target (T) location is at tile boundary,
// and we're (S) approaching it parallel to the tile edge.
// The choice at the vertex can be arbitrary,
// +---+---+
// |:::|:::|
// +-S-+-T-+
// |:::| | <-- the step can end up in here, resulting U-turn path.
// +---+---+
public static List<long> fixupShortcuts(List<long> path, NavMeshQuery navQuery) {
if (path.Count < 3) {
return path;
}
// Get connected polygons
List<long> neis = new();
Result<Tuple<MeshTile, Poly>> tileAndPoly = navQuery.getAttachedNavMesh().getTileAndPolyByRef(path[0]);
if (tileAndPoly.failed()) {
return path;
}
MeshTile tile = tileAndPoly.result.Item1;
Poly poly = tileAndPoly.result.Item2;
for (int k = tile.polyLinks[poly.index]; k != NavMesh.DT_NULL_LINK; k = tile.links[k].next) {
Link link = tile.links[k];
if (link.refs != 0) {
neis.Add(link.refs);
}
}
// If any of the neighbour polygons is within the next few polygons
// in the path, short cut to that polygon directly.
int maxLookAhead = 6;
int cut = 0;
for (int i = Math.Min(maxLookAhead, path.Count) - 1; i > 1 && cut == 0; i--) {
for (int j = 0; j < neis.Count; j++) {
if (path[i] == neis[j]) {
cut = i;
break;
}
}
}
if (cut > 1) {
List<long> shortcut = new();
shortcut.Add(path[0]);
shortcut.AddRange(path.GetRange(cut, path.Count - cut));
return shortcut;
}
return path;
}
}

View File

@ -0,0 +1,110 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2021 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;
namespace DotRecast.Recast.Demo.Tools;
public class PolyUtils {
public static bool pointInPoly(float[] verts, float[] p) {
int i, j;
bool c = false;
for (i = 0, j = verts.Length / 3 - 1; i < verts.Length / 3; j = i++) {
float[] vi = new float[] { verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2] };
float[] vj = new float[] { verts[j * 3], verts[j * 3 + 1], verts[j * 3 + 2] };
if (((vi[2] > p[2]) != (vj[2] > p[2]))
&& (p[0] < (vj[0] - vi[0]) * (p[2] - vi[2]) / (vj[2] - vi[2]) + vi[0])) {
c = !c;
}
}
return c;
}
public static int offsetPoly(float[] verts, int nverts, float offset, float[] outVerts, int maxOutVerts) {
float MITER_LIMIT = 1.20f;
int n = 0;
for (int i = 0; i < nverts; i++) {
int a = (i + nverts - 1) % nverts;
int b = i;
int c = (i + 1) % nverts;
int va = a * 3;
int vb = b * 3;
int vc = c * 3;
float dx0 = verts[vb] - verts[va];
float dy0 = verts[vb + 2] - verts[va + 2];
float d0 = dx0 * dx0 + dy0 * dy0;
if (d0 > 1e-6f) {
d0 = (float) (1.0f / Math.Sqrt(d0));
dx0 *= d0;
dy0 *= d0;
}
float dx1 = verts[vc] - verts[vb];
float dy1 = verts[vc + 2] - verts[vb + 2];
float d1 = dx1 * dx1 + dy1 * dy1;
if (d1 > 1e-6f) {
d1 = (float) (1.0f / Math.Sqrt(d1));
dx1 *= d1;
dy1 *= d1;
}
float dlx0 = -dy0;
float dly0 = dx0;
float dlx1 = -dy1;
float dly1 = dx1;
float cross = dx1 * dy0 - dx0 * dy1;
float dmx = (dlx0 + dlx1) * 0.5f;
float dmy = (dly0 + dly1) * 0.5f;
float dmr2 = dmx * dmx + dmy * dmy;
bool bevel = dmr2 * MITER_LIMIT * MITER_LIMIT < 1.0f;
if (dmr2 > 1e-6f) {
float scale = 1.0f / dmr2;
dmx *= scale;
dmy *= scale;
}
if (bevel && cross < 0.0f) {
if (n + 2 >= maxOutVerts) {
return 0;
}
float d = (1.0f - (dx0 * dx1 + dy0 * dy1)) * 0.5f;
outVerts[n * 3 + 0] = verts[vb] + (-dlx0 + dx0 * d) * offset;
outVerts[n * 3 + 1] = verts[vb + 1];
outVerts[n * 3 + 2] = verts[vb + 2] + (-dly0 + dy0 * d) * offset;
n++;
outVerts[n * 3 + 0] = verts[vb] + (-dlx1 - dx1 * d) * offset;
outVerts[n * 3 + 1] = verts[vb + 1];
outVerts[n * 3 + 2] = verts[vb + 2] + (-dly1 - dy1 * d) * offset;
n++;
} else {
if (n + 1 >= maxOutVerts) {
return 0;
}
outVerts[n * 3 + 0] = verts[vb] - dmx * offset;
outVerts[n * 3 + 1] = verts[vb + 1];
outVerts[n * 3 + 2] = verts[vb + 2] - dmy * offset;
n++;
}
}
return n;
}
}

View File

@ -0,0 +1,15 @@
namespace DotRecast.Recast.Demo.Tools;
public class SteerTarget {
public readonly float[] steerPos;
public readonly int steerPosFlag;
public readonly long steerPosRef;
public readonly float[] steerPoints;
public SteerTarget(float[] steerPos, int steerPosFlag, long steerPosRef, float[] steerPoints) {
this.steerPos = steerPos;
this.steerPosFlag = steerPosFlag;
this.steerPosRef = steerPosRef;
this.steerPoints = steerPoints;
}
}

View File

@ -0,0 +1,880 @@
using System;
using System.Collections.Generic;
using Silk.NET.Windowing;
using DotRecast.Core;
using DotRecast.Detour;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class TestNavmeshTool : Tool {
private readonly static int MAX_POLYS = 256;
private readonly static int MAX_SMOOTH = 2048;
private Sample m_sample;
private ToolMode m_toolMode = ToolMode.PATHFIND_FOLLOW;
private bool m_sposSet;
private bool m_eposSet;
private float[] m_spos;
private float[] m_epos;
private readonly DefaultQueryFilter m_filter;
private readonly float[] m_polyPickExt = new float[] { 2, 4, 2 };
private long m_startRef;
private long m_endRef;
private float[] m_hitPos;
private float m_distanceToWall;
private float[] m_hitNormal;
private List<StraightPathItem> m_straightPath;
private int m_straightPathOptions;
private List<long> m_polys;
private bool m_hitResult;
private List<long> m_parent;
private float m_neighbourhoodRadius;
private readonly float[] m_queryPoly = new float[12];
private List<float[]> m_smoothPath;
private Status m_pathFindStatus = Status.FAILURE;
private bool enableRaycast = true;
private readonly List<float[]> randomPoints = new();
private bool constrainByCircle;
private enum ToolMode {
PATHFIND_FOLLOW,
PATHFIND_STRAIGHT,
PATHFIND_SLICED,
DISTANCE_TO_WALL,
RAYCAST,
FIND_POLYS_IN_CIRCLE,
FIND_POLYS_IN_SHAPE,
FIND_LOCAL_NEIGHBOURHOOD,
RANDOM_POINTS_IN_CIRCLE
}
public TestNavmeshTool() {
m_filter = new DefaultQueryFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 1f, 1f, 1f, 2f, 1.5f });
}
public override void setSample(Sample m_sample) {
this.m_sample = m_sample;
}
public override void handleClick(float[] s, float[] p, bool shift) {
if (shift) {
m_sposSet = true;
m_spos = ArrayUtils.CopyOf(p, p.Length);
} else {
m_eposSet = true;
m_epos = ArrayUtils.CopyOf(p, p.Length);
}
recalc();
}
public override void layout(IWindow ctx) {
ToolMode previousToolMode = m_toolMode;
int previousStraightPathOptions = m_straightPathOptions;
int previousIncludeFlags = m_filter.getIncludeFlags();
int previousExcludeFlags = m_filter.getExcludeFlags();
bool previousConstrainByCircle = constrainByCircle;
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Pathfind Follow", m_toolMode == ToolMode.PATHFIND_FOLLOW)) {
// m_toolMode = ToolMode.PATHFIND_FOLLOW;
// }
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Pathfind Straight", m_toolMode == ToolMode.PATHFIND_STRAIGHT)) {
// m_toolMode = ToolMode.PATHFIND_STRAIGHT;
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_label(ctx, "Vertices at crossings", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "None", m_straightPathOptions == 0)) {
// m_straightPathOptions = 0;
// }
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Area", m_straightPathOptions == NavMeshQuery.DT_STRAIGHTPATH_AREA_CROSSINGS)) {
// m_straightPathOptions = NavMeshQuery.DT_STRAIGHTPATH_AREA_CROSSINGS;
// }
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "All", m_straightPathOptions == NavMeshQuery.DT_STRAIGHTPATH_ALL_CROSSINGS)) {
// m_straightPathOptions = NavMeshQuery.DT_STRAIGHTPATH_ALL_CROSSINGS;
// }
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
// }
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Pathfind Sliced", m_toolMode == ToolMode.PATHFIND_SLICED)) {
// m_toolMode = ToolMode.PATHFIND_SLICED;
// }
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Distance to Wall", m_toolMode == ToolMode.DISTANCE_TO_WALL)) {
// m_toolMode = ToolMode.DISTANCE_TO_WALL;
// }
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Raycast", m_toolMode == ToolMode.RAYCAST)) {
// m_toolMode = ToolMode.RAYCAST;
// }
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Find Polys in Circle", m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE)) {
// m_toolMode = ToolMode.FIND_POLYS_IN_CIRCLE;
// }
// if (nk_option_label(ctx, "Find Polys in Shape", m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE)) {
// m_toolMode = ToolMode.FIND_POLYS_IN_SHAPE;
// }
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Find Local Neighbourhood", m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD)) {
// m_toolMode = ToolMode.FIND_LOCAL_NEIGHBOURHOOD;
// }
// if (nk_option_label(ctx, "Random Points in Circle", m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE)) {
// m_toolMode = ToolMode.RANDOM_POINTS_IN_CIRCLE;
// nk_layout_row_dynamic(ctx, 20, 1);
// constrainByCircle = nk_check_text(ctx, "Constrained", constrainByCircle);
// }
//
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_label(ctx, "Include Flags", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// int includeFlags = 0;
// if (nk_option_label(ctx, "Walk",
// (m_filter.getIncludeFlags() & SampleAreaModifications.SAMPLE_POLYFLAGS_WALK) != 0)) {
// includeFlags |= SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
// }
// if (nk_option_label(ctx, "Swim",
// (m_filter.getIncludeFlags() & SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM) != 0)) {
// includeFlags |= SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
// }
// if (nk_option_label(ctx, "Door",
// (m_filter.getIncludeFlags() & SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR) != 0)) {
// includeFlags |= SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
// }
// if (nk_option_label(ctx, "Jump",
// (m_filter.getIncludeFlags() & SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP) != 0)) {
// includeFlags |= SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
// }
// m_filter.setIncludeFlags(includeFlags);
//
// nk_layout_row_dynamic(ctx, 5, 1);
// nk_spacing(ctx, 1);
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_label(ctx, "Exclude Flags", NK_TEXT_ALIGN_LEFT);
// nk_layout_row_dynamic(ctx, 20, 1);
// int excludeFlags = 0;
// if (nk_option_label(ctx, "Walk",
// (m_filter.getExcludeFlags() & SampleAreaModifications.SAMPLE_POLYFLAGS_WALK) != 0)) {
// excludeFlags |= SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
// }
// if (nk_option_label(ctx, "Swim",
// (m_filter.getExcludeFlags() & SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM) != 0)) {
// excludeFlags |= SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
// }
// if (nk_option_label(ctx, "Door",
// (m_filter.getExcludeFlags() & SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR) != 0)) {
// excludeFlags |= SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
// }
// if (nk_option_label(ctx, "Jump",
// (m_filter.getExcludeFlags() & SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP) != 0)) {
// excludeFlags |= SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
// }
// m_filter.setExcludeFlags(excludeFlags);
//
// nk_layout_row_dynamic(ctx, 30, 1);
// bool previousEnableRaycast = enableRaycast;
// enableRaycast = nk_check_label(ctx, "Raycast shortcuts", enableRaycast);
//
// if (previousToolMode != m_toolMode || m_straightPathOptions != previousStraightPathOptions
// || previousIncludeFlags != includeFlags || previousExcludeFlags != excludeFlags
// || previousEnableRaycast != enableRaycast || previousConstrainByCircle != constrainByCircle) {
// recalc();
// }
}
public override string getName() {
return "Test Navmesh";
}
private void recalc() {
if (m_sample.getNavMesh() == null) {
return;
}
NavMeshQuery m_navQuery = m_sample.getNavMeshQuery();
if (m_sposSet) {
m_startRef = m_navQuery.findNearestPoly(m_spos, m_polyPickExt, m_filter).result.getNearestRef();
} else {
m_startRef = 0;
}
if (m_eposSet) {
m_endRef = m_navQuery.findNearestPoly(m_epos, m_polyPickExt, m_filter).result.getNearestRef();
} else {
m_endRef = 0;
}
NavMesh m_navMesh = m_sample.getNavMesh();
if (m_toolMode == ToolMode.PATHFIND_FOLLOW) {
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
m_polys = m_navQuery.findPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue).result;
if (0 < m_polys.Count) {
List<long> polys = new(m_polys);
// Iterate over the path to find smooth path on the detail mesh surface.
float[] iterPos = m_navQuery.closestPointOnPoly(m_startRef, m_spos).result.getClosest();
float[] targetPos = m_navQuery.closestPointOnPoly(polys[polys.Count - 1], m_epos).result
.getClosest();
float STEP_SIZE = 0.5f;
float SLOP = 0.01f;
m_smoothPath = new();
m_smoothPath.Add(iterPos);
// Move towards target a small advancement at a time until target reached or
// when ran out of memory to store the path.
while (0 < polys.Count && m_smoothPath.Count < MAX_SMOOTH) {
// Find location to steer towards.
SteerTarget steerTarget = PathUtils.getSteerTarget(m_navQuery, iterPos, targetPos,
SLOP, polys);
if (null == steerTarget) {
break;
}
bool endOfPath = (steerTarget.steerPosFlag & NavMeshQuery.DT_STRAIGHTPATH_END) != 0
? true
: false;
bool offMeshConnection = (steerTarget.steerPosFlag
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 ? true : false;
// Find movement delta.
float[] delta = vSub(steerTarget.steerPos, iterPos);
float len = (float) Math.Sqrt(DemoMath.vDot(delta, delta));
// If the steer target is end of path or off-mesh link, do not move past the location.
if ((endOfPath || offMeshConnection) && len < STEP_SIZE) {
len = 1;
} else {
len = STEP_SIZE / len;
}
float[] moveTgt = vMad(iterPos, delta, len);
// Move
Result<MoveAlongSurfaceResult> result = m_navQuery.moveAlongSurface(polys[0], iterPos,
moveTgt, m_filter);
MoveAlongSurfaceResult moveAlongSurface = result.result;
iterPos = new float[3];
iterPos[0] = moveAlongSurface.getResultPos()[0];
iterPos[1] = moveAlongSurface.getResultPos()[1];
iterPos[2] = moveAlongSurface.getResultPos()[2];
List<long> visited = result.result.getVisited();
polys = PathUtils.fixupCorridor(polys, visited);
polys = PathUtils.fixupShortcuts(polys, m_navQuery);
Result<float> polyHeight = m_navQuery.getPolyHeight(polys[0], moveAlongSurface.getResultPos());
if (polyHeight.succeeded()) {
iterPos[1] = polyHeight.result;
}
// Handle end of path and off-mesh links when close enough.
if (endOfPath && PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f)) {
// Reached end of path.
vCopy(iterPos, targetPos);
if (m_smoothPath.Count < MAX_SMOOTH) {
m_smoothPath.Add(iterPos);
}
break;
} else if (offMeshConnection
&& PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f)) {
// Reached off-mesh connection.
// Advance the path up to and over the off-mesh connection.
long prevRef = 0;
long polyRef = polys[0];
int npos = 0;
while (npos < polys.Count && polyRef != steerTarget.steerPosRef) {
prevRef = polyRef;
polyRef = polys[npos];
npos++;
}
polys = polys.GetRange(npos, polys.Count - npos);
// Handle the connection.
Result<Tuple<float[], float[]>> offMeshCon = m_navMesh
.getOffMeshConnectionPolyEndPoints(prevRef, polyRef);
if (offMeshCon.succeeded()) {
float[] startPos = offMeshCon.result.Item1;
float[] endPos = offMeshCon.result.Item2;
if (m_smoothPath.Count < MAX_SMOOTH) {
m_smoothPath.Add(startPos);
// Hack to make the dotted path not visible during off-mesh connection.
if ((m_smoothPath.Count & 1) != 0) {
m_smoothPath.Add(startPos);
}
}
// Move position at the other side of the off-mesh link.
vCopy(iterPos, endPos);
iterPos[1] = m_navQuery.getPolyHeight(polys[0], iterPos).result;
}
}
// Store results.
if (m_smoothPath.Count < MAX_SMOOTH) {
m_smoothPath.Add(iterPos);
}
}
}
} else {
m_polys = null;
m_smoothPath = null;
}
} else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT) {
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
m_polys = m_navQuery.findPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue).result;
if (0 < m_polys.Count) {
// In case of partial path, make sure the end point is clamped to the last polygon.
float[] epos = new float[] { m_epos[0], m_epos[1], m_epos[2] };
if (m_polys[m_polys.Count - 1] != m_endRef) {
Result<ClosestPointOnPolyResult> result = m_navQuery
.closestPointOnPoly(m_polys[m_polys.Count - 1], m_epos);
if (result.succeeded()) {
epos = result.result.getClosest();
}
}
m_straightPath = m_navQuery.findStraightPath(m_spos, epos, m_polys, MAX_POLYS,
m_straightPathOptions).result;
}
} else {
m_straightPath = null;
}
} else if (m_toolMode == ToolMode.PATHFIND_SLICED) {
m_polys = null;
m_straightPath = null;
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
m_pathFindStatus = m_navQuery.initSlicedFindPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue);
}
} else if (m_toolMode == ToolMode.RAYCAST) {
m_straightPath = null;
if (m_sposSet && m_eposSet && m_startRef != 0) {
{
Result<RaycastHit> hit = m_navQuery.raycast(m_startRef, m_spos, m_epos, m_filter, 0, 0);
if (hit.succeeded()) {
m_polys = hit.result.path;
if (hit.result.t > 1) {
// No hit
m_hitPos = ArrayUtils.CopyOf(m_epos, m_epos.Length);
m_hitResult = false;
} else {
// Hit
m_hitPos = vLerp(m_spos, m_epos, hit.result.t);
m_hitNormal = ArrayUtils.CopyOf(hit.result.hitNormal, hit.result.hitNormal.Length);
m_hitResult = true;
}
// Adjust height.
if (hit.result.path.Count > 0) {
Result<float> result = m_navQuery
.getPolyHeight(hit.result.path[hit.result.path.Count - 1], m_hitPos);
if (result.succeeded()) {
m_hitPos[1] = result.result;
}
}
}
m_straightPath = new();
m_straightPath.Add(new StraightPathItem(m_spos, 0, 0));
m_straightPath.Add(new StraightPathItem(m_hitPos, 0, 0));
}
}
} else if (m_toolMode == ToolMode.DISTANCE_TO_WALL) {
m_distanceToWall = 0;
if (m_sposSet && m_startRef != 0) {
m_distanceToWall = 0.0f;
Result<FindDistanceToWallResult> result = m_navQuery.findDistanceToWall(m_startRef, m_spos, 100.0f,
m_filter);
if (result.succeeded()) {
m_distanceToWall = result.result.getDistance();
m_hitPos = result.result.getPosition();
m_hitNormal = result.result.getNormal();
}
}
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE) {
if (m_sposSet && m_startRef != 0 && m_eposSet) {
float dx = m_epos[0] - m_spos[0];
float dz = m_epos[2] - m_spos[2];
float dist = (float) Math.Sqrt(dx * dx + dz * dz);
Result<FindPolysAroundResult> result = m_navQuery.findPolysAroundCircle(m_startRef, m_spos, dist,
m_filter);
if (result.succeeded()) {
m_polys = result.result.getRefs();
m_parent = result.result.getParentRefs();
}
}
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE) {
if (m_sposSet && m_startRef != 0 && m_eposSet) {
float nx = (m_epos[2] - m_spos[2]) * 0.25f;
float nz = -(m_epos[0] - m_spos[0]) * 0.25f;
float agentHeight = m_sample != null ? m_sample.getSettingsUI().getAgentHeight() : 0;
m_queryPoly[0] = m_spos[0] + nx * 1.2f;
m_queryPoly[1] = m_spos[1] + agentHeight / 2;
m_queryPoly[2] = m_spos[2] + nz * 1.2f;
m_queryPoly[3] = m_spos[0] - nx * 1.3f;
m_queryPoly[4] = m_spos[1] + agentHeight / 2;
m_queryPoly[5] = m_spos[2] - nz * 1.3f;
m_queryPoly[6] = m_epos[0] - nx * 0.8f;
m_queryPoly[7] = m_epos[1] + agentHeight / 2;
m_queryPoly[8] = m_epos[2] - nz * 0.8f;
m_queryPoly[9] = m_epos[0] + nx;
m_queryPoly[10] = m_epos[1] + agentHeight / 2;
m_queryPoly[11] = m_epos[2] + nz;
Result<FindPolysAroundResult> result = m_navQuery.findPolysAroundShape(m_startRef, m_queryPoly, m_filter);
if (result.succeeded()) {
m_polys = result.result.getRefs();
m_parent = result.result.getParentRefs();
}
}
} else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD) {
if (m_sposSet && m_startRef != 0) {
m_neighbourhoodRadius = m_sample.getSettingsUI().getAgentRadius() * 20.0f;
Result<FindLocalNeighbourhoodResult> result = m_navQuery.findLocalNeighbourhood(m_startRef, m_spos,
m_neighbourhoodRadius, m_filter);
if (result.succeeded()) {
m_polys = result.result.getRefs();
m_parent = result.result.getParentRefs();
}
}
} else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE) {
randomPoints.Clear();
if (m_sposSet && m_startRef != 0 && m_eposSet) {
float dx = m_epos[0] - m_spos[0];
float dz = m_epos[2] - m_spos[2];
float dist = (float) Math.Sqrt(dx * dx + dz * dz);
PolygonByCircleConstraint constraint = constrainByCircle ? PolygonByCircleConstraint.strict()
: PolygonByCircleConstraint.noop();
for (int i = 0; i < 200; i++) {
Result<FindRandomPointResult> result = m_navQuery.findRandomPointAroundCircle(m_startRef, m_spos, dist,
m_filter, new NavMeshQuery.FRand(), constraint);
if (result.succeeded()) {
randomPoints.Add(result.result.getRandomPt());
}
}
}
}
}
public override void handleRender(NavMeshRenderer renderer) {
if (m_sample == null) {
return;
}
RecastDebugDraw dd = renderer.getDebugDraw();
int startCol = duRGBA(128, 25, 0, 192);
int endCol = duRGBA(51, 102, 0, 129);
int pathCol = duRGBA(0, 0, 0, 64);
float agentRadius = m_sample.getSettingsUI().getAgentRadius();
float agentHeight = m_sample.getSettingsUI().getAgentHeight();
float agentClimb = m_sample.getSettingsUI().getAgentMaxClimb();
if (m_sposSet) {
drawAgent(dd, m_spos, startCol);
}
if (m_eposSet) {
drawAgent(dd, m_epos, endCol);
}
dd.depthMask(true);
NavMesh m_navMesh = m_sample.getNavMesh();
if (m_navMesh == null) {
return;
}
if (m_toolMode == ToolMode.PATHFIND_FOLLOW) {
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
dd.debugDrawNavMeshPoly(m_navMesh, m_endRef, endCol);
if (m_polys != null) {
foreach (long poly in m_polys) {
if (poly == m_startRef || poly == m_endRef) {
continue;
}
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
}
}
if (m_smoothPath != null) {
dd.depthMask(false);
int spathCol = duRGBA(0, 0, 0, 220);
dd.begin(LINES, 3.0f);
for (int i = 0; i < m_smoothPath.Count; ++i) {
dd.vertex(m_smoothPath[i][0], m_smoothPath[i][1] + 0.1f, m_smoothPath[i][2], spathCol);
}
dd.end();
dd.depthMask(true);
}
/*
if (m_pathIterNum)
{
duDebugDrawNavMeshPoly(&dd, *m_navMesh, m_pathIterPolys[0], DebugDraw.duRGBA(255,255,255,128));
dd.depthMask(false);
dd.begin(DebugDrawPrimitives.LINES, 1.0f);
int prevCol = DebugDraw.duRGBA(255,192,0,220);
int curCol = DebugDraw.duRGBA(255,255,255,220);
int steerCol = DebugDraw.duRGBA(0,192,255,220);
dd.vertex(m_prevIterPos[0],m_prevIterPos[1]-0.3f,m_prevIterPos[2], prevCol);
dd.vertex(m_prevIterPos[0],m_prevIterPos[1]+0.3f,m_prevIterPos[2], prevCol);
dd.vertex(m_iterPos[0],m_iterPos[1]-0.3f,m_iterPos[2], curCol);
dd.vertex(m_iterPos[0],m_iterPos[1]+0.3f,m_iterPos[2], curCol);
dd.vertex(m_prevIterPos[0],m_prevIterPos[1]+0.3f,m_prevIterPos[2], prevCol);
dd.vertex(m_iterPos[0],m_iterPos[1]+0.3f,m_iterPos[2], prevCol);
dd.vertex(m_prevIterPos[0],m_prevIterPos[1]+0.3f,m_prevIterPos[2], steerCol);
dd.vertex(m_steerPos[0],m_steerPos[1]+0.3f,m_steerPos[2], steerCol);
for (int i = 0; i < m_steerPointCount-1; ++i)
{
dd.vertex(m_steerPoints[i*3+0],m_steerPoints[i*3+1]+0.2f,m_steerPoints[i*3+2], duDarkenCol(steerCol));
dd.vertex(m_steerPoints[(i+1)*3+0],m_steerPoints[(i+1)*3+1]+0.2f,m_steerPoints[(i+1)*3+2], duDarkenCol(steerCol));
}
dd.end();
dd.depthMask(true);
}
*/
} else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT || m_toolMode == ToolMode.PATHFIND_SLICED) {
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
dd.debugDrawNavMeshPoly(m_navMesh, m_endRef, endCol);
if (m_polys != null) {
foreach (long poly in m_polys) {
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
}
}
if (m_straightPath != null) {
dd.depthMask(false);
int spathCol = duRGBA(64, 16, 0, 220);
int offMeshCol = duRGBA(128, 96, 0, 220);
dd.begin(LINES, 2.0f);
for (int i = 0; i < m_straightPath.Count - 1; ++i) {
StraightPathItem straightPathItem = m_straightPath[i];
StraightPathItem straightPathItem2 = m_straightPath[i + 1];
int col;
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
col = offMeshCol;
} else {
col = spathCol;
}
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
straightPathItem.getPos()[2], col);
dd.vertex(straightPathItem2.getPos()[0], straightPathItem2.getPos()[1] + 0.4f,
straightPathItem2.getPos()[2], col);
}
dd.end();
dd.begin(POINTS, 6.0f);
for (int i = 0; i < m_straightPath.Count; ++i) {
StraightPathItem straightPathItem = m_straightPath[i];
int col;
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_START) != 0) {
col = startCol;
} else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_END) != 0) {
col = endCol;
} else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
col = offMeshCol;
} else {
col = spathCol;
}
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
straightPathItem.getPos()[2], col);
}
dd.end();
dd.depthMask(true);
}
} else if (m_toolMode == ToolMode.RAYCAST) {
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
if (m_straightPath != null) {
if (m_polys != null) {
foreach (long poly in m_polys) {
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
}
}
dd.depthMask(false);
int spathCol = m_hitResult ? duRGBA(64, 16, 0, 220) : duRGBA(240, 240, 240, 220);
dd.begin(LINES, 2.0f);
for (int i = 0; i < m_straightPath.Count - 1; ++i) {
StraightPathItem straightPathItem = m_straightPath[i];
StraightPathItem straightPathItem2 = m_straightPath[i + 1];
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
straightPathItem.getPos()[2], spathCol);
dd.vertex(straightPathItem2.getPos()[0], straightPathItem2.getPos()[1] + 0.4f,
straightPathItem2.getPos()[2], spathCol);
}
dd.end();
dd.begin(POINTS, 4.0f);
for (int i = 0; i < m_straightPath.Count; ++i) {
StraightPathItem straightPathItem = m_straightPath[i];
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
straightPathItem.getPos()[2], spathCol);
}
dd.end();
if (m_hitResult) {
int hitCol = duRGBA(0, 0, 0, 128);
dd.begin(LINES, 2.0f);
dd.vertex(m_hitPos[0], m_hitPos[1] + 0.4f, m_hitPos[2], hitCol);
dd.vertex(m_hitPos[0] + m_hitNormal[0] * agentRadius,
m_hitPos[1] + 0.4f + m_hitNormal[1] * agentRadius,
m_hitPos[2] + m_hitNormal[2] * agentRadius, hitCol);
dd.end();
}
dd.depthMask(true);
}
} else if (m_toolMode == ToolMode.DISTANCE_TO_WALL) {
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
dd.depthMask(false);
if (m_spos != null) {
dd.debugDrawCircle(m_spos[0], m_spos[1] + agentHeight / 2, m_spos[2], m_distanceToWall,
duRGBA(64, 16, 0, 220), 2.0f);
}
if (m_hitPos != null) {
dd.begin(LINES, 3.0f);
dd.vertex(m_hitPos[0], m_hitPos[1] + 0.02f, m_hitPos[2], duRGBA(0, 0, 0, 192));
dd.vertex(m_hitPos[0], m_hitPos[1] + agentHeight, m_hitPos[2], duRGBA(0, 0, 0, 192));
dd.end();
}
dd.depthMask(true);
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE) {
if (m_polys != null) {
for (int i = 0; i < m_polys.Count; i++) {
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
dd.depthMask(false);
if (m_parent[i] != 0) {
dd.depthMask(false);
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
dd.debugDrawArc(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], 0.25f, 0.0f, 0.4f,
duRGBA(0, 0, 0, 128), 2.0f);
dd.depthMask(true);
}
dd.depthMask(true);
}
}
if (m_sposSet && m_eposSet) {
dd.depthMask(false);
float dx = m_epos[0] - m_spos[0];
float dz = m_epos[2] - m_spos[2];
float dist = (float) Math.Sqrt(dx * dx + dz * dz);
dd.debugDrawCircle(m_spos[0], m_spos[1] + agentHeight / 2, m_spos[2], dist, duRGBA(64, 16, 0, 220),
2.0f);
dd.depthMask(true);
}
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE) {
if (m_polys != null) {
for (int i = 0; i < m_polys.Count; i++) {
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
dd.depthMask(false);
if (m_parent[i] != 0) {
dd.depthMask(false);
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
dd.debugDrawArc(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], 0.25f, 0.0f, 0.4f,
duRGBA(0, 0, 0, 128), 2.0f);
dd.depthMask(true);
}
dd.depthMask(true);
}
}
if (m_sposSet && m_eposSet) {
dd.depthMask(false);
int col = duRGBA(64, 16, 0, 220);
dd.begin(LINES, 2.0f);
for (int i = 0, j = 3; i < 4; j = i++) {
dd.vertex(m_queryPoly[j * 3], m_queryPoly[j * 3 + 1], m_queryPoly[j * 3 + 2], col);
dd.vertex(m_queryPoly[i * 3], m_queryPoly[i * 3 + 1], m_queryPoly[i * 3 + 2], col);
}
dd.end();
dd.depthMask(true);
}
} else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD) {
if (m_polys != null) {
for (int i = 0; i < m_polys.Count; i++) {
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
dd.depthMask(false);
if (m_parent[i] != 0) {
dd.depthMask(false);
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
dd.debugDrawArc(p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], 0.25f, 0.0f, 0.4f,
duRGBA(0, 0, 0, 128), 2.0f);
dd.depthMask(true);
}
dd.depthMask(true);
if (m_sample.getNavMeshQuery() != null) {
Result<GetPolyWallSegmentsResult> result = m_sample.getNavMeshQuery()
.getPolyWallSegments(m_polys[i], false, m_filter);
if (result.succeeded()) {
dd.begin(LINES, 2.0f);
GetPolyWallSegmentsResult wallSegments = result.result;
for (int j = 0; j < wallSegments.getSegmentVerts().Count; ++j) {
float[] s = wallSegments.getSegmentVerts()[j];
float[] s3 = new float[] { s[3], s[4], s[5] };
// Skip too distant segments.
Tuple<float, float> distSqr = DetourCommon.distancePtSegSqr2D(m_spos, s, 0, 3);
if (distSqr.Item1 > DemoMath.sqr(m_neighbourhoodRadius)) {
continue;
}
float[] delta = vSub(s3, s);
float[] p0 = vMad(s, delta, 0.5f);
float[] norm = new float[] { delta[2], 0, -delta[0] };
vNormalize(norm);
float[] p1 = vMad(p0, norm, agentRadius * 0.5f);
// Skip backfacing segments.
if (wallSegments.getSegmentRefs()[j] != 0) {
int col = duRGBA(255, 255, 255, 32);
dd.vertex(s[0], s[1] + agentClimb, s[2], col);
dd.vertex(s[3], s[4] + agentClimb, s[5], col);
} else {
int col = duRGBA(192, 32, 16, 192);
if (DetourCommon.triArea2D(m_spos, s, s3) < 0.0f) {
col = duRGBA(96, 32, 16, 192);
}
dd.vertex(p0[0], p0[1] + agentClimb, p0[2], col);
dd.vertex(p1[0], p1[1] + agentClimb, p1[2], col);
dd.vertex(s[0], s[1] + agentClimb, s[2], col);
dd.vertex(s[3], s[4] + agentClimb, s[5], col);
}
}
dd.end();
}
}
dd.depthMask(true);
}
if (m_sposSet) {
dd.depthMask(false);
dd.debugDrawCircle(m_spos[0], m_spos[1] + agentHeight / 2, m_spos[2], m_neighbourhoodRadius,
duRGBA(64, 16, 0, 220), 2.0f);
dd.depthMask(true);
}
}
} else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE) {
dd.depthMask(false);
dd.begin(POINTS, 4.0f);
int col = duRGBA(64, 16, 0, 220);
foreach (float[] point in randomPoints) {
dd.vertex(point[0], point[1] + 0.1f, point[2], col);
}
dd.end();
if (m_sposSet && m_eposSet) {
dd.depthMask(false);
float dx = m_epos[0] - m_spos[0];
float dz = m_epos[2] - m_spos[2];
float dist = (float) Math.Sqrt(dx * dx + dz * dz);
dd.debugDrawCircle(m_spos[0], m_spos[1] + agentHeight / 2, m_spos[2], dist, duRGBA(64, 16, 0, 220),
2.0f);
dd.depthMask(true);
}
dd.depthMask(true);
}
}
private void drawAgent(RecastDebugDraw dd, float[] pos, int col) {
float r = m_sample.getSettingsUI().getAgentRadius();
float h = m_sample.getSettingsUI().getAgentHeight();
float c = m_sample.getSettingsUI().getAgentMaxClimb();
dd.depthMask(false);
// Agent dimensions.
dd.debugDrawCylinderWire(pos[0] - r, pos[1] + 0.02f, pos[2] - r, pos[0] + r, pos[1] + h, pos[2] + r, col, 2.0f);
dd.debugDrawCircle(pos[0], pos[1] + c, pos[2], r, duRGBA(0, 0, 0, 64), 1.0f);
int colb = duRGBA(0, 0, 0, 196);
dd.begin(LINES);
dd.vertex(pos[0], pos[1] - c, pos[2], colb);
dd.vertex(pos[0], pos[1] + c, pos[2], colb);
dd.vertex(pos[0] - r / 2, pos[1] + 0.02f, pos[2], colb);
dd.vertex(pos[0] + r / 2, pos[1] + 0.02f, pos[2], colb);
dd.vertex(pos[0], pos[1] + 0.02f, pos[2] - r / 2, colb);
dd.vertex(pos[0], pos[1] + 0.02f, pos[2] + r / 2, colb);
dd.end();
dd.depthMask(true);
}
private float[] getPolyCenter(NavMesh navMesh, long refs) {
float[] center = new float[3];
center[0] = 0;
center[1] = 0;
center[2] = 0;
Result<Tuple<MeshTile, Poly>> tileAndPoly = navMesh.getTileAndPolyByRef(refs);
if (tileAndPoly.succeeded()) {
MeshTile tile = tileAndPoly.result.Item1;
Poly poly = tileAndPoly.result.Item2;
for (int i = 0; i < poly.vertCount; ++i) {
int v = poly.verts[i] * 3;
center[0] += tile.data.verts[v];
center[1] += tile.data.verts[v + 1];
center[2] += tile.data.verts[v + 2];
}
float s = 1.0f / poly.vertCount;
center[0] *= s;
center[1] *= s;
center[2] *= s;
}
return center;
}
public override void handleUpdate(float dt) {
// TODO Auto-generated method stub
if (m_toolMode == ToolMode.PATHFIND_SLICED) {
NavMeshQuery m_navQuery = m_sample.getNavMeshQuery();
if (m_pathFindStatus.isInProgress()) {
m_pathFindStatus = m_navQuery.updateSlicedFindPath(1).status;
}
if (m_pathFindStatus.isSuccess()) {
m_polys = m_navQuery.finalizeSlicedFindPath().result;
m_straightPath = null;
if (m_polys != null) {
// In case of partial path, make sure the end point is clamped to the last polygon.
float[] epos = new float[3];
DetourCommon.vCopy(epos, m_epos);
if (m_polys[m_polys.Count - 1] != m_endRef) {
Result<ClosestPointOnPolyResult> result = m_navQuery
.closestPointOnPoly(m_polys[m_polys.Count - 1], m_epos);
if (result.succeeded()) {
epos = result.result.getClosest();
}
}
{
Result<List<StraightPathItem>> result = m_navQuery.findStraightPath(m_spos, epos, m_polys,
MAX_POLYS, NavMeshQuery.DT_STRAIGHTPATH_ALL_CROSSINGS);
if (result.succeeded())
{
m_straightPath = result.result;
}
}
}
m_pathFindStatus = Status.FAILURE;
}
}
}
}

View File

@ -0,0 +1,43 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 DotRecast.Recast.Demo.Draw;
using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Tools;
public abstract class Tool {
public abstract void setSample(Sample m_sample);
public abstract void handleClick(float[] s, float[] p, bool shift);
public abstract void handleRender(NavMeshRenderer renderer);
public abstract void handleUpdate(float dt);
public abstract void layout(IWindow ctx);
public abstract string getName();
public virtual void handleClickRay(float[] start, float[] direction, bool shift)
{
// ...
}
}

View File

@ -0,0 +1,26 @@
/*
recast4j copyright (c) 2021 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 Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Tools;
public interface ToolUIModule {
void layout(IWindow ctx);
}

View File

@ -0,0 +1,82 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2021 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 DotRecast.Core;
using DotRecast.Recast.Demo.UI;
using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Tools;
public class ToolsUI : NuklearUIModule {
//private readonly NkColor white = NkColor.create();
private Tool currentTool;
private bool enabled;
private readonly Tool[] tools;
public ToolsUI(params Tool[] tools) {
this.tools = tools;
}
public bool layout(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY) {
bool mouseInside = false;
// nk_rgb(255, 255, 255, white);
// try (MemoryStack stack = stackPush()) {
// NkRect rect = NkRect.mallocStack(stack);
// if (nk_begin(ctx, "Tools", nk_rect(5, 5, 250, height - 10, rect), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE)) {
// if (enabled) {
// foreach (Tool tool in tools) {
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, tool.getName(), tool == currentTool)) {
// currentTool = tool;
// }
// }
// nk_layout_row_dynamic(ctx, 3, 1);
// nk_spacing(ctx, 1);
// if (currentTool != null) {
// currentTool.layout(ctx);
// }
// }
// nk_window_get_bounds(ctx, rect);
// if (mouseX >= rect.x() && mouseX <= rect.x() + rect.w() && mouseY >= rect.y() && mouseY <= rect.y() + rect.h()) {
// mouseInside = true;
// }
// }
// nk_end(ctx);
// }
return mouseInside;
}
public void setEnabled(bool enabled) {
this.enabled = enabled;
}
public Tool getTool() {
return currentTool;
}
public void setSample(Sample sample) {
tools.forEach(t => t.setSample(sample));
}
public void handleUpdate(float dt) {
tools.forEach(t => t.handleUpdate(dt));
}
}

View File

@ -0,0 +1,131 @@
/*
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using Silk.NET.Input;
using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.UI;
public class Mouse {
private double x;
private double y;
private double scrollX;
private double scrollY;
private double px;
private double py;
private double pScrollX;
private double pScrollY;
private readonly HashSet<int> pressed = new();
private readonly List<MouseListener> listeners = new();
public Mouse(IInputContext input)
{
foreach (IMouse mouse in input.Mice)
{
mouse.MouseDown += (mouse, button) => buttonPress((int)button, 0);
mouse.MouseUp += (mouse, button) => buttonRelease((int)button, 0);
// if (action == GLFW_PRESS) {
// buttonPress(button, mods);
// } else if (action == GLFW_RELEASE) {
// buttonRelease(button, mods);
// }
}
// glfwSetCursorPosCallback(window, (win, x, y) => cursorPos(x, y));
// glfwSetScrollCallback(window, (win, x, y) => scroll(x, y));
}
public void cursorPos(double x, double y) {
foreach (MouseListener l in listeners) {
l.position(x, y);
}
this.x = x;
this.y = y;
}
public void scroll(double xoffset, double yoffset) {
foreach (MouseListener l in listeners) {
l.scroll(xoffset, yoffset);
}
scrollX += xoffset;
scrollY += yoffset;
}
public double getDX() {
return x - px;
}
public double getDY() {
return y - py;
}
public double getDScrollX() {
return scrollX - pScrollX;
}
public double getDScrollY() {
return scrollY - pScrollY;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public void setDelta() {
px = x;
py = y;
pScrollX = scrollX;
pScrollY = scrollY;
}
public void buttonPress(int button, int mods) {
foreach (MouseListener l in listeners) {
l.button(button, mods, true);
}
pressed.Add(button);
}
public void buttonRelease(int button, int mods) {
foreach (MouseListener l in listeners) {
l.button(button, mods, false);
}
pressed.Remove(button);
}
public bool isPressed(int button) {
return pressed.Contains(button);
}
public void addListener(MouseListener listener) {
listeners.Add(listener);
}
}

View File

@ -0,0 +1,28 @@
/*
recast4j copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast.Demo.UI;
public interface MouseListener {
void button(int button, int mods, bool down);
void scroll(double xoffset, double yoffset);
void position(double x, double y);
}

View File

@ -0,0 +1,333 @@
/*
recast4j copyright (c) 2015-2019 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
using Microsoft.DotNet.PlatformAbstractions;
namespace DotRecast.Recast.Demo.UI;
public class NuklearGL {
private static readonly int BUFFER_INITIAL_SIZE = 4 * 1024;
private static readonly int MAX_VERTEX_BUFFER = 512 * 1024;
private static readonly int MAX_ELEMENT_BUFFER = 128 * 1024;
private static readonly int FONT_BITMAP_W = 1024;
private static readonly int FONT_BITMAP_H = 1024;
private static readonly int FONT_HEIGHT = 15;
private readonly NuklearUI context;
// private readonly NkDrawNullTexture null_texture = NkDrawNullTexture.create();
// private readonly NkBuffer cmds = NkBuffer.create();
// private readonly NkUserFont default_font;
private readonly int program;
private readonly int uniform_tex;
private readonly int uniform_proj;
private readonly int vbo;
private readonly int ebo;
private readonly int vao;
//private readonly Buffer vertexLayout;
public NuklearGL(NuklearUI context) {
this.context = context;
// nk_buffer_init(cmds, context.allocator, BUFFER_INITIAL_SIZE);
// vertexLayout = NkDrawVertexLayoutElement.create(4)//
// .position(0).attribute(NK_VERTEX_POSITION).format(NK_FORMAT_FLOAT).offset(0)//
// .position(1).attribute(NK_VERTEX_TEXCOORD).format(NK_FORMAT_FLOAT).offset(8)//
// .position(2).attribute(NK_VERTEX_COLOR).format(NK_FORMAT_R8G8B8A8).offset(16)//
// .position(3).attribute(NK_VERTEX_ATTRIBUTE_COUNT).format(NK_FORMAT_COUNT).offset(0)//
// .flip();
// string NK_SHADER_VERSION = Platform.get() == Platform.MACOSX ? "#version 150\n" : "#version 300 es\n";
// string vertex_shader = NK_SHADER_VERSION + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n"
// + "in vec2 TexCoord;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n"
// + "void main() {\n" + " Frag_UV = TexCoord;\n" + " Frag_Color = Color;\n"
// + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n";
// string fragment_shader = NK_SHADER_VERSION + "precision mediump float;\n" + "uniform sampler2D Texture;\n"
// + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main(){\n"
// + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n";
//
// program = glCreateProgram();
// int vert_shdr = glCreateShader(GL_VERTEX_SHADER);
// int frag_shdr = glCreateShader(GL_FRAGMENT_SHADER);
// glShaderSource(vert_shdr, vertex_shader);
// glShaderSource(frag_shdr, fragment_shader);
// glCompileShader(vert_shdr);
// glCompileShader(frag_shdr);
// if (glGetShaderi(vert_shdr, GL_COMPILE_STATUS) != GL_TRUE) {
// throw new IllegalStateException();
// }
// if (glGetShaderi(frag_shdr, GL_COMPILE_STATUS) != GL_TRUE) {
// throw new IllegalStateException();
// }
// glAttachShader(program, vert_shdr);
// glAttachShader(program, frag_shdr);
// glLinkProgram(program);
// if (glGetProgrami(program, GL_LINK_STATUS) != GL_TRUE) {
// throw new IllegalStateException();
// }
//
// uniform_tex = glGetUniformLocation(program, "Texture");
// uniform_proj = glGetUniformLocation(program, "ProjMtx");
// int attrib_pos = glGetAttribLocation(program, "Position");
// int attrib_uv = glGetAttribLocation(program, "TexCoord");
// int attrib_col = glGetAttribLocation(program, "Color");
//
// // buffer setup
// vbo = glGenBuffers();
// ebo = glGenBuffers();
// vao = glGenVertexArrays();
//
// glBindVertexArray(vao);
// glBindBuffer(GL_ARRAY_BUFFER, vbo);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
//
// glEnableVertexAttribArray(attrib_pos);
// glEnableVertexAttribArray(attrib_uv);
// glEnableVertexAttribArray(attrib_col);
//
// glVertexAttribPointer(attrib_pos, 2, GL_FLOAT, false, 20, 0);
// glVertexAttribPointer(attrib_uv, 2, GL_FLOAT, false, 20, 8);
// glVertexAttribPointer(attrib_col, 4, GL_UNSIGNED_BYTE, true, 20, 16);
//
// // null texture setup
// int nullTexID = glGenTextures();
//
// null_texture.texture().id(nullTexID);
// null_texture.uv().set(0.5f, 0.5f);
//
// glBindTexture(GL_TEXTURE_2D, nullTexID);
// try (MemoryStack stack = stackPush()) {
// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
// stack.ints(0xFFFFFFFF));
// }
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//
// glBindTexture(GL_TEXTURE_2D, 0);
// glBindBuffer(GL_ARRAY_BUFFER, 0);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// glBindVertexArray(0);
// default_font = setupFont();
// nk_style_set_font(context.ctx, default_font);
}
private long setupFont() {
return 0;
// NkUserFont font = NkUserFont.create();
// ByteBuffer ttf;
// try {
// ttf = IOUtils.toByteBuffer(Loader.ToBytes("fonts/DroidSans.ttf"), true);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
//
// int fontTexID = glGenTextures();
//
// STBTTFontinfo fontInfo = STBTTFontinfo.malloc();
// STBTTPackedchar.Buffer cdata = STBTTPackedchar.create(95);
//
// float scale;
// float descent;
//
// try (MemoryStack stack = stackPush()) {
// stbtt_InitFont(fontInfo, ttf);
// scale = stbtt_ScaleForPixelHeight(fontInfo, FONT_HEIGHT);
//
// int[] d = stack.mallocInt(1);
// stbtt_GetFontVMetrics(fontInfo, null, d, null);
// descent = d[0] * scale;
//
// ByteBuffer bitmap = memAlloc(FONT_BITMAP_W * FONT_BITMAP_H);
//
// STBTTPackContext pc = STBTTPackContext.mallocStack(stack);
// stbtt_PackBegin(pc, bitmap, FONT_BITMAP_W, FONT_BITMAP_H, 0, 1);
// stbtt_PackSetOversampling(pc, 1, 1);
// stbtt_PackFontRange(pc, ttf, 0, FONT_HEIGHT, 32, cdata);
// stbtt_PackEnd(pc);
//
// // Convert R8 to RGBA8
// ByteBuffer texture = memAlloc(FONT_BITMAP_W * FONT_BITMAP_H * 4);
// for (int i = 0; i < bitmap.capacity(); i++) {
// texture.putInt((bitmap[i] << 24) | 0x00FFFFFF);
// }
// texture.flip();
//
// glBindTexture(GL_TEXTURE_2D, fontTexID);
// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, FONT_BITMAP_W, FONT_BITMAP_H, 0, GL_RGBA,
// GL_UNSIGNED_INT_8_8_8_8_REV, texture);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//
// memFree(texture);
// memFree(bitmap);
// }
// int[] cache = new int[1024];
// try (MemoryStack stack = stackPush()) {
// int[] advance = stack.mallocInt(1);
// for (int i = 0; i < 1024; i++) {
// stbtt_GetCodepointHMetrics(fontInfo, i, advance, null);
// cache[i] = advance[0];
// }
// }
// font.width((handle, h, text, len) => {
// float text_width = 0;
// try (MemoryStack stack = stackPush()) {
// int[] unicode = stack.mallocInt(1);
//
// int glyph_len = nnk_utf_decode(text, memAddress(unicode), len);
// int text_len = glyph_len;
//
// if (glyph_len == 0) {
// return 0;
// }
//
// int[] advance = stack.mallocInt(1);
// while (text_len <= len && glyph_len != 0) {
// if (unicode[0] == NK_UTF_INVALID) {
// break;
// }
//
// /* query currently drawn glyph information */
// // stbtt_GetCodepointHMetrics(fontInfo, unicode[0], advance, null);
// // text_width += advance[0] * scale;
//
// text_width += cache[unicode[0]] * scale;
// /* offset next glyph */
// glyph_len = nnk_utf_decode(text + text_len, memAddress(unicode), len - text_len);
// text_len += glyph_len;
// }
// }
// return text_width;
// }).height(FONT_HEIGHT).query((handle, font_height, glyph, codepoint, next_codepoint) => {
// try (MemoryStack stack = stackPush()) {
// float[] x = stack.floats(0.0f);
// float[] y = stack.floats(0.0f);
//
// STBTTAlignedQuad q = STBTTAlignedQuad.mallocStack(stack);
// // int[] advance = stack.mallocInt(1);
//
// stbtt_GetPackedQuad(cdata, FONT_BITMAP_W, FONT_BITMAP_H, codepoint - 32, x, y, q, false);
// // stbtt_GetCodepointHMetrics(fontInfo, codepoint, advance, null);
//
// NkUserFontGlyph ufg = NkUserFontGlyph.create(glyph);
//
// ufg.width(q.x1() - q.x0());
// ufg.height(q.y1() - q.y0());
// ufg.offset().set(q.x0(), q.y0() + (FONT_HEIGHT + descent));
// // ufg.xadvance(advance[0] * scale);
// ufg.xadvance(cache[codepoint] * scale);
// ufg.uv(0).set(q.s0(), q.t0());
// ufg.uv(1).set(q.s1(), q.t1());
// }
// }).texture().id(fontTexID);
// return font;
}
void render(long win, int AA) {
// int width;
// int height;
// int display_width;
// int display_height;
// try (MemoryStack stack = stackPush()) {
// int[] w = stack.mallocInt(1);
// int[] h = stack.mallocInt(1);
//
// glfwGetWindowSize(win, w, h);
// width = w[0];
// height = h[0];
//
// glfwGetFramebufferSize(win, w, h);
// display_width = w[0];
// display_height = h[0];
// }
//
// try (MemoryStack stack = stackPush()) {
// // setup global state
// glEnable(GL_BLEND);
// glBlendEquation(GL_FUNC_ADD);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// glDisable(GL_CULL_FACE);
// glDisable(GL_DEPTH_TEST);
// glEnable(GL_SCISSOR_TEST);
// glActiveTexture(GL_TEXTURE0);
//
// glUseProgram(program);
// // setup program
// glUniform1i(uniform_tex, 0);
// glUniformMatrix4fv(uniform_proj, false, stack.floats(2.0f / width, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f / height,
// 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f));
// glViewport(0, 0, display_width, display_height);
// }
// // allocate vertex and element buffer
// glBindVertexArray(vao);
// glBindBuffer(GL_ARRAY_BUFFER, vbo);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
//
// glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_BUFFER, GL_STREAM_DRAW);
// glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_BUFFER, GL_STREAM_DRAW);
//
// // load draw vertices & elements directly into vertex + element buffer
// ByteBuffer vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY, MAX_VERTEX_BUFFER, null);
// ByteBuffer elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY, MAX_ELEMENT_BUFFER, null);
//
// try (MemoryStack stack = stackPush()) {
// // fill convert configuration
// NkConvertConfig config = NkConvertConfig.callocStack(stack).vertex_layout(vertexLayout).vertex_size(20)
// .vertex_alignment(4).null_texture(null_texture).circle_segment_count(22).curve_segment_count(22)
// .arc_segment_count(22).global_alpha(1f).shape_AA(AA).line_AA(AA);
//
// // setup buffers to load vertices and elements
// NkBuffer vbuf = NkBuffer.mallocStack(stack);
// NkBuffer ebuf = NkBuffer.mallocStack(stack);
//
// nk_buffer_init_fixed(vbuf, vertices/* , max_vertex_buffer */);
// nk_buffer_init_fixed(ebuf, elements/* , max_element_buffer */);
// nk_convert(context.ctx, cmds, vbuf, ebuf, config);
// }
// glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
// glUnmapBuffer(GL_ARRAY_BUFFER);
//
// // iterate over and execute each draw command
// float fb_scale_x = (float) display_width / (float) width;
// float fb_scale_y = (float) display_height / (float) height;
//
// long offset = NULL;
// for (NkDrawCommand cmd = nk__draw_begin(context.ctx, cmds); cmd != null; cmd = nk__draw_next(cmd, cmds,
// context.ctx)) {
// if (cmd.elem_count() == 0) {
// continue;
// }
// glBindTexture(GL_TEXTURE_2D, cmd.texture().id());
// glScissor((int) (cmd.clip_rect().x() * fb_scale_x),
// (int) ((height - (int) (cmd.clip_rect().y() + cmd.clip_rect().h())) * fb_scale_y),
// (int) (cmd.clip_rect().w() * fb_scale_x), (int) (cmd.clip_rect().h() * fb_scale_y));
// glDrawElements(GL_TRIANGLES, cmd.elem_count(), GL_UNSIGNED_SHORT, offset);
// offset += cmd.elem_count() * 2;
// }
// nk_clear(context.ctx);
// glUseProgram(0);
// glBindTexture(GL_TEXTURE_2D, 0);
// glBindBuffer(GL_ARRAY_BUFFER, 0);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// glBindVertexArray(0);
// glDisable(GL_BLEND);
// glDisable(GL_SCISSOR_TEST);
}
}

View File

@ -0,0 +1,150 @@
/*
recast4j copyright (c) 2015-2019 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 Silk.NET.Input;
using Silk.NET.OpenGL;
using Silk.NET.OpenGL.Extensions.ImGui;
using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.UI;
public class NuklearUI {
// readonly NkAllocator allocator;
private readonly IWindow _window;
private readonly GL _gl;
// readonly NkColor background;
// readonly NkColor white;
private readonly NuklearUIModule[] _modules;
private readonly NuklearGL glContext;
private bool mouseOverUI;
public NuklearUI(IWindow window, IInputContext input, params NuklearUIModule[] modules)
{
var mouse = new Mouse(input);
_window = window;
_gl = GL.GetApi(window);
// allocator = NkAllocator.create();
// allocator.alloc((handle, old, size) => {
// long mem = nmemAlloc(size);
// if (mem == NULL) {
// throw new OutOfMemoryError();
// }
// return mem;
//
// });
// allocator.mfree((handle, ptr) => nmemFree(ptr));
// background = NkColor.create();
// nk_rgb(28, 48, 62, background);
// white = NkColor.create();
// nk_rgb(255, 255, 255, white);
// nk_init(ctx, allocator, null);
setupMouse(mouse);
// setupClipboard(window);
// glfwSetCharCallback(window, (w, codepoint) => nk_input_unicode(ctx, codepoint));
// glContext = new NuklearGL(this);
_modules = modules;
}
private void setupMouse(Mouse mouse) {
// mouse.addListener(new MouseListener() {
//
// @Override
// public void scroll(double xoffset, double yoffset) {
// if (mouseOverUI) {
// try (MemoryStack stack = stackPush()) {
// NkVec2 scroll = NkVec2.mallocStack(stack).x((float) xoffset).y((float) yoffset);
// nk_input_scroll(ctx, scroll);
// }
// }
// }
//
// @Override
// public void button(int button, int mods, bool down) {
// try (MemoryStack stack = stackPush()) {
// int nkButton;
// switch (button) {
// case GLFW_MOUSE_BUTTON_RIGHT:
// nkButton = NK_BUTTON_RIGHT;
// break;
// case GLFW_MOUSE_BUTTON_MIDDLE:
// nkButton = NK_BUTTON_MIDDLE;
// break;
// default:
// nkButton = NK_BUTTON_LEFT;
// }
// nk_input_button(ctx, nkButton, (int) mouse.getX(), (int) mouse.getY(), down);
// }
// }
//
// @Override
// public void position(double x, double y) {
// nk_input_motion(ctx, (int) x, (int) y);
// }
// });
}
private void setupClipboard(long window) {
// ctx.clip().copy((handle, text, len) => {
// if (len == 0) {
// return;
// }
//
// try (MemoryStack stack = stackPush()) {
// ByteBuffer str = stack.malloc(len + 1);
// memCopy(text, memAddress(str), len);
// str.put(len, (byte) 0);
// glfwSetClipboardString(window, str);
// }
// });
// ctx.clip().paste((handle, edit) => {
// long text = nglfwGetClipboardString(window);
// if (text != NULL) {
// nnk_textedit_paste(edit, text, nnk_strlen(text));
// }
// });
}
public void inputBegin() {
//nk_input_begin(ctx);
}
public void inputEnd(IWindow win) {
// NkMouse mouse = ctx.input().mouse();
// if (mouse.grab()) {
// glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
// } else if (mouse.grabbed()) {
// float prevX = mouse.prev().x();
// float prevY = mouse.prev().y();
// glfwSetCursorPos(win, prevX, prevY);
// mouse.pos().x(prevX);
// mouse.pos().y(prevY);
// } else if (mouse.ungrab()) {
// glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
// }
// nk_input_end(ctx);
}
public bool layout(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY) {
mouseOverUI = false;
foreach (NuklearUIModule m in _modules) {
mouseOverUI = m.layout(ctx, x, y, width, height, mouseX, mouseY) | mouseOverUI;
}
return mouseOverUI;
}
}

View File

@ -0,0 +1,74 @@
/*
recast4j copyright (c) 2015-2019 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.Runtime.InteropServices.JavaScript;
namespace DotRecast.Recast.Demo.UI;
public class NuklearUIHelper {
// public static void nk_color_rgb(IWindow ctx, NkColorf color) {
// try (MemoryStack stack = stackPush()) {
// if (nk_combo_begin_color(ctx, nk_rgb_cf(color, NkColor.mallocStack(stack)),
// NkVec2.mallocStack(stack).set(nk_widget_width(ctx), 400))) {
// nk_layout_row_dynamic(ctx, 120, 1);
// nk_color_picker(ctx, color, NK_RGB);
// nk_layout_row_dynamic(ctx, 20, 1);
// color.r(nk_propertyf(ctx, "#R:", 0, color.r(), 1f, 0.01f, 0.005f));
// color.g(nk_propertyf(ctx, "#G:", 0, color.g(), 1f, 0.01f, 0.005f));
// color.b(nk_propertyf(ctx, "#B:", 0, color.b(), 1f, 0.01f, 0.005f));
// nk_combo_end(ctx);
// }
// }
// }
//
// public static <T extends Enum<?>> T nk_radio(IWindow ctx, T[] values, T currentValue, JSType.Function<T, string> nameFormatter) {
// try (MemoryStack stack = stackPush()) {
// foreach (T v in values) {
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_text(ctx, nameFormatter.apply(v), currentValue == v)) {
// currentValue = v;
// }
// }
// }
// return currentValue;
// }
//
// public static <T extends Enum<?>> T nk_combo(IWindow ctx, T[] values, T currentValue) {
// try (MemoryStack stack = stackPush()) {
// if (nk_combo_begin_label(ctx, currentValue.toString(), NkVec2.mallocStack(stack).set(nk_widget_width(ctx), 200))) {
// nk_layout_row_dynamic(ctx, 20, 1);
// foreach (T v in values) {
// if (nk_combo_item_label(ctx, v.toString(), NK_TEXT_LEFT)) {
// currentValue = v;
// }
// }
// nk_combo_end(ctx);
// }
// }
// return currentValue;
// }
//
// public static NkColorf nk_colorf(int r, int g, int b) {
// NkColorf color = NkColorf.create();
// color.r(r / 255f);
// color.g(g / 255f);
// color.b(b / 255f);
// return color;
// }
}

View File

@ -0,0 +1,29 @@
/*
recast4j copyright (c) 2015-2019 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 Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.UI;
public interface NuklearUIModule {
bool layout(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY);
}

View File

@ -0,0 +1,5 @@
[Window][Debug##Default]
Pos=280,129
Size=400,400
Collapsed=0

View File

@ -0,0 +1,69 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
public class AreaModification
{
public readonly int RC_AREA_FLAGS_MASK = 0x3F;
private readonly int value;
private readonly int mask;
/**
* Mask is set to all available bits, which means value is fully applied
*
* @param value
* The area id to apply. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
*/
public AreaModification(int value)
{
this.value = value;
mask = RC_AREA_FLAGS_MASK;
}
/**
*
* @param value
* The area id to apply. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
* @param mask
* Bitwise mask used when applying value. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
*/
public AreaModification(int value, int mask)
{
this.value = value;
this.mask = mask;
}
public AreaModification(AreaModification other)
{
value = other.value;
mask = other.mask;
}
public int getMaskedValue()
{
return value & mask;
}
public int apply(int area)
{
return ((value & mask) | (area & ~mask));
}
}

View File

@ -0,0 +1,30 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
/** Provides information on the content of a cell column in a compact heightfield. */
public class CompactCell
{
/** Index to the first span in the column. */
public int index;
/** Number of spans in the column. */
public int count;
}

View File

@ -0,0 +1,72 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
/** A compact, static heightfield representing unobstructed space. */
public class CompactHeightfield
{
/** The width of the heightfield. (Along the x-axis in cell units.) */
public int width;
/** The height of the heightfield. (Along the z-axis in cell units.) */
public int height;
/** The number of spans in the heightfield. */
public int spanCount;
/** The walkable height used during the build of the field. (See: RecastConfig::walkableHeight) */
public int walkableHeight;
/** The walkable climb used during the build of the field. (See: RecastConfig::walkableClimb) */
public int walkableClimb;
/** The AABB border size used during the build of the field. (See: RecastConfig::borderSize) */
public int borderSize;
/** The maximum distance value of any span within the field. */
public int maxDistance;
/** The maximum region id of any span within the field. */
public int maxRegions;
/** The minimum bounds in world space. [(x, y, z)] */
public float[] bmin = new float[3];
/** The maximum bounds in world space. [(x, y, z)] */
public float[] bmax = new float[3];
/** 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;
/** Array of cells. [Size: #width*#height] */
public CompactCell[] cells;
/** Array of spans. [Size: #spanCount] */
public CompactSpan[] spans;
/** Array containing border distance data. [Size: #spanCount] */
public int[] dist;
/** Array containing area id data. [Size: #spanCount] */
public int[] areas;
}

View File

@ -0,0 +1,36 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
/** Represents a span of unobstructed space within a compact heightfield. */
public class CompactSpan
{
/** The lower extent of the span. (Measured from the heightfield's base.) */
public int y;
/** The id of the region the span belongs to. (Or zero if not in a region.) */
public int reg;
/** Packed neighbor connection data. */
public int con;
/** The height of the span. (Measured from #y.) */
public int h;
}

View File

@ -0,0 +1,42 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
/** Represents a simple, non-overlapping contour in field space. */
public class Contour
{
/** Simplified contour vertex and connection data. [Size: 4 * #nverts] */
public int[] verts;
/** The number of vertices in the simplified contour. */
public int nverts;
/** 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

@ -0,0 +1,53 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Recast;
/** Represents a group of related contours. */
public class ContourSet
{
/** A list of the contours in the set. */
public List<Contour> conts = new();
/** The minimum bounds in world space. [(x, y, z)] */
public float[] bmin = new float[3];
/** The maximum bounds in world space. [(x, y, z)] */
public float[] bmax = new float[3];
/** 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 width of the set. (Along the x-axis in cell units.) */
public int width;
/** The height of the set. (Along the z-axis in cell units.) */
public int height;
/** The AABB border size used to generate the source data from which the contours were derived. */
public int borderSize;
/** The max edge error that this contour set was simplified with. */
public float maxError;
}

View File

@ -0,0 +1,28 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
public class ConvexVolume
{
public float[] verts;
public float hmin;
public float hmax;
public AreaModification areaMod;
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,246 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Recast.Geom;
public class ChunkyTriMesh
{
private class BoundsItem
{
public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2];
public int i;
}
private class CompareItemX : IComparer<BoundsItem>
{
public int Compare(BoundsItem? a, BoundsItem? b)
{
return a.bmin[0].CompareTo(b.bmin[0]);
}
}
private class CompareItemY : IComparer<BoundsItem>
{
public int Compare(BoundsItem? a, BoundsItem? b)
{
return a.bmin[1].CompareTo(b.bmin[1]);
}
}
List<ChunkyTriMeshNode> nodes;
int ntris;
int maxTrisPerChunk;
private void calcExtends(BoundsItem[] items, int imin, int imax, float[] bmin, float[] bmax)
{
bmin[0] = items[imin].bmin[0];
bmin[1] = items[imin].bmin[1];
bmax[0] = items[imin].bmax[0];
bmax[1] = items[imin].bmax[1];
for (int i = imin + 1; i < imax; ++i)
{
BoundsItem it = items[i];
if (it.bmin[0] < bmin[0])
{
bmin[0] = it.bmin[0];
}
if (it.bmin[1] < bmin[1])
{
bmin[1] = it.bmin[1];
}
if (it.bmax[0] > bmax[0])
{
bmax[0] = it.bmax[0];
}
if (it.bmax[1] > bmax[1])
{
bmax[1] = it.bmax[1];
}
}
}
private int longestAxis(float x, float y)
{
return y > x ? 1 : 0;
}
private void subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<ChunkyTriMeshNode> nodes,
int[] inTris)
{
int inum = imax - imin;
ChunkyTriMeshNode node = new ChunkyTriMeshNode();
nodes.Add(node);
if (inum <= trisPerChunk)
{
// Leaf
calcExtends(items, imin, imax, node.bmin, 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, node.bmin, node.bmax);
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1]);
if (axis == 0)
{
Array.Sort(items, imin, imax - imin, new CompareItemX());
// Sort along x-axis
}
else if (axis == 1)
{
Array.Sort(items, imin, imax - imin, new CompareItemY());
// 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 ChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk)
{
int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;
nodes = new(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[0] = it.bmax[0] = verts[tris[t] * 3 + 0];
it.bmin[1] = it.bmax[1] = verts[tris[t] * 3 + 2];
for (int j = 1; j < 3; ++j)
{
int v = tris[t + j] * 3;
if (verts[v] < it.bmin[0])
{
it.bmin[0] = verts[v];
}
if (verts[v + 2] < it.bmin[1])
{
it.bmin[1] = verts[v + 2];
}
if (verts[v] > it.bmax[0])
{
it.bmax[0] = verts[v];
}
if (verts[v + 2] > it.bmax[1])
{
it.bmax[1] = verts[v + 2];
}
}
}
subdivide(items, 0, ntris, trisPerChunk, nodes, tris);
// Calc max tris per node.
maxTrisPerChunk = 0;
foreach (ChunkyTriMeshNode 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, float[] bmin, float[] bmax)
{
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
return overlap;
}
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax)
{
// Traverse tree
List<ChunkyTriMeshNode> ids = new();
int i = 0;
while (i < nodes.Count)
{
ChunkyTriMeshNode 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;
}
}

View File

@ -0,0 +1,9 @@
namespace DotRecast.Recast.Geom;
public class ChunkyTriMeshNode
{
public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2];
public int i;
public int[] tris;
}

View File

@ -0,0 +1,26 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
namespace DotRecast.Recast.Geom;
public interface ConvexVolumeProvider
{
IList<ConvexVolume> convexVolumes();
}

View File

@ -0,0 +1,31 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Recast.Geom;
public interface InputGeomProvider : ConvexVolumeProvider
{
float[] getMeshBoundsMin();
float[] getMeshBoundsMax();
IEnumerable<TriMesh> meshes();
}

View File

@ -0,0 +1,136 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using System.Collections.Immutable;
namespace DotRecast.Recast.Geom;
public class SimpleInputGeomProvider : InputGeomProvider
{
public readonly float[] vertices;
public readonly int[] faces;
public readonly float[] normals;
readonly float[] bmin;
readonly float[] bmax;
readonly List<ConvexVolume> volumes = new();
public SimpleInputGeomProvider(List<float> vertexPositions, List<int> meshFaces)
: this(mapVertices(vertexPositions), mapFaces(meshFaces))
{
}
private static int[] mapFaces(List<int> meshFaces)
{
int[] faces = new int[meshFaces.Count];
for (int i = 0; i < faces.Length; i++)
{
faces[i] = meshFaces[i];
}
return faces;
}
private static float[] mapVertices(List<float> vertexPositions)
{
float[] vertices = new float[vertexPositions.Count];
for (int i = 0; i < vertices.Length; i++)
{
vertices[i] = vertexPositions[i];
}
return vertices;
}
public SimpleInputGeomProvider(float[] vertices, int[] faces)
{
this.vertices = vertices;
this.faces = faces;
normals = new float[faces.Length];
calculateNormals();
bmin = new float[3];
bmax = new float[3];
RecastVectors.copy(bmin, vertices, 0);
RecastVectors.copy(bmax, vertices, 0);
for (int i = 1; i < vertices.Length / 3; i++)
{
RecastVectors.min(bmin, vertices, i * 3);
RecastVectors.max(bmax, vertices, i * 3);
}
}
public float[] getMeshBoundsMin()
{
return bmin;
}
public float[] getMeshBoundsMax()
{
return bmax;
}
public IList<ConvexVolume> convexVolumes()
{
return volumes;
}
public void addConvexVolume(float[] verts, float minh, float maxh, AreaModification areaMod)
{
ConvexVolume vol = new ConvexVolume();
vol.hmin = minh;
vol.hmax = maxh;
vol.verts = verts;
vol.areaMod = areaMod;
volumes.Add(vol);
}
public IEnumerable<TriMesh> meshes()
{
return ImmutableArray.Create(new TriMesh(vertices, faces));
}
public void calculateNormals()
{
for (int i = 0; i < faces.Length; i += 3)
{
int v0 = faces[i] * 3;
int v1 = faces[i + 1] * 3;
int v2 = faces[i + 2] * 3;
float[] e0 = new float[3], e1 = new float[3];
for (int j = 0; j < 3; ++j)
{
e0[j] = vertices[v1 + j] - vertices[v0 + j];
e1[j] = vertices[v2 + j] - vertices[v0 + j];
}
normals[i] = e0[1] * e1[2] - e0[2] * e1[1];
normals[i + 1] = e0[2] * e1[0] - e0[0] * e1[2];
normals[i + 2] = e0[0] * e1[1] - e0[1] * e1[0];
float d = (float)Math.Sqrt(normals[i] * normals[i] + normals[i + 1] * normals[i + 1] + normals[i + 2] * normals[i + 2]);
if (d > 0)
{
d = 1.0f / d;
normals[i] *= d;
normals[i + 1] *= d;
normals[i + 2] *= d;
}
}
}
}

View File

@ -0,0 +1,64 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using System.Collections.Immutable;
namespace DotRecast.Recast.Geom;
public class SingleTrimeshInputGeomProvider : InputGeomProvider
{
private readonly float[] bmin;
private readonly float[] bmax;
private readonly ImmutableArray<TriMesh> _meshes;
public SingleTrimeshInputGeomProvider(float[] vertices, int[] faces)
{
bmin = new float[3];
bmax = new float[3];
RecastVectors.copy(bmin, vertices, 0);
RecastVectors.copy(bmax, vertices, 0);
for (int i = 1; i < vertices.Length / 3; i++)
{
RecastVectors.min(bmin, vertices, i * 3);
RecastVectors.max(bmax, vertices, i * 3);
}
_meshes = ImmutableArray.Create(new TriMesh(vertices, faces));
}
public float[] getMeshBoundsMin()
{
return bmin;
}
public float[] getMeshBoundsMax()
{
return bmax;
}
public IEnumerable<TriMesh> meshes()
{
return _meshes;
}
public IList<ConvexVolume> convexVolumes()
{
return ImmutableArray<ConvexVolume>.Empty;
}
}

View File

@ -0,0 +1,51 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Recast.Geom;
public class TriMesh
{
private readonly float[] vertices;
private readonly int[] faces;
private readonly ChunkyTriMesh chunkyTriMesh;
public TriMesh(float[] vertices, int[] faces)
{
this.vertices = vertices;
this.faces = faces;
chunkyTriMesh = new ChunkyTriMesh(vertices, faces, faces.Length / 3, 32);
}
public int[] getTris()
{
return faces;
}
public float[] getVerts()
{
return vertices;
}
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax)
{
return chunkyTriMesh.getChunksOverlappingRect(bmin, bmax);
}
}

View File

@ -0,0 +1,60 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
/** Represents a heightfield layer within a layer set. */
public class Heightfield
{
/** The width of the heightfield. (Along the x-axis in cell units.) */
public readonly int width;
/** The height of the heightfield. (Along the z-axis in cell units.) */
public readonly int height;
/** The minimum bounds in world space. [(x, y, z)] */
public readonly float[] bmin;
/** The maximum bounds in world space. [(x, y, z)] */
public readonly float[] 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 Span[] spans;
/** Border size in cell units */
public readonly int borderSize;
public Heightfield(int width, int height, float[] bmin, float[] bmax, float cs, float ch, int borderSize)
{
this.width = width;
this.height = height;
this.bmin = bmin;
this.bmax = bmax;
this.cs = cs;
this.ch = ch;
this.borderSize = borderSize;
spans = new Span[width * height];
}
}

View File

@ -0,0 +1,77 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
/// Represents a set of heightfield layers.
/// @ingroup recast
/// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet
public class HeightfieldLayerSet
{
/// Represents a heightfield layer within a layer set.
/// @see rcHeightfieldLayerSet
public class HeightfieldLayer
{
public readonly float[] bmin = new float[3];
/// < The minimum bounds in world space. [(x, y, z)]
public readonly float[] bmax = new float[3];
/// < The maximum bounds in world space. [(x, y, z)]
public float cs;
/// < The size of each cell. (On the xz-plane.)
public float ch;
/// < The height of each cell. (The minimum increment along the y-axis.)
public int width;
/// < The width of the heightfield. (Along the x-axis in cell units.)
public int height;
/// < The height of the heightfield. (Along the z-axis in cell units.)
public int minx;
/// < The minimum x-bounds of usable data.
public int maxx;
/// < The maximum x-bounds of usable data.
public int miny;
/// < The minimum y-bounds of usable data. (Along the z-axis.)
public int maxy;
/// < The maximum y-bounds of usable data. (Along the z-axis.)
public int hmin;
/// < The minimum height bounds of usable data. (Along the y-axis.)
public int hmax;
/// < The maximum height bounds of usable data. (Along the y-axis.)
public int[] heights;
/// < The heightfield. [Size: width * height]
public int[] areas;
/// < Area ids. [Size: Same as #heights]
public int[] cons; /// < Packed neighbor connection information. [Size: Same as #heights]
}
public HeightfieldLayer[] layers; /// < The layers in the set. [Size: #nlayers]
}

View File

@ -0,0 +1,5 @@
namespace DotRecast.Recast;
public class InputGeomReader
{
}

View File

@ -0,0 +1,138 @@
/*
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using System.IO;
using DotRecast.Recast.Geom;
namespace DotRecast.Recast;
public static class ObjImporter
{
public class ObjImporterContext
{
public List<float> vertexPositions = new();
public List<int> meshFaces = new();
}
public static InputGeomProvider load(byte[] chunck)
{
var context = loadContext(chunck);
return new SimpleInputGeomProvider(context.vertexPositions, context.meshFaces);
}
public static ObjImporterContext loadContext(byte[] chunck)
{
ObjImporterContext context = new ObjImporterContext();
try
{
using StreamReader reader = new StreamReader(new MemoryStream(chunck));
string line;
while ((line = reader.ReadLine()) != null)
{
line = line.Trim();
readLine(line, context);
}
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
return context;
}
public static void readLine(string line, ObjImporterContext context)
{
if (line.StartsWith("v"))
{
readVertex(line, context);
}
else if (line.StartsWith("f"))
{
readFace(line, context);
}
}
private static void readVertex(string line, ObjImporterContext context)
{
if (line.StartsWith("v "))
{
float[] vert = readVector3f(line);
foreach (float vp in vert)
{
context.vertexPositions.Add(vp);
}
}
}
private static float[] readVector3f(string line)
{
string[] v = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (v.Length < 4)
{
throw new Exception("Invalid vector, expected 3 coordinates, found " + (v.Length - 1));
}
return new float[] { float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]) };
}
private static void readFace(string line, ObjImporterContext context)
{
string[] v = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (v.Length < 4)
{
throw new Exception("Invalid number of face vertices: 3 coordinates expected, found " + v.Length);
}
for (int j = 0; j < v.Length - 3; j++)
{
context.meshFaces.Add(readFaceVertex(v[1], context));
for (int i = 0; i < 2; i++)
{
context.meshFaces.Add(readFaceVertex(v[2 + j + i], context));
}
}
}
private static int readFaceVertex(string face, ObjImporterContext context)
{
string[] v = face.Split("/");
return getIndex(int.Parse(v[0]), context.vertexPositions.Count);
}
private static int getIndex(int posi, int size)
{
if (posi > 0)
{
posi--;
}
else if (posi < 0)
{
posi = size + posi;
}
else
{
throw new Exception("0 vertex index");
}
return posi;
}
}

View File

@ -0,0 +1,10 @@
namespace DotRecast.Recast;
/// < Tessellate edges between areas during contour
/// simplification.
public enum PartitionType
{
WATERSHED,
MONOTONE,
LAYERS
}

View File

@ -0,0 +1,69 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
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.
*/
namespace DotRecast.Recast;
/** Represents a polygon mesh suitable for use in building a navigation mesh. */
public class PolyMesh
{
/** The mesh vertices. [Form: (x, y, z) coordinates * #nverts] */
public int[] verts;
/** Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] */
public int[] polys;
/** The region id assigned to each polygon. [Length: #maxpolys] */
public int[] regs;
/** The area id assigned to each polygon. [Length: #maxpolys] */
public int[] areas;
/** The number of vertices. */
public int nverts;
/** The number of polygons. */
public int npolys;
/** The maximum number of vertices per polygon. */
public int nvp;
/** The number of allocated polygons. */
public int maxpolys;
/** The user defined flags for each polygon. [Length: #maxpolys] */
public int[] flags;
/** The minimum bounds in world space. [(x, y, z)] */
public readonly float[] bmin = new float[3];
/** The maximum bounds in world space. [(x, y, z)] */
public readonly float[] bmax = new float[3];
/** 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

@ -0,0 +1,45 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
/**
* Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon
* mesh object.
*/
public class PolyMeshDetail
{
/** The sub-mesh data. [Size: 4*#nmeshes] */
public int[] meshes;
/** The mesh vertices. [Size: 3*#nverts] */
public float[] verts;
/** The mesh triangles. [Size: 4*#ntris] */
public int[] tris;
/** The number of sub-meshes defined by #meshes. */
public int nmeshes;
/** The number of vertices in #verts. */
public int nverts;
/** The number of triangles in #tris. */
public int ntris;
}

View File

@ -0,0 +1,121 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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;
namespace DotRecast.Recast;
using static RecastConstants;
public class Recast
{
void calcBounds(float[] verts, int nv, float[] bmin, float[] bmax)
{
for (int i = 0; i < 3; i++)
{
bmin[i] = verts[i];
bmax[i] = verts[i];
}
for (int i = 1; i < nv; ++i)
{
for (int j = 0; j < 3; j++)
{
bmin[j] = Math.Min(bmin[j], verts[i * 3 + j]);
bmax[j] = Math.Max(bmax[j], verts[i * 3 + j]);
}
}
// Calculate bounding box.
}
public static int[] calcGridSize(float[] bmin, float[] bmax, float cs)
{
return new int[] { (int)((bmax[0] - bmin[0]) / cs + 0.5f), (int)((bmax[2] - bmin[2]) / cs + 0.5f) };
}
public static int[] calcTileCount(float[] bmin, float[] bmax, float cs, int tileSizeX, int tileSizeZ)
{
int[] gwd = Recast.calcGridSize(bmin, bmax, cs);
int gw = gwd[0];
int gd = gwd[1];
int tw = (gw + tileSizeX - 1) / tileSizeX;
int td = (gd + tileSizeZ - 1) / tileSizeZ;
return new int[] { tw, td };
}
/// @par
///
/// Modifies the area id of all triangles with a slope below the specified value.
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static int[] markWalkableTriangles(Telemetry ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt,
AreaModification areaMod)
{
int[] areas = new int[nt];
float walkableThr = (float)Math.Cos(walkableSlopeAngle / 180.0f * Math.PI);
float[] norm = new float[3];
for (int i = 0; i < nt; ++i)
{
int tri = i * 3;
calcTriNormal(verts, tris[tri], tris[tri + 1], tris[tri + 2], norm);
// Check if the face is walkable.
if (norm[1] > walkableThr)
areas[i] = areaMod.apply(areas[i]);
}
return areas;
}
static void calcTriNormal(float[] verts, int v0, int v1, int v2, float[] norm)
{
float[] e0 = new float[3];
float[] e1 = new float[3];
RecastVectors.sub(e0, verts, v1 * 3, v0 * 3);
RecastVectors.sub(e1, verts, v2 * 3, v0 * 3);
RecastVectors.cross(norm, e0, e1);
RecastVectors.normalize(norm);
}
/// @par
///
/// Only sets the area id's for the unwalkable triangles. Does not alter the
/// area id's for walkable triangles.
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static void clearUnwalkableTriangles(Telemetry ctx, float walkableSlopeAngle, float[] verts, int nv,
int[] tris, int nt, int[] areas)
{
float walkableThr = (float)Math.Cos(walkableSlopeAngle / 180.0f * Math.PI);
float[] norm = new float[3];
for (int i = 0; i < nt; ++i)
{
int tri = i * 3;
calcTriNormal(verts, tris[tri], tris[tri + 1], tris[tri + 2], norm);
// Check if the face is walkable.
if (norm[1] <= walkableThr)
areas[i] = RC_NULL_AREA;
}
}
}

View File

@ -0,0 +1,584 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4J Copyright (c) 2015-2019 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;
namespace DotRecast.Recast;
using static RecastConstants;
public class RecastArea
{
/// @par
///
/// Basically, any spans that are closer to a boundary or obstruction than the specified radius
/// are marked as unwalkable.
///
/// This method is usually called immediately after the heightfield has been built.
///
/// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius
public static void erodeWalkableArea(Telemetry ctx, int radius, CompactHeightfield chf)
{
int w = chf.width;
int h = chf.height;
ctx.startTimer("ERODE_AREA");
int[] dist = new int[chf.spanCount];
Array.Fill(dist, 255);
// Mark boundary cells.
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
if (chf.areas[i] == RC_NULL_AREA)
{
dist[i] = 0;
}
else
{
CompactSpan s = chf.spans[i];
int nc = 0;
for (int dir = 0; dir < 4; ++dir)
{
if (RecastCommon.GetCon(s, dir) != RC_NOT_CONNECTED)
{
int nx = x + RecastCommon.GetDirOffsetX(dir);
int ny = y + RecastCommon.GetDirOffsetY(dir);
int nidx = chf.cells[nx + ny * w].index + RecastCommon.GetCon(s, dir);
if (chf.areas[nidx] != RC_NULL_AREA)
{
nc++;
}
}
}
// At least one missing neighbour.
if (nc != 4)
dist[i] = 0;
}
}
}
}
int nd;
// Pass 1
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
if (RecastCommon.GetCon(s, 0) != RC_NOT_CONNECTED)
{
// (-1,0)
int ax = x + RecastCommon.GetDirOffsetX(0);
int ay = y + RecastCommon.GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 0);
CompactSpan @as = chf.spans[ai];
nd = Math.Min(dist[ai] + 2, 255);
if (nd < dist[i])
dist[i] = nd;
// (-1,-1)
if (RecastCommon.GetCon(@as, 3) != RC_NOT_CONNECTED)
{
int aax = ax + RecastCommon.GetDirOffsetX(3);
int aay = ay + RecastCommon.GetDirOffsetY(3);
int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 3);
nd = Math.Min(dist[aai] + 3, 255);
if (nd < dist[i])
dist[i] = nd;
}
}
if (RecastCommon.GetCon(s, 3) != RC_NOT_CONNECTED)
{
// (0,-1)
int ax = x + RecastCommon.GetDirOffsetX(3);
int ay = y + RecastCommon.GetDirOffsetY(3);
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 3);
CompactSpan @as = chf.spans[ai];
nd = Math.Min(dist[ai] + 2, 255);
if (nd < dist[i])
dist[i] = nd;
// (1,-1)
if (RecastCommon.GetCon(@as, 2) != RC_NOT_CONNECTED)
{
int aax = ax + RecastCommon.GetDirOffsetX(2);
int aay = ay + RecastCommon.GetDirOffsetY(2);
int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 2);
nd = Math.Min(dist[aai] + 3, 255);
if (nd < dist[i])
dist[i] = nd;
}
}
}
}
}
// Pass 2
for (int y = h - 1; y >= 0; --y)
{
for (int x = w - 1; x >= 0; --x)
{
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
if (RecastCommon.GetCon(s, 2) != RC_NOT_CONNECTED)
{
// (1,0)
int ax = x + RecastCommon.GetDirOffsetX(2);
int ay = y + RecastCommon.GetDirOffsetY(2);
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 2);
CompactSpan @as = chf.spans[ai];
nd = Math.Min(dist[ai] + 2, 255);
if (nd < dist[i])
dist[i] = nd;
// (1,1)
if (RecastCommon.GetCon(@as, 1) != RC_NOT_CONNECTED)
{
int aax = ax + RecastCommon.GetDirOffsetX(1);
int aay = ay + RecastCommon.GetDirOffsetY(1);
int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 1);
nd = Math.Min(dist[aai] + 3, 255);
if (nd < dist[i])
dist[i] = nd;
}
}
if (RecastCommon.GetCon(s, 1) != RC_NOT_CONNECTED)
{
// (0,1)
int ax = x + RecastCommon.GetDirOffsetX(1);
int ay = y + RecastCommon.GetDirOffsetY(1);
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 1);
CompactSpan @as = chf.spans[ai];
nd = Math.Min(dist[ai] + 2, 255);
if (nd < dist[i])
dist[i] = nd;
// (-1,1)
if (RecastCommon.GetCon(@as, 0) != RC_NOT_CONNECTED)
{
int aax = ax + RecastCommon.GetDirOffsetX(0);
int aay = ay + RecastCommon.GetDirOffsetY(0);
int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 0);
nd = Math.Min(dist[aai] + 3, 255);
if (nd < dist[i])
dist[i] = nd;
}
}
}
}
}
int thr = radius * 2;
for (int i = 0; i < chf.spanCount; ++i)
if (dist[i] < thr)
chf.areas[i] = RC_NULL_AREA;
ctx.stopTimer("ERODE_AREA");
}
/// @par
///
/// This filter is usually applied after applying area id's using functions
/// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea.
///
/// @see rcCompactHeightfield
public bool medianFilterWalkableArea(Telemetry ctx, CompactHeightfield chf)
{
int w = chf.width;
int h = chf.height;
ctx.startTimer("MEDIAN_AREA");
int[] areas = new int[chf.spanCount];
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
{
areas[i] = chf.areas[i];
continue;
}
int[] nei = new int[9];
for (int j = 0; j < 9; ++j)
nei[j] = chf.areas[i];
for (int dir = 0; dir < 4; ++dir)
{
if (RecastCommon.GetCon(s, dir) != RC_NOT_CONNECTED)
{
int ax = x + RecastCommon.GetDirOffsetX(dir);
int ay = y + RecastCommon.GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, dir);
if (chf.areas[ai] != RC_NULL_AREA)
nei[dir * 2 + 0] = chf.areas[ai];
CompactSpan @as = chf.spans[ai];
int dir2 = (dir + 1) & 0x3;
if (RecastCommon.GetCon(@as, dir2) != RC_NOT_CONNECTED)
{
int ax2 = ax + RecastCommon.GetDirOffsetX(dir2);
int ay2 = ay + RecastCommon.GetDirOffsetY(dir2);
int ai2 = chf.cells[ax2 + ay2 * w].index + RecastCommon.GetCon(@as, dir2);
if (chf.areas[ai2] != RC_NULL_AREA)
nei[dir * 2 + 1] = chf.areas[ai2];
}
}
}
Array.Sort(nei);
areas[i] = nei[4];
}
}
}
chf.areas = areas;
ctx.stopTimer("MEDIAN_AREA");
return true;
}
/// @par
///
/// The value of spacial parameters are in world units.
///
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
public void markBoxArea(Telemetry ctx, float[] bmin, float[] bmax, AreaModification areaMod, CompactHeightfield chf)
{
ctx.startTimer("MARK_BOX_AREA");
int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs);
int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch);
int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs);
int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs);
int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch);
int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs);
if (maxx < 0)
return;
if (minx >= chf.width)
return;
if (maxz < 0)
return;
if (minz >= chf.height)
return;
if (minx < 0)
minx = 0;
if (maxx >= chf.width)
maxx = chf.width - 1;
if (minz < 0)
minz = 0;
if (maxz >= chf.height)
maxz = chf.height - 1;
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
CompactCell c = chf.cells[x + z * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
if (s.y >= miny && s.y <= maxy)
{
if (chf.areas[i] != RC_NULL_AREA)
chf.areas[i] = areaMod.apply(chf.areas[i]);
}
}
}
}
ctx.stopTimer("MARK_BOX_AREA");
}
static bool pointInPoly(float[] verts, float[] p)
{
bool c = false;
int i, j;
for (i = 0, j = verts.Length - 3; i < verts.Length; j = i, i += 3)
{
int vi = i;
int vj = j;
if (((verts[vi + 2] > p[2]) != (verts[vj + 2] > p[2]))
&& (p[0] < (verts[vj] - verts[vi]) * (p[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2])
+ verts[vi]))
c = !c;
}
return c;
}
/// @par
///
/// The value of spacial parameters are in world units.
///
/// The y-values of the polygon vertices are ignored. So the polygon is effectively
/// projected onto the xz-plane at @p hmin, then extruded to @p hmax.
///
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
public static void markConvexPolyArea(Telemetry ctx, float[] verts, float hmin, float hmax, AreaModification areaMod,
CompactHeightfield chf)
{
ctx.startTimer("MARK_CONVEXPOLY_AREA");
float[] bmin = new float[3];
float[] bmax = new float[3];
RecastVectors.copy(bmin, verts, 0);
RecastVectors.copy(bmax, verts, 0);
for (int i = 3; i < verts.Length; i += 3)
{
RecastVectors.min(bmin, verts, i);
RecastVectors.max(bmax, verts, i);
}
bmin[1] = hmin;
bmax[1] = hmax;
int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs);
int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch);
int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs);
int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs);
int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch);
int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs);
if (maxx < 0)
return;
if (minx >= chf.width)
return;
if (maxz < 0)
return;
if (minz >= chf.height)
return;
if (minx < 0)
minx = 0;
if (maxx >= chf.width)
maxx = chf.width - 1;
if (minz < 0)
minz = 0;
if (maxz >= chf.height)
maxz = chf.height - 1;
// TODO: Optimize.
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
CompactCell c = chf.cells[x + z * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
if (s.y >= miny && s.y <= maxy)
{
float[] p = new float[3];
p[0] = chf.bmin[0] + (x + 0.5f) * chf.cs;
p[1] = 0;
p[2] = chf.bmin[2] + (z + 0.5f) * chf.cs;
if (pointInPoly(verts, p))
{
chf.areas[i] = areaMod.apply(chf.areas[i]);
}
}
}
}
}
ctx.stopTimer("MARK_CONVEXPOLY_AREA");
}
int offsetPoly(float[] verts, int nverts, float offset, float[] outVerts, int maxOutVerts)
{
float MITER_LIMIT = 1.20f;
int n = 0;
for (int i = 0; i < nverts; i++)
{
int a = (i + nverts - 1) % nverts;
int b = i;
int c = (i + 1) % nverts;
int va = a * 3;
int vb = b * 3;
int vc = c * 3;
float dx0 = verts[vb] - verts[va];
float dy0 = verts[vb + 2] - verts[va + 2];
float d0 = dx0 * dx0 + dy0 * dy0;
if (d0 > 1e-6f)
{
d0 = (float)(1.0f / Math.Sqrt(d0));
dx0 *= d0;
dy0 *= d0;
}
float dx1 = verts[vc] - verts[vb];
float dy1 = verts[vc + 2] - verts[vb + 2];
float d1 = dx1 * dx1 + dy1 * dy1;
if (d1 > 1e-6f)
{
d1 = (float)(1.0f / Math.Sqrt(d1));
dx1 *= d1;
dy1 *= d1;
}
float dlx0 = -dy0;
float dly0 = dx0;
float dlx1 = -dy1;
float dly1 = dx1;
float cross = dx1 * dy0 - dx0 * dy1;
float dmx = (dlx0 + dlx1) * 0.5f;
float dmy = (dly0 + dly1) * 0.5f;
float dmr2 = dmx * dmx + dmy * dmy;
bool bevel = dmr2 * MITER_LIMIT * MITER_LIMIT < 1.0f;
if (dmr2 > 1e-6f)
{
float scale = 1.0f / dmr2;
dmx *= scale;
dmy *= scale;
}
if (bevel && cross < 0.0f)
{
if (n + 2 >= maxOutVerts)
return 0;
float d = (1.0f - (dx0 * dx1 + dy0 * dy1)) * 0.5f;
outVerts[n * 3 + 0] = verts[vb] + (-dlx0 + dx0 * d) * offset;
outVerts[n * 3 + 1] = verts[vb + 1];
outVerts[n * 3 + 2] = verts[vb + 2] + (-dly0 + dy0 * d) * offset;
n++;
outVerts[n * 3 + 0] = verts[vb] + (-dlx1 - dx1 * d) * offset;
outVerts[n * 3 + 1] = verts[vb + 1];
outVerts[n * 3 + 2] = verts[vb + 2] + (-dly1 - dy1 * d) * offset;
n++;
}
else
{
if (n + 1 >= maxOutVerts)
return 0;
outVerts[n * 3 + 0] = verts[vb] - dmx * offset;
outVerts[n * 3 + 1] = verts[vb + 1];
outVerts[n * 3 + 2] = verts[vb + 2] - dmy * offset;
n++;
}
}
return n;
}
/// @par
///
/// The value of spacial parameters are in world units.
///
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
public void markCylinderArea(Telemetry ctx, float[] pos, float r, float h, AreaModification areaMod,
CompactHeightfield chf)
{
ctx.startTimer("MARK_CYLINDER_AREA");
float[] bmin = new float[3];
float[] bmax = new float[3];
bmin[0] = pos[0] - r;
bmin[1] = pos[1];
bmin[2] = pos[2] - r;
bmax[0] = pos[0] + r;
bmax[1] = pos[1] + h;
bmax[2] = pos[2] + r;
float r2 = r * r;
int minx = (int)((bmin[0] - chf.bmin[0]) / chf.cs);
int miny = (int)((bmin[1] - chf.bmin[1]) / chf.ch);
int minz = (int)((bmin[2] - chf.bmin[2]) / chf.cs);
int maxx = (int)((bmax[0] - chf.bmin[0]) / chf.cs);
int maxy = (int)((bmax[1] - chf.bmin[1]) / chf.ch);
int maxz = (int)((bmax[2] - chf.bmin[2]) / chf.cs);
if (maxx < 0)
return;
if (minx >= chf.width)
return;
if (maxz < 0)
return;
if (minz >= chf.height)
return;
if (minx < 0)
minx = 0;
if (maxx >= chf.width)
maxx = chf.width - 1;
if (minz < 0)
minz = 0;
if (maxz >= chf.height)
maxz = chf.height - 1;
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
CompactCell c = chf.cells[x + z * chf.width];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
if (s.y >= miny && s.y <= maxy)
{
float sx = chf.bmin[0] + (x + 0.5f) * chf.cs;
float sz = chf.bmin[2] + (z + 0.5f) * chf.cs;
float dx = sx - pos[0];
float dz = sz - pos[2];
if (dx * dx + dz * dz < r2)
{
chf.areas[i] = areaMod.apply(chf.areas[i]);
}
}
}
}
}
ctx.stopTimer("MARK_CYLINDER_AREA");
}
}

View File

@ -0,0 +1,319 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using DotRecast.Core;
using DotRecast.Recast.Geom;
namespace DotRecast.Recast;
public class RecastBuilder
{
public interface RecastBuilderProgressListener
{
void onProgress(int completed, int total);
}
private readonly RecastBuilderProgressListener progressListener;
public RecastBuilder()
{
progressListener = null;
}
public RecastBuilder(RecastBuilderProgressListener progressListener)
{
this.progressListener = progressListener;
}
public List<RecastBuilderResult> buildTiles(InputGeomProvider geom, RecastConfig cfg, TaskFactory taskFactory) {
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
int[] twh = Recast.calcTileCount(bmin, bmax, cfg.cs, cfg.tileSizeX, cfg.tileSizeZ);
int tw = twh[0];
int th = twh[1];
List<RecastBuilderResult> results = new();
if (null != taskFactory)
{
buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default);
} else {
buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
}
return results;
}
public Task buildTilesAsync(InputGeomProvider geom, RecastConfig cfg, int threads, List<RecastBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
{
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
int[] twh = Recast.calcTileCount(bmin, bmax, cfg.cs, cfg.tileSizeX, cfg.tileSizeZ);
int tw = twh[0];
int th = twh[1];
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(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax,
int tw, int th, List<RecastBuilderResult> results)
{
AtomicInteger counter = new AtomicInteger(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(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax,
int tw, int th, List<RecastBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
{
AtomicInteger counter = new AtomicInteger(0);
CountdownEvent latch = new CountdownEvent(tw * th);
List<Task> tasks = new();
for (int x = 0; x < tw; ++x)
{
for (int y = 0; y < th; ++y)
{
int tx = x;
int ty = y;
var task = taskFactory.StartNew(() =>
{
if (cancellationToken.IsCancellationRequested)
return;
try
{
RecastBuilderResult tile = buildTile(geom, cfg, bmin, bmax, tx, ty, counter, tw * th);
lock (results)
{
results.Add(tile);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
latch.Signal();
}, cancellationToken);
tasks.Add(task);
}
}
try
{
latch.Wait();
}
catch (ThreadInterruptedException e)
{
}
return Task.WhenAll(tasks.ToArray());
}
private RecastBuilderResult buildTile(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax, int tx,
int ty, AtomicInteger counter, int total)
{
RecastBuilderResult result = build(geom, new RecastBuilderConfig(cfg, bmin, bmax, tx, ty));
if (progressListener != null)
{
progressListener.onProgress(counter.IncrementAndGet(), total);
}
return result;
}
public RecastBuilderResult build(InputGeomProvider geom, RecastBuilderConfig builderCfg)
{
RecastConfig cfg = builderCfg.cfg;
Telemetry ctx = new Telemetry();
//
// Step 1. Rasterize input polygon soup.
//
Heightfield solid = RecastVoxelization.buildSolidHeightfield(geom, builderCfg, ctx);
return build(builderCfg.tileX, builderCfg.tileZ, geom, cfg, solid, ctx);
}
public RecastBuilderResult build(int tileX, int tileZ, ConvexVolumeProvider geom, RecastConfig cfg, Heightfield solid,
Telemetry ctx)
{
filterHeightfield(solid, cfg, ctx);
CompactHeightfield chf = buildCompactHeightfield(geom, cfg, ctx, solid);
// Partition the heightfield so that we can use simple algorithm later
// to triangulate the walkable areas.
// There are 3 martitioning methods, each with some pros and cons:
// 1) Watershed partitioning
// - the classic Recast partitioning
// - creates the nicest tessellation
// - usually slowest
// - partitions the heightfield into nice regions without holes or
// overlaps
// - the are some corner cases where this method creates produces holes
// and overlaps
// - holes may appear when a small obstacles is close to large open area
// (triangulation can handle this)
// - overlaps may occur if you have narrow spiral corridors (i.e
// stairs), this make triangulation to fail
// * generally the best choice if you precompute the nacmesh, use this
// if you have large open areas
// 2) Monotone partioning
// - fastest
// - partitions the heightfield into regions without holes and overlaps
// (guaranteed)
// - creates long thin polygons, which sometimes causes paths with
// detours
// * use this if you want fast navmesh generation
// 3) Layer partitoining
// - quite fast
// - partitions the heighfield into non-overlapping regions
// - relies on the triangulation code to cope with holes (thus slower
// than monotone partitioning)
// - produces better triangles than monotone partitioning
// - does not have the corner cases of watershed partitioning
// - can be slow and create a bit ugly tessellation (still better than
// monotone)
// if you have large open areas with small obstacles (not a problem if
// you use tiles)
// * good choice to use for tiled navmesh with medium and small sized
// tiles
if (cfg.partitionType == PartitionType.WATERSHED)
{
// Prepare for region partitioning, by calculating distance field
// along the walkable surface.
RecastRegion.buildDistanceField(ctx, chf);
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildRegions(ctx, chf, cfg.minRegionArea, cfg.mergeRegionArea);
}
else if (cfg.partitionType == PartitionType.MONOTONE)
{
// Partition the walkable surface into simple regions without holes.
// Monotone partitioning does not need distancefield.
RecastRegion.buildRegionsMonotone(ctx, chf, cfg.minRegionArea, cfg.mergeRegionArea);
}
else
{
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildLayerRegions(ctx, chf, cfg.minRegionArea);
}
//
// Step 5. Trace and simplify region contours.
//
// Create contours.
ContourSet cset = RecastContour.buildContours(ctx, chf, cfg.maxSimplificationError, cfg.maxEdgeLen,
RecastConstants.RC_CONTOUR_TESS_WALL_EDGES);
//
// Step 6. Build polygons mesh from contours.
//
PolyMesh pmesh = RecastMesh.buildPolyMesh(ctx, cset, cfg.maxVertsPerPoly);
//
// Step 7. Create detail mesh which allows to access approximate height
// on each polygon.
//
PolyMeshDetail dmesh = cfg.buildMeshDetail
? RecastMeshDetail.buildPolyMeshDetail(ctx, pmesh, chf, cfg.detailSampleDist, cfg.detailSampleMaxError)
: null;
return new RecastBuilderResult(tileX, tileZ, solid, chf, cset, pmesh, dmesh, ctx);
}
/*
* Step 2. Filter walkable surfaces.
*/
private void filterHeightfield(Heightfield solid, RecastConfig cfg, Telemetry ctx)
{
// Once all geometry is rasterized, we do initial pass of filtering to
// remove unwanted overhangs caused by the conservative rasterization
// as well as filter spans where the character cannot possibly stand.
if (cfg.filterLowHangingObstacles)
{
RecastFilter.filterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, solid);
}
if (cfg.filterLedgeSpans)
{
RecastFilter.filterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, solid);
}
if (cfg.filterWalkableLowHeightSpans)
{
RecastFilter.filterWalkableLowHeightSpans(ctx, cfg.walkableHeight, solid);
}
}
/*
* Step 3. Partition walkable surface to simple regions.
*/
private CompactHeightfield buildCompactHeightfield(ConvexVolumeProvider volumeProvider, RecastConfig cfg, Telemetry ctx,
Heightfield solid)
{
// Compact the heightfield so that it is faster to handle from now on.
// This will result more cache coherent data as well as the neighbours
// between walkable cells will be calculated.
CompactHeightfield chf = RecastCompact.buildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, solid);
// Erode the walkable area by agent radius.
RecastArea.erodeWalkableArea(ctx, cfg.walkableRadius, chf);
// (Optional) Mark areas.
if (volumeProvider != null)
{
foreach (ConvexVolume vol in volumeProvider.convexVolumes())
{
RecastArea.markConvexPolyArea(ctx, vol.verts, vol.hmin, vol.hmax, vol.areaMod, chf);
}
}
return chf;
}
public HeightfieldLayerSet buildLayers(InputGeomProvider geom, RecastBuilderConfig builderCfg)
{
Telemetry ctx = new Telemetry();
Heightfield solid = RecastVoxelization.buildSolidHeightfield(geom, builderCfg, ctx);
filterHeightfield(solid, builderCfg.cfg, ctx);
CompactHeightfield chf = buildCompactHeightfield(geom, builderCfg.cfg, ctx, solid);
return RecastLayers.buildHeightfieldLayers(ctx, chf, builderCfg.cfg.walkableHeight);
}
}

View File

@ -0,0 +1,99 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
using static RecastVectors;
public class RecastBuilderConfig
{
public readonly RecastConfig cfg;
public readonly int tileX;
public readonly int tileZ;
/** The width of the field along the x-axis. [Limit: >= 0] [Units: vx] **/
public readonly int width;
/** The height of the field along the z-axis. [Limit: >= 0] [Units: vx] **/
public readonly int height;
/** The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] **/
public readonly float[] bmin = new float[3];
/** The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] **/
public readonly float[] bmax = new float[3];
public RecastBuilderConfig(RecastConfig cfg, float[] bmin, float[] bmax) : this(cfg, bmin, bmax, 0, 0)
{
}
public RecastBuilderConfig(RecastConfig cfg, float[] bmin, float[] bmax, int tileX, int tileZ)
{
this.tileX = tileX;
this.tileZ = tileZ;
this.cfg = cfg;
copy(this.bmin, bmin);
copy(this.bmax, bmax);
if (cfg.useTiles)
{
float tsx = cfg.tileSizeX * cfg.cs;
float tsz = cfg.tileSizeZ * cfg.cs;
this.bmin[0] += tileX * tsx;
this.bmin[2] += tileZ * tsz;
this.bmax[0] = this.bmin[0] + tsx;
this.bmax[2] = this.bmin[2] + tsz;
// Expand the heighfield bounding box by border size to find the extents of geometry we need to build this
// tile.
//
// This is done in order to make sure that the navmesh tiles connect correctly at the borders,
// and the obstacles close to the border work correctly with the dilation process.
// No polygons (or contours) will be created on the border area.
//
// IMPORTANT!
//
// :''''''''':
// : +-----+ :
// : | | :
// : | |<--- tile to build
// : | | :
// : +-----+ :<-- geometry needed
// :.........:
//
// You should use this bounding box to query your input geometry.
//
// For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size
// you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8
// neighbours,
// or use the bounding box below to only pass in a sliver of each of the 8 neighbours.
this.bmin[0] -= cfg.borderSize * cfg.cs;
this.bmin[2] -= cfg.borderSize * cfg.cs;
this.bmax[0] += cfg.borderSize * cfg.cs;
this.bmax[2] += cfg.borderSize * cfg.cs;
width = cfg.tileSizeX + cfg.borderSize * 2;
height = cfg.tileSizeZ + cfg.borderSize * 2;
}
else
{
int[] wh = Recast.calcGridSize(this.bmin, this.bmax, cfg.cs);
width = wh[0];
height = wh[1];
}
}
}

View File

@ -0,0 +1,56 @@
namespace DotRecast.Recast;
public class RecastBuilderResult
{
public readonly int tileX;
public readonly int tileZ;
private readonly CompactHeightfield chf;
private readonly ContourSet cs;
private readonly PolyMesh pmesh;
private readonly PolyMeshDetail dmesh;
private readonly Heightfield solid;
private readonly Telemetry telemetry;
public RecastBuilderResult(int tileX, int tileZ, Heightfield solid, CompactHeightfield chf, ContourSet cs, PolyMesh pmesh,
PolyMeshDetail dmesh, Telemetry ctx)
{
this.tileX = tileX;
this.tileZ = tileZ;
this.solid = solid;
this.chf = chf;
this.cs = cs;
this.pmesh = pmesh;
this.dmesh = dmesh;
telemetry = ctx;
}
public PolyMesh getMesh()
{
return pmesh;
}
public PolyMeshDetail getMeshDetail()
{
return dmesh;
}
public CompactHeightfield getCompactHeightfield()
{
return chf;
}
public ContourSet getContourSet()
{
return cs;
}
public Heightfield getSolidHeightfield()
{
return solid;
}
public Telemetry getTelemetry()
{
return telemetry;
}
}

View File

@ -0,0 +1,84 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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;
namespace DotRecast.Recast;
public class RecastCommon
{
/// Gets neighbor connection data for the specified direction.
/// @param[in] s The span to check.
/// @param[in] dir The direction to check. [Limits: 0 <= value < 4]
/// @return The neighbor connection data for the specified direction,
/// or #RC_NOT_CONNECTED if there is no connection.
public static int GetCon(CompactSpan s, int dir)
{
int shift = dir * 6;
return (s.con >> shift) & 0x3f;
}
/// Gets the standard width (x-axis) offset for the specified direction.
/// @param[in] dir The direction. [Limits: 0 <= value < 4]
/// @return The width offset to apply to the current cell position to move
/// in the direction.
public static int GetDirOffsetX(int dir)
{
int[] offset = { -1, 0, 1, 0, };
return offset[dir & 0x03];
}
/// Gets the standard height (z-axis) offset for the specified direction.
/// @param[in] dir The direction. [Limits: 0 <= value < 4]
/// @return The height offset to apply to the current cell position to move
/// in the direction.
public static int GetDirOffsetY(int dir)
{
int[] offset = { 0, 1, 0, -1 };
return offset[dir & 0x03];
}
/// Gets the direction for the specified offset. One of x and y should be 0.
/// @param[in] x The x offset. [Limits: -1 <= value <= 1]
/// @param[in] y The y offset. [Limits: -1 <= value <= 1]
/// @return The direction that represents the offset.
public static int rcGetDirForOffset(int x, int y)
{
int[] dirs = { 3, 0, -1, 2, 1 };
return dirs[((y + 1) << 1) + x];
}
/// Sets the neighbor connection data for the specified direction.
/// @param[in] s The span to update.
/// @param[in] dir The direction to set. [Limits: 0 <= value < 4]
/// @param[in] i The index of the neighbor span.
public static void SetCon(CompactSpan s, int dir, int i)
{
int shift = dir * 6;
int con = s.con;
s.con = (con & ~(0x3f << shift)) | ((i & 0x3f) << shift);
}
public static int clamp(int v, int min, int max)
{
return Math.Max(Math.Min(max, v), min);
}
}

View File

@ -0,0 +1,186 @@
/*
recast4j copyright (c) 2021 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;
namespace DotRecast.Recast;
using static RecastConstants;
using static RecastVectors;
public class RecastCompact
{
private const int MAX_LAYERS = RC_NOT_CONNECTED - 1;
private const int MAX_HEIGHT = RecastConstants.SPAN_MAX_HEIGHT;
/// @par
///
/// 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.
/// E.g: #rcBuildDistanceField and #rcBuildRegions
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig
public static CompactHeightfield buildCompactHeightfield(Telemetry ctx, int walkableHeight, int walkableClimb,
Heightfield hf)
{
ctx.startTimer("BUILD_COMPACTHEIGHTFIELD");
CompactHeightfield chf = new CompactHeightfield();
int w = hf.width;
int h = hf.height;
int spanCount = getHeightFieldSpanCount(hf);
// Fill in header.
chf.width = w;
chf.height = h;
chf.borderSize = hf.borderSize;
chf.spanCount = spanCount;
chf.walkableHeight = walkableHeight;
chf.walkableClimb = walkableClimb;
chf.maxRegions = 0;
copy(chf.bmin, hf.bmin);
copy(chf.bmax, hf.bmax);
chf.bmax[1] += walkableHeight * hf.ch;
chf.cs = hf.cs;
chf.ch = hf.ch;
chf.cells = new CompactCell[w * h];
chf.spans = new CompactSpan[spanCount];
chf.areas = new int[spanCount];
for (int i = 0; i < chf.cells.Length; i++)
{
chf.cells[i] = new CompactCell();
}
for (int i = 0; i < chf.spans.Length; i++)
{
chf.spans[i] = new CompactSpan();
}
// Fill in cells and spans.
int idx = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
Span s = hf.spans[x + y * w];
// If there are no spans at this cell, just leave the data to index=0, count=0.
if (s == null)
continue;
CompactCell c = chf.cells[x + y * w];
c.index = idx;
c.count = 0;
while (s != null)
{
if (s.area != RC_NULL_AREA)
{
int bot = s.smax;
int top = s.next != null ? (int)s.next.smin : MAX_HEIGHT;
chf.spans[idx].y = RecastCommon.clamp(bot, 0, MAX_HEIGHT);
chf.spans[idx].h = RecastCommon.clamp(top - bot, 0, MAX_HEIGHT);
chf.areas[idx] = s.area;
idx++;
c.count++;
}
s = s.next;
}
}
}
// Find neighbour connections.
int tooHighNeighbour = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
for (int dir = 0; dir < 4; ++dir)
{
RecastCommon.SetCon(s, dir, RC_NOT_CONNECTED);
int nx = x + RecastCommon.GetDirOffsetX(dir);
int ny = y + RecastCommon.GetDirOffsetY(dir);
// First check that the neighbour cell is in bounds.
if (nx < 0 || ny < 0 || nx >= w || ny >= h)
continue;
// Iterate over all neighbour spans and check if any of the is
// accessible from current cell.
CompactCell nc = chf.cells[nx + ny * w];
for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k)
{
CompactSpan ns = chf.spans[k];
int bot = Math.Max(s.y, ns.y);
int top = Math.Min(s.y + s.h, ns.y + ns.h);
// Check that the gap between the spans is walkable,
// and that the climb height between the gaps is not too high.
if ((top - bot) >= walkableHeight && Math.Abs(ns.y - s.y) <= walkableClimb)
{
// Mark direction as walkable.
int lidx = k - nc.index;
if (lidx < 0 || lidx > MAX_LAYERS)
{
tooHighNeighbour = Math.Max(tooHighNeighbour, lidx);
continue;
}
RecastCommon.SetCon(s, dir, lidx);
break;
}
}
}
}
}
}
if (tooHighNeighbour > MAX_LAYERS)
{
throw new Exception("rcBuildCompactHeightfield: Heightfield has too many layers " + tooHighNeighbour
+ " (max: " + MAX_LAYERS + ")");
}
ctx.stopTimer("BUILD_COMPACTHEIGHTFIELD");
return chf;
}
private static int getHeightFieldSpanCount(Heightfield hf)
{
int w = hf.width;
int h = hf.height;
int spanCount = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
for (Span s = hf.spans[x + y * w]; s != null; s = s.next)
{
if (s.area != RC_NULL_AREA)
spanCount++;
}
}
}
return spanCount;
}
}

View File

@ -0,0 +1,185 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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;
namespace DotRecast.Recast;
using static RecastConstants;
public class RecastConfig
{
public readonly PartitionType partitionType;
public readonly bool useTiles;
/** The width/depth size of tile's on the xz-plane. [Limit: &gt;= 0] [Units: vx] **/
public readonly int tileSizeX;
public readonly int tileSizeZ;
/** The xz-plane cell size to use for fields. [Limit: &gt; 0] [Units: wu] **/
public readonly float cs;
/** The y-axis cell size to use for fields. [Limit: &gt; 0] [Units: wu] **/
public readonly float ch;
/** The maximum slope that is considered walkable. [Limits: 0 &lt;= value &lt; 90] [Units: Degrees] **/
public readonly float walkableSlopeAngle;
/**
* Minimum floor to 'ceiling' height that will still allow the floor area to be considered walkable. [Limit: &gt;= 3]
* [Units: vx]
**/
public readonly int walkableHeight;
/** Maximum ledge height that is considered to still be traversable. [Limit: &gt;=0] [Units: vx] **/
public readonly int walkableClimb;
/**
* The distance to erode/shrink the walkable area of the heightfield away from obstructions. [Limit: &gt;=0] [Units:
* vx]
**/
public readonly int walkableRadius;
/** The maximum allowed length for contour edges along the border of the mesh. [Limit: &gt;=0] [Units: vx] **/
public readonly int maxEdgeLen;
/**
* The maximum distance a simplfied contour's border edges should deviate the original raw contour. [Limit: &gt;=0]
* [Units: vx]
**/
public readonly float maxSimplificationError;
/** The minimum number of cells allowed to form isolated island areas. [Limit: &gt;=0] [Units: vx] **/
public readonly int minRegionArea;
/**
* Any regions with a span count smaller than this value will, if possible, be merged with larger regions. [Limit:
* &gt;=0] [Units: vx]
**/
public readonly int mergeRegionArea;
/**
* The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process.
* [Limit: &gt;= 3]
**/
public readonly int maxVertsPerPoly;
/**
* Sets the sampling distance to use when generating the detail mesh. (For height detail only.) [Limits: 0 or >=
* 0.9] [Units: wu]
**/
public readonly float detailSampleDist;
/**
* The maximum distance the detail mesh surface should deviate from heightfield data. (For height detail only.)
* [Limit: &gt;=0] [Units: wu]
**/
public readonly float detailSampleMaxError;
public readonly AreaModification walkableAreaMod;
public readonly bool filterLowHangingObstacles;
public readonly bool filterLedgeSpans;
public readonly bool filterWalkableLowHeightSpans;
/** Set to false to disable building detailed mesh **/
public readonly bool buildMeshDetail;
/** The size of the non-navigable border around the heightfield. [Limit: &gt;=0] [Units: vx] **/
public readonly int borderSize;
/** Set of original settings passed in world units */
public readonly float minRegionAreaWorld;
public readonly float mergeRegionAreaWorld;
public readonly float walkableHeightWorld;
public readonly float walkableClimbWorld;
public readonly float walkableRadiusWorld;
public readonly float maxEdgeLenWorld;
/**
* Non-tiled build configuration
*/
public RecastConfig(PartitionType partitionType, float cellSize, float cellHeight, float agentHeight, float agentRadius,
float agentMaxClimb, float agentMaxSlope, int regionMinSize, int regionMergeSize, float edgeMaxLen,
float edgeMaxError, int vertsPerPoly, float detailSampleDist, float detailSampleMaxError,
AreaModification walkableAreaMod) : this(partitionType, cellSize, cellHeight, agentMaxSlope, true, true, true, agentHeight, agentRadius, agentMaxClimb,
regionMinSize, regionMergeSize, edgeMaxLen, edgeMaxError, vertsPerPoly, detailSampleDist, detailSampleMaxError,
walkableAreaMod, true)
{
}
/**
* Non-tiled build configuration
*/
public RecastConfig(PartitionType partitionType, float cellSize, float cellHeight, float agentMaxSlope,
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans, float agentHeight,
float agentRadius, float agentMaxClimb, int regionMinSize, int regionMergeSize, float edgeMaxLen, float edgeMaxError,
int vertsPerPoly, float detailSampleDist, float detailSampleMaxError, AreaModification walkableAreaMod,
bool buildMeshDetail) : this(false, 0, 0, 0, partitionType, cellSize, cellHeight, agentMaxSlope, filterLowHangingObstacles, filterLedgeSpans,
filterWalkableLowHeightSpans, agentHeight, agentRadius, agentMaxClimb,
regionMinSize * regionMinSize * cellSize * cellSize, regionMergeSize * regionMergeSize * cellSize * cellSize,
edgeMaxLen, edgeMaxError, vertsPerPoly, buildMeshDetail, detailSampleDist, detailSampleMaxError, walkableAreaMod)
{
// Note: area = size*size in [Units: wu]
}
public RecastConfig(bool useTiles, int tileSizeX, int tileSizeZ, int borderSize, PartitionType partitionType,
float cellSize, float cellHeight, float agentMaxSlope, bool filterLowHangingObstacles, bool filterLedgeSpans,
bool filterWalkableLowHeightSpans, float agentHeight, float agentRadius, float agentMaxClimb, float minRegionArea,
float mergeRegionArea, float edgeMaxLen, float edgeMaxError, int vertsPerPoly, bool buildMeshDetail,
float detailSampleDist, float detailSampleMaxError, AreaModification walkableAreaMod)
{
this.useTiles = useTiles;
this.tileSizeX = tileSizeX;
this.tileSizeZ = tileSizeZ;
this.borderSize = borderSize;
this.partitionType = partitionType;
cs = cellSize;
ch = cellHeight;
walkableSlopeAngle = agentMaxSlope;
walkableHeight = (int)Math.Ceiling(agentHeight / ch);
walkableHeightWorld = agentHeight;
walkableClimb = (int)Math.Floor(agentMaxClimb / ch);
walkableClimbWorld = agentMaxClimb;
walkableRadius = (int)Math.Ceiling(agentRadius / cs);
walkableRadiusWorld = agentRadius;
this.minRegionArea = (int)Math.Round(minRegionArea / (cs * cs));
minRegionAreaWorld = minRegionArea;
this.mergeRegionArea = (int)Math.Round(mergeRegionArea / (cs * cs));
mergeRegionAreaWorld = mergeRegionArea;
maxEdgeLen = (int)(edgeMaxLen / cellSize);
maxEdgeLenWorld = edgeMaxLen;
maxSimplificationError = edgeMaxError;
maxVertsPerPoly = vertsPerPoly;
this.detailSampleDist = detailSampleDist < 0.9f ? 0 : cellSize * detailSampleDist;
this.detailSampleMaxError = cellHeight * detailSampleMaxError;
this.walkableAreaMod = walkableAreaMod;
this.filterLowHangingObstacles = filterLowHangingObstacles;
this.filterLedgeSpans = filterLedgeSpans;
this.filterWalkableLowHeightSpans = filterWalkableLowHeightSpans;
this.buildMeshDetail = buildMeshDetail;
}
public static int calcBorder(float agentRadius, float cs)
{
return 3 + (int)Math.Ceiling(agentRadius / cs);
}
}

View File

@ -0,0 +1,84 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
public static class RecastConstants
{
public const int RC_NULL_AREA = 0;
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_CONTOUR_TESS_WALL_EDGES = 0x01;
/// < Tessellate solid (impassable) edges during contour
/// simplification.
public const int RC_CONTOUR_TESS_AREA_EDGES = 0x02;
public const int RC_LOG_WARNING = 1;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,802 @@
/*
+recast4j copyright (c) 2021 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 DotRecast.Core;
namespace DotRecast.Recast;
using static RecastConstants;
using static RecastVectors;
using static RecastCommon;
public class RecastFilledVolumeRasterization
{
private const float EPSILON = 0.00001f;
private static readonly int[] BOX_EDGES = new[] { 0, 1, 0, 2, 0, 4, 1, 3, 1, 5, 2, 3, 2, 6, 3, 7, 4, 5, 4, 6, 5, 7, 6, 7 };
public static void rasterizeSphere(Heightfield hf, float[] center, float radius, int area, int flagMergeThr, Telemetry ctx)
{
ctx.startTimer("RASTERIZE_SPHERE");
float[] bounds =
{
center[0] - radius, center[1] - radius, center[2] - radius, center[0] + radius, center[1] + radius,
center[2] + radius
};
rasterizationFilledShape(hf, bounds, area, flagMergeThr,
rectangle => intersectSphere(rectangle, center, radius * radius));
ctx.stopTimer("RASTERIZE_SPHERE");
}
public static void rasterizeCapsule(Heightfield hf, float[] start, float[] end, float radius, int area, int flagMergeThr,
Telemetry ctx)
{
ctx.startTimer("RASTERIZE_CAPSULE");
float[] bounds =
{
Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
Math.Min(start[2], end[2]) - radius, Math.Max(start[0], end[0]) + radius, Math.Max(start[1], end[1]) + radius,
Math.Max(start[2], end[2]) + radius
};
float[] axis = { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
rasterizationFilledShape(hf, bounds, area, flagMergeThr,
rectangle => intersectCapsule(rectangle, start, end, axis, radius * radius));
ctx.stopTimer("RASTERIZE_CAPSULE");
}
public static void rasterizeCylinder(Heightfield hf, float[] start, float[] end, float radius, int area, int flagMergeThr,
Telemetry ctx)
{
ctx.startTimer("RASTERIZE_CYLINDER");
float[] bounds =
{
Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
Math.Min(start[2], end[2]) - radius, Math.Max(start[0], end[0]) + radius, Math.Max(start[1], end[1]) + radius,
Math.Max(start[2], end[2]) + radius
};
float[] axis = { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
rasterizationFilledShape(hf, bounds, area, flagMergeThr,
rectangle => intersectCylinder(rectangle, start, end, axis, radius * radius));
ctx.stopTimer("RASTERIZE_CYLINDER");
}
public static void rasterizeBox(Heightfield hf, float[] center, float[][] halfEdges, int area, int flagMergeThr,
Telemetry ctx)
{
ctx.startTimer("RASTERIZE_BOX");
float[][] normals =
{
new[] { halfEdges[0][0], halfEdges[0][1], halfEdges[0][2] },
new[] { halfEdges[1][0], halfEdges[1][1], halfEdges[1][2] },
new[] { halfEdges[2][0], halfEdges[2][1], halfEdges[2][2] }
};
normalize(normals[0]);
normalize(normals[1]);
normalize(normals[2]);
float[] vertices = new float[8 * 3];
float[] bounds = new float[]
{
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
};
for (int i = 0; i < 8; ++i)
{
float s0 = (i & 1) != 0 ? 1f : -1f;
float s1 = (i & 2) != 0 ? 1f : -1f;
float s2 = (i & 4) != 0 ? 1f : -1f;
vertices[i * 3 + 0] = center[0] + s0 * halfEdges[0][0] + s1 * halfEdges[1][0] + s2 * halfEdges[2][0];
vertices[i * 3 + 1] = center[1] + s0 * halfEdges[0][1] + s1 * halfEdges[1][1] + s2 * halfEdges[2][1];
vertices[i * 3 + 2] = center[2] + s0 * halfEdges[0][2] + s1 * halfEdges[1][2] + s2 * halfEdges[2][2];
bounds[0] = Math.Min(bounds[0], vertices[i * 3 + 0]);
bounds[1] = Math.Min(bounds[1], vertices[i * 3 + 1]);
bounds[2] = Math.Min(bounds[2], vertices[i * 3 + 2]);
bounds[3] = Math.Max(bounds[3], vertices[i * 3 + 0]);
bounds[4] = Math.Max(bounds[4], vertices[i * 3 + 1]);
bounds[5] = Math.Max(bounds[5], vertices[i * 3 + 2]);
}
float[][] planes = ArrayUtils.Of<float>(6, 4);
for (int i = 0; i < 6; i++)
{
float m = i < 3 ? -1 : 1;
int vi = i < 3 ? 0 : 7;
planes[i][0] = m * normals[i % 3][0];
planes[i][1] = m * normals[i % 3][1];
planes[i][2] = m * normals[i % 3][2];
planes[i][3] = vertices[vi * 3] * planes[i][0] + vertices[vi * 3 + 1] * planes[i][1]
+ vertices[vi * 3 + 2] * planes[i][2];
}
rasterizationFilledShape(hf, bounds, area, flagMergeThr, rectangle => intersectBox(rectangle, vertices, planes));
ctx.stopTimer("RASTERIZE_BOX");
}
public static void rasterizeConvex(Heightfield hf, float[] vertices, int[] triangles, int area, int flagMergeThr,
Telemetry ctx)
{
ctx.startTimer("RASTERIZE_CONVEX");
float[] bounds = new float[] { vertices[0], vertices[1], vertices[2], vertices[0], vertices[1], vertices[2] };
for (int i = 0; i < vertices.Length; i += 3)
{
bounds[0] = Math.Min(bounds[0], vertices[i + 0]);
bounds[1] = Math.Min(bounds[1], vertices[i + 1]);
bounds[2] = Math.Min(bounds[2], vertices[i + 2]);
bounds[3] = Math.Max(bounds[3], vertices[i + 0]);
bounds[4] = Math.Max(bounds[4], vertices[i + 1]);
bounds[5] = Math.Max(bounds[5], vertices[i + 2]);
}
float[][] planes = ArrayUtils.Of<float>(triangles.Length, 4);
float[][] triBounds = ArrayUtils.Of<float>(triangles.Length / 3, 4);
for (int i = 0, j = 0; i < triangles.Length; i += 3, j++)
{
int a = triangles[i] * 3;
int b = triangles[i + 1] * 3;
int c = triangles[i + 2] * 3;
float[] ab = { vertices[b] - vertices[a], vertices[b + 1] - vertices[a + 1], vertices[b + 2] - vertices[a + 2] };
float[] ac = { vertices[c] - vertices[a], vertices[c + 1] - vertices[a + 1], vertices[c + 2] - vertices[a + 2] };
float[] bc = { vertices[c] - vertices[b], vertices[c + 1] - vertices[b + 1], vertices[c + 2] - vertices[b + 2] };
float[] ca = { vertices[a] - vertices[c], vertices[a + 1] - vertices[c + 1], vertices[a + 2] - vertices[c + 2] };
plane(planes, i, ab, ac, vertices, a);
plane(planes, i + 1, planes[i], bc, vertices, b);
plane(planes, i + 2, planes[i], ca, vertices, c);
float s = 1.0f / (vertices[a] * planes[i + 1][0] + vertices[a + 1] * planes[i + 1][1]
+ vertices[a + 2] * planes[i + 1][2] - planes[i + 1][3]);
planes[i + 1][0] *= s;
planes[i + 1][1] *= s;
planes[i + 1][2] *= s;
planes[i + 1][3] *= s;
s = 1.0f / (vertices[b] * planes[i + 2][0] + vertices[b + 1] * planes[i + 2][1] + vertices[b + 2] * planes[i + 2][2]
- planes[i + 2][3]);
planes[i + 2][0] *= s;
planes[i + 2][1] *= s;
planes[i + 2][2] *= s;
planes[i + 2][3] *= s;
triBounds[j][0] = Math.Min(Math.Min(vertices[a], vertices[b]), vertices[c]);
triBounds[j][1] = Math.Min(Math.Min(vertices[a + 2], vertices[b + 2]), vertices[c + 2]);
triBounds[j][2] = Math.Max(Math.Max(vertices[a], vertices[b]), vertices[c]);
triBounds[j][3] = Math.Max(Math.Max(vertices[a + 2], vertices[b + 2]), vertices[c + 2]);
}
rasterizationFilledShape(hf, bounds, area, flagMergeThr,
rectangle => intersectConvex(rectangle, triangles, vertices, planes, triBounds));
ctx.stopTimer("RASTERIZE_CONVEX");
}
private static void plane(float[][] planes, int p, float[] v1, float[] v2, float[] vertices, int vert)
{
RecastVectors.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];
}
private static void rasterizationFilledShape(Heightfield hf, float[] bounds, int area, int flagMergeThr,
Func<float[], float[]> intersection)
{
if (!overlapBounds(hf.bmin, hf.bmax, bounds))
{
return;
}
bounds[3] = Math.Min(bounds[3], hf.bmax[0]);
bounds[5] = Math.Min(bounds[5], hf.bmax[2]);
bounds[0] = Math.Max(bounds[0], hf.bmin[0]);
bounds[2] = Math.Max(bounds[2], hf.bmin[2]);
if (bounds[3] <= bounds[0] || bounds[4] <= bounds[1] || bounds[5] <= bounds[2])
{
return;
}
float ics = 1.0f / hf.cs;
float ich = 1.0f / hf.ch;
int xMin = (int)((bounds[0] - hf.bmin[0]) * ics);
int zMin = (int)((bounds[2] - hf.bmin[2]) * ics);
int xMax = Math.Min(hf.width - 1, (int)((bounds[3] - hf.bmin[0]) * ics));
int zMax = Math.Min(hf.height - 1, (int)((bounds[5] - hf.bmin[2]) * ics));
float[] rectangle = new float[5];
rectangle[4] = hf.bmin[1];
for (int x = xMin; x <= xMax; x++)
{
for (int z = zMin; z <= zMax; z++)
{
rectangle[0] = x * hf.cs + hf.bmin[0];
rectangle[1] = z * hf.cs + hf.bmin[2];
rectangle[2] = rectangle[0] + hf.cs;
rectangle[3] = rectangle[1] + hf.cs;
float[] h = intersection.Invoke(rectangle);
if (h != null)
{
int smin = (int)Math.Floor((h[0] - hf.bmin[1]) * ich);
int smax = (int)Math.Ceiling((h[1] - hf.bmin[1]) * ich);
if (smin != smax)
{
int ismin = RecastCommon.clamp(smin, 0, SPAN_MAX_HEIGHT);
int ismax = RecastCommon.clamp(smax, ismin + 1, SPAN_MAX_HEIGHT);
RecastRasterization.addSpan(hf, x, z, ismin, ismax, area, flagMergeThr);
}
}
}
}
}
private static float[] intersectSphere(float[] rectangle, float[] center, float radiusSqr)
{
float x = Math.Max(rectangle[0], Math.Min(center[0], rectangle[2]));
float y = rectangle[4];
float z = Math.Max(rectangle[1], Math.Min(center[2], rectangle[3]));
float mx = x - center[0];
float my = y - center[1];
float mz = z - center[2];
float b = my; // dot(m, d) d = (0, 1, 0)
float c = lenSqr(mx, my, mz) - radiusSqr;
if (c > 0.0f && b > 0.0f)
{
return null;
}
float discr = b * b - c;
if (discr < 0.0f)
{
return null;
}
float discrSqrt = (float)Math.Sqrt(discr);
float tmin = -b - discrSqrt;
float tmax = -b + discrSqrt;
if (tmin < 0.0f)
{
tmin = 0.0f;
}
return new float[] { y + tmin, y + tmax };
}
private static float[] intersectCapsule(float[] rectangle, float[] start, float[] end, float[] axis, float radiusSqr)
{
float[] s = mergeIntersections(intersectSphere(rectangle, start, radiusSqr), intersectSphere(rectangle, end, radiusSqr));
float axisLen2dSqr = axis[0] * axis[0] + axis[2] * axis[2];
if (axisLen2dSqr > EPSILON)
{
s = slabsCylinderIntersection(rectangle, start, end, axis, radiusSqr, s);
}
return s;
}
private static float[] intersectCylinder(float[] rectangle, float[] start, float[] end, float[] axis, float radiusSqr)
{
float[] s = mergeIntersections(
rayCylinderIntersection(new float[]
{
clamp(start[0], rectangle[0], rectangle[2]), rectangle[4],
clamp(start[2], rectangle[1], rectangle[3])
}, start, axis, radiusSqr),
rayCylinderIntersection(new float[]
{
clamp(end[0], rectangle[0], rectangle[2]), rectangle[4],
clamp(end[2], rectangle[1], rectangle[3])
}, start, axis, radiusSqr));
float axisLen2dSqr = axis[0] * axis[0] + axis[2] * axis[2];
if (axisLen2dSqr > EPSILON)
{
s = slabsCylinderIntersection(rectangle, start, end, axis, radiusSqr, s);
}
if (axis[1] * axis[1] > EPSILON)
{
float[][] rectangleOnStartPlane = ArrayUtils.Of<float>(4, 3);
float[][] rectangleOnEndPlane = ArrayUtils.Of<float>(4, 3);
float ds = dot(axis, start);
float de = dot(axis, end);
for (int i = 0; i < 4; i++)
{
float x = rectangle[(i + 1) & 2];
float z = rectangle[(i & 2) + 1];
float[] a = { x, rectangle[4], z };
float dotAxisA = dot(axis, a);
float t = (ds - dotAxisA) / axis[1];
rectangleOnStartPlane[i][0] = x;
rectangleOnStartPlane[i][1] = rectangle[4] + t;
rectangleOnStartPlane[i][2] = z;
t = (de - dotAxisA) / axis[1];
rectangleOnEndPlane[i][0] = x;
rectangleOnEndPlane[i][1] = rectangle[4] + t;
rectangleOnEndPlane[i][2] = z;
}
for (int i = 0; i < 4; i++)
{
s = cylinderCapIntersection(start, radiusSqr, s, i, rectangleOnStartPlane);
s = cylinderCapIntersection(end, radiusSqr, s, i, rectangleOnEndPlane);
}
}
return s;
}
private static float[] cylinderCapIntersection(float[] start, float radiusSqr, float[] s, int i, float[][] rectangleOnPlane)
{
int j = (i + 1) % 4;
// Ray against sphere intersection
float[] m = { rectangleOnPlane[i][0] - start[0], rectangleOnPlane[i][1] - start[1], rectangleOnPlane[i][2] - start[2] };
float[] d =
{
rectangleOnPlane[j][0] - rectangleOnPlane[i][0], rectangleOnPlane[j][1] - rectangleOnPlane[i][1],
rectangleOnPlane[j][2] - rectangleOnPlane[i][2]
};
float dl = dot(d, d);
float b = dot(m, d) / dl;
float c = (dot(m, m) - radiusSqr) / dl;
float discr = b * b - c;
if (discr > EPSILON)
{
float discrSqrt = (float)Math.Sqrt(discr);
float t1 = -b - discrSqrt;
float t2 = -b + discrSqrt;
if (t1 <= 1 && t2 >= 0)
{
t1 = Math.Max(0, t1);
t2 = Math.Min(1, t2);
float y1 = rectangleOnPlane[i][1] + t1 * d[1];
float y2 = rectangleOnPlane[i][1] + t2 * d[1];
float[] y = { Math.Min(y1, y2), Math.Max(y1, y2) };
s = mergeIntersections(s, y);
}
}
return s;
}
private static float[] slabsCylinderIntersection(float[] rectangle, float[] start, float[] end, float[] axis, float radiusSqr,
float[] s)
{
if (Math.Min(start[0], end[0]) < rectangle[0])
{
s = mergeIntersections(s, xSlabCylinderIntersection(rectangle, start, axis, radiusSqr, rectangle[0]));
}
if (Math.Max(start[0], end[0]) > rectangle[2])
{
s = mergeIntersections(s, xSlabCylinderIntersection(rectangle, start, axis, radiusSqr, rectangle[2]));
}
if (Math.Min(start[2], end[2]) < rectangle[1])
{
s = mergeIntersections(s, zSlabCylinderIntersection(rectangle, start, axis, radiusSqr, rectangle[1]));
}
if (Math.Max(start[2], end[2]) > rectangle[3])
{
s = mergeIntersections(s, zSlabCylinderIntersection(rectangle, start, axis, radiusSqr, rectangle[3]));
}
return s;
}
private static float[] xSlabCylinderIntersection(float[] rectangle, float[] start, float[] axis, float radiusSqr, float x)
{
return rayCylinderIntersection(xSlabRayIntersection(rectangle, start, axis, x), start, axis, radiusSqr);
}
private static float[] xSlabRayIntersection(float[] rectangle, float[] start, float[] direction, float x)
{
// 2d intersection of plane and segment
float t = (x - start[0]) / direction[0];
float z = clamp(start[2] + t * direction[2], rectangle[1], rectangle[3]);
return new float[] { x, rectangle[4], z };
}
private static float[] zSlabCylinderIntersection(float[] rectangle, float[] start, float[] axis, float radiusSqr, float z)
{
return rayCylinderIntersection(zSlabRayIntersection(rectangle, start, axis, z), start, axis, radiusSqr);
}
private static float[] zSlabRayIntersection(float[] rectangle, float[] start, float[] direction, float z)
{
// 2d intersection of plane and segment
float t = (z - start[2]) / direction[2];
float x = clamp(start[0] + t * direction[0], rectangle[0], rectangle[2]);
return new float[] { x, rectangle[4], z };
}
// Based on Christer Ericsons's "Real-Time Collision Detection"
private static float[] rayCylinderIntersection(float[] point, float[] start, float[] axis, float radiusSqr)
{
float[] d = axis;
float[] m = { point[0] - start[0], point[1] - start[1], point[2] - start[2] };
// float[] n = { 0, 1, 0 };
float md = dot(m, d);
// float nd = dot(n, d);
float nd = axis[1];
float dd = dot(d, d);
// float nn = dot(n, n);
float nn = 1;
// float mn = dot(m, n);
float mn = m[1];
// float a = dd * nn - nd * nd;
float a = dd - nd * nd;
float k = dot(m, m) - radiusSqr;
float c = dd * k - md * md;
if (Math.Abs(a) < EPSILON)
{
// Segment runs parallel to cylinder axis
if (c > 0.0f)
{
return null; // a and thus the segment lie outside cylinder
}
// Now known that segment intersects cylinder; figure out how it intersects
float tt1 = -mn / nn; // Intersect segment against p endcap
float tt2 = (nd - mn) / nn; // Intersect segment against q endcap
return new float[] { point[1] + Math.Min(tt1, tt2), point[1] + Math.Max(tt1, tt2) };
}
float b = dd * mn - nd * md;
float discr = b * b - a * c;
if (discr < 0.0f)
{
return null; // No real roots; no intersection
}
float discSqrt = (float)Math.Sqrt(discr);
float t1 = (-b - discSqrt) / a;
float t2 = (-b + discSqrt) / a;
if (md + t1 * nd < 0.0f)
{
// Intersection outside cylinder on p side
t1 = -md / nd;
if (k + t1 * (2 * mn + t1 * nn) > 0.0f)
{
return null;
}
}
else if (md + t1 * nd > dd)
{
// Intersection outside cylinder on q side
t1 = (dd - md) / nd;
if (k + dd - 2 * md + t1 * (2 * (mn - nd) + t1 * nn) > 0.0f)
{
return null;
}
}
if (md + t2 * nd < 0.0f)
{
// Intersection outside cylinder on p side
t2 = -md / nd;
if (k + t2 * (2 * mn + t2 * nn) > 0.0f)
{
return null;
}
}
else if (md + t2 * nd > dd)
{
// Intersection outside cylinder on q side
t2 = (dd - md) / nd;
if (k + dd - 2 * md + t2 * (2 * (mn - nd) + t2 * nn) > 0.0f)
{
return null;
}
}
return new float[] { point[1] + Math.Min(t1, t2), point[1] + Math.Max(t1, t2) };
}
private static float[] intersectBox(float[] rectangle, float[] vertices, float[][] planes)
{
float yMin = float.PositiveInfinity;
float yMax = float.NegativeInfinity;
// check intersection with rays starting in box vertices first
for (int i = 0; i < 8; i++)
{
int vi = i * 3;
if (vertices[vi] >= rectangle[0] && vertices[vi] < rectangle[2] && vertices[vi + 2] >= rectangle[1]
&& vertices[vi + 2] < rectangle[3])
{
yMin = Math.Min(yMin, vertices[vi + 1]);
yMax = Math.Max(yMax, vertices[vi + 1]);
}
}
// check intersection with rays starting in rectangle vertices
float[] point = new float[] { 0, rectangle[1], 0 };
for (int i = 0; i < 4; i++)
{
point[0] = ((i & 1) == 0) ? rectangle[0] : rectangle[2];
point[2] = ((i & 2) == 0) ? rectangle[1] : rectangle[3];
for (int j = 0; j < 6; j++)
{
if (Math.Abs(planes[j][1]) > EPSILON)
{
float dotNormalPoint = dot(planes[j], point);
float t = (planes[j][3] - dotNormalPoint) / planes[j][1];
float y = point[1] + t;
bool valid = true;
for (int k = 0; k < 6; k++)
{
if (k != j)
{
if (point[0] * planes[k][0] + y * planes[k][1] + point[2] * planes[k][2] > planes[k][3])
{
valid = false;
break;
}
}
}
if (valid)
{
yMin = Math.Min(yMin, y);
yMax = Math.Max(yMax, y);
}
}
}
}
// check intersection with box edges
for (int i = 0; i < BOX_EDGES.Length; i += 2)
{
int vi = BOX_EDGES[i] * 3;
int vj = BOX_EDGES[i + 1] * 3;
float x = vertices[vi];
float z = vertices[vi + 2];
// edge slab intersection
float y = vertices[vi + 1];
float dx = vertices[vj] - x;
float dy = vertices[vj + 1] - y;
float dz = vertices[vj + 2] - z;
if (Math.Abs(dx) > EPSILON)
{
float? iy = xSlabSegmentIntersection(rectangle, x, y, z, dx, dy, dz, rectangle[0]);
if (iy != null)
{
yMin = Math.Min(yMin, iy.Value);
yMax = Math.Max(yMax, iy.Value);
}
iy = xSlabSegmentIntersection(rectangle, x, y, z, dx, dy, dz, rectangle[2]);
if (iy != null)
{
yMin = Math.Min(yMin, iy.Value);
yMax = Math.Max(yMax, iy.Value);
}
}
if (Math.Abs(dz) > EPSILON)
{
float? iy = zSlabSegmentIntersection(rectangle, x, y, z, dx, dy, dz, rectangle[1]);
if (iy != null)
{
yMin = Math.Min(yMin, iy.Value);
yMax = Math.Max(yMax, iy.Value);
}
iy = zSlabSegmentIntersection(rectangle, x, y, z, dx, dy, dz, rectangle[3]);
if (iy != null)
{
yMin = Math.Min(yMin, iy.Value);
yMax = Math.Max(yMax, iy.Value);
}
}
}
if (yMin <= yMax)
{
return new float[] { yMin, yMax };
}
return null;
}
private static float[] intersectConvex(float[] rectangle, int[] triangles, float[] verts, float[][] planes,
float[][] triBounds)
{
float imin = float.PositiveInfinity;
float imax = float.NegativeInfinity;
for (int tr = 0, tri = 0; tri < triangles.Length; tr++, tri += 3)
{
if (triBounds[tr][0] > rectangle[2] || triBounds[tr][2] < rectangle[0] || triBounds[tr][1] > rectangle[3]
|| triBounds[tr][3] < rectangle[1])
{
continue;
}
if (Math.Abs(planes[tri][1]) < EPSILON)
{
continue;
}
for (int i = 0; i < 3; i++)
{
int vi = triangles[tri + i] * 3;
int vj = triangles[tri + (i + 1) % 3] * 3;
float x = verts[vi];
float z = verts[vi + 2];
// triangle vertex
if (x >= rectangle[0] && x <= rectangle[2] && z >= rectangle[1] && z <= rectangle[3])
{
imin = Math.Min(imin, verts[vi + 1]);
imax = Math.Max(imax, verts[vi + 1]);
}
// triangle slab intersection
float y = verts[vi + 1];
float dx = verts[vj] - x;
float dy = verts[vj + 1] - y;
float dz = verts[vj + 2] - z;
if (Math.Abs(dx) > EPSILON)
{
float? iy = xSlabSegmentIntersection(rectangle, x, y, z, dx, dy, dz, rectangle[0]);
if (iy != null)
{
imin = Math.Min(imin, iy.Value);
imax = Math.Max(imax, iy.Value);
}
iy = xSlabSegmentIntersection(rectangle, x, y, z, dx, dy, dz, rectangle[2]);
if (iy != null)
{
imin = Math.Min(imin, iy.Value);
imax = Math.Max(imax, iy.Value);
}
}
if (Math.Abs(dz) > EPSILON)
{
float? iy = zSlabSegmentIntersection(rectangle, x, y, z, dx, dy, dz, rectangle[1]);
if (iy != null)
{
imin = Math.Min(imin, iy.Value);
imax = Math.Max(imax, iy.Value);
}
iy = zSlabSegmentIntersection(rectangle, x, y, z, dx, dy, dz, rectangle[3]);
if (iy != null)
{
imin = Math.Min(imin, iy.Value);
imax = Math.Max(imax, iy.Value);
}
}
}
// rectangle vertex
float[] point = new float[] { 0, rectangle[1], 0 };
for (int i = 0; i < 4; i++)
{
point[0] = ((i & 1) == 0) ? rectangle[0] : rectangle[2];
point[2] = ((i & 2) == 0) ? rectangle[1] : rectangle[3];
float? y = rayTriangleIntersection(point, tri, planes);
if (y != null)
{
imin = Math.Min(imin, y.Value);
imax = Math.Max(imax, y.Value);
}
}
}
if (imin < imax)
{
return new float[] { imin, imax };
}
return null;
}
private static float? xSlabSegmentIntersection(float[] rectangle, float x, float y, float z, float dx, float dy, float dz,
float slabX)
{
float x2 = x + dx;
if ((x < slabX && x2 > slabX) || (x > slabX && x2 < slabX))
{
float t = (slabX - x) / dx;
float iz = z + dz * t;
if (iz >= rectangle[1] && iz <= rectangle[3])
{
return y + dy * t;
}
}
return null;
}
private static float? zSlabSegmentIntersection(float[] rectangle, float x, float y, float z, float dx, float dy, float dz,
float slabZ)
{
float z2 = z + dz;
if ((z < slabZ && z2 > slabZ) || (z > slabZ && z2 < slabZ))
{
float t = (slabZ - z) / dz;
float ix = x + dx * t;
if (ix >= rectangle[0] && ix <= rectangle[2])
{
return y + dy * t;
}
}
return null;
}
private static float? rayTriangleIntersection(float[] point, int plane, float[][] planes)
{
float t = (planes[plane][3] - dot(planes[plane], point)) / planes[plane][1];
float[] s = { point[0], point[1] + t, point[2] };
float u = dot(s, planes[plane + 1]) - planes[plane + 1][3];
if (u < 0.0f || u > 1.0f)
{
return null;
}
float v = dot(s, planes[plane + 2]) - planes[plane + 2][3];
if (v < 0.0f)
{
return null;
}
float w = 1f - u - v;
if (w < 0.0f)
{
return null;
}
return s[1];
}
private static float[] mergeIntersections(float[] s1, float[] s2)
{
if (s1 == null)
{
return s2;
}
if (s2 == null)
{
return s1;
}
return new float[] { Math.Min(s1[0], s2[0]), Math.Max(s1[1], s2[1]) };
}
private static float lenSqr(float dx, float dy, float dz)
{
return dx * dx + dy * dy + dz * dz;
}
public static float clamp(float v, float min, float max)
{
return Math.Max(Math.Min(max, v), min);
}
private static bool overlapBounds(float[] amin, float[] amax, float[] bounds)
{
bool overlap = true;
overlap = (amin[0] > bounds[3] || amax[0] < bounds[0]) ? false : overlap;
overlap = (amin[1] > bounds[4]) ? false : overlap;
overlap = (amin[2] > bounds[5] || amax[2] < bounds[2]) ? false : overlap;
return overlap;
}
}

View File

@ -0,0 +1,204 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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;
namespace DotRecast.Recast;
using static RecastConstants;
public class RecastFilter
{
/// @par
///
/// Allows the formation of walkable regions that will flow over low lying
/// objects such as curbs, and up structures such as stairways.
///
/// Two neighboring spans are walkable if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb</tt>
///
/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call
/// #rcFilterLedgeSpans after calling this filter.
///
/// @see rcHeightfield, rcConfig
public static void filterLowHangingWalkableObstacles(Telemetry ctx, int walkableClimb, Heightfield solid)
{
ctx.startTimer("FILTER_LOW_OBSTACLES");
int w = solid.width;
int h = solid.height;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
Span ps = null;
bool previousWalkable = false;
int previousArea = RC_NULL_AREA;
for (Span s = solid.spans[x + y * w]; s != null; ps = s, s = s.next)
{
bool walkable = s.area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable
// span just below it, mark the span above it walkable too.
if (!walkable && previousWalkable)
{
if (Math.Abs(s.smax - ps.smax) <= walkableClimb)
s.area = previousArea;
}
// Copy walkable flag so that it cannot propagate
// past multiple non-walkable objects.
previousWalkable = walkable;
previousArea = s.area;
}
}
}
ctx.stopTimer("FILTER_LOW_OBSTACLES");
}
/// @par
///
/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb
/// from the current span's maximum.
/// This method removes the impact of the overestimation of conservative voxelization
/// so the resulting mesh will not have regions hanging in the air over ledges.
///
/// A span is a ledge if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt>
///
/// @see rcHeightfield, rcConfig
public static void filterLedgeSpans(Telemetry ctx, int walkableHeight, int walkableClimb, Heightfield solid)
{
ctx.startTimer("FILTER_LEDGE");
int w = solid.width;
int h = solid.height;
// Mark border spans.
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
for (Span s = solid.spans[x + y * w]; s != null; s = s.next)
{
// Skip non walkable spans.
if (s.area == RC_NULL_AREA)
continue;
int bot = (s.smax);
int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
// Find neighbours minimum height.
int minh = SPAN_MAX_HEIGHT;
// Min and max height of accessible neighbours.
int asmin = s.smax;
int asmax = s.smax;
for (int dir = 0; dir < 4; ++dir)
{
int dx = x + RecastCommon.GetDirOffsetX(dir);
int dy = y + RecastCommon.GetDirOffsetY(dir);
// Skip neighbours which are out of bounds.
if (dx < 0 || dy < 0 || dx >= w || dy >= h)
{
minh = Math.Min(minh, -walkableClimb - bot);
continue;
}
// From minus infinity to the first span.
Span 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);
// Rest of the spans.
for (ns = solid.spans[dx + dy * w]; ns != null; ns = ns.next)
{
nbot = ns.smax;
ntop = ns.next != null ? ns.next.smin : SPAN_MAX_HEIGHT;
// Skip neightbour if the gap between the spans is too small.
if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
{
minh = Math.Min(minh, nbot - bot);
// Find min/max accessible neighbour height.
if (Math.Abs(nbot - bot) <= walkableClimb)
{
if (nbot < asmin)
asmin = nbot;
if (nbot > asmax)
asmax = nbot;
}
}
}
}
// The current span is close to a ledge if the drop to any
// neighbour span is less than the walkableClimb.
if (minh < -walkableClimb)
s.area = RC_NULL_AREA;
// If the difference between all neighbours is too large,
// we are at steep slope, mark the span as ledge.
if ((asmax - asmin) > walkableClimb)
{
s.area = RC_NULL_AREA;
}
}
}
}
ctx.stopTimer("FILTER_LEDGE");
}
/// @par
///
/// For this filter, the clearance above the span is the distance from the span's
/// maximum to the next higher span's minimum. (Same grid column.)
///
/// @see rcHeightfield, rcConfig
public static void filterWalkableLowHeightSpans(Telemetry ctx, int walkableHeight, Heightfield solid)
{
ctx.startTimer("FILTER_WALKABLE");
int w = solid.width;
int h = solid.height;
// Remove walkable flag from spans which do not have enough
// space above them for the agent to stand there.
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
for (Span s = solid.spans[x + y * w]; s != null; s = s.next)
{
int bot = (s.smax);
int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
if ((top - bot) <= walkableHeight)
s.area = RC_NULL_AREA;
}
}
}
ctx.stopTimer("FILTER_WALKABLE");
}
}

View File

@ -0,0 +1,506 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
namespace DotRecast.Recast;
using static RecastCommon;
using static RecastConstants;
using static RecastVectors;
using static RecastRegion;
public class RecastLayers {
const int RC_MAX_LAYERS = RecastConstants.RC_NOT_CONNECTED;
const int RC_MAX_NEIS = 16;
private class LayerRegion {
public int id;
public int layerId;
public bool @base;
public int ymin, ymax;
public List<int> layers;
public List<int> neis;
public LayerRegion(int i) {
id = i;
ymin = 0xFFFF;
layerId = 0xff;
layers = new();
neis = new();
}
};
private static void addUnique(List<int> a, int v) {
if (!a.Contains(v)) {
a.Add(v);
}
}
private static bool contains(List<int> a, int v) {
return a.Contains(v);
}
private static bool overlapRange(int amin, int amax, int bmin, int bmax) {
return (amin > bmax || amax < bmin) ? false : true;
}
public static HeightfieldLayerSet buildHeightfieldLayers(Telemetry ctx, CompactHeightfield chf, int walkableHeight) {
ctx.startTimer("RC_TIMER_BUILD_LAYERS");
int w = chf.width;
int h = chf.height;
int borderSize = chf.borderSize;
int[] srcReg = new int[chf.spanCount];
Array.Fill(srcReg, 0xFF);
int nsweeps = chf.width;// Math.Max(chf.width, chf.height);
SweepSpan[] sweeps = new SweepSpan[nsweeps];
for (int i = 0; i < sweeps.Length; i++) {
sweeps[i] = new SweepSpan();
}
// Partition walkable area into monotone regions.
int[] prevCount = new int[256];
int regId = 0;
// Sweep one line at a time.
for (int y = borderSize; y < h - borderSize; ++y) {
// Collect spans from this row.
Array.Fill(prevCount, 0, 0, (regId) - (0));
int sweepId = 0;
for (int x = borderSize; x < w - borderSize; ++x) {
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
CompactSpan s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
int sid = 0xFF;
// -x
if (GetCon(s, 0) != RC_NOT_CONNECTED) {
int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
sid = srcReg[ai];
}
if (sid == 0xff) {
sid = sweepId++;
sweeps[sid].nei = 0xff;
sweeps[sid].ns = 0;
}
// -y
if (GetCon(s, 3) != RC_NOT_CONNECTED) {
int ax = x + GetDirOffsetX(3);
int ay = y + GetDirOffsetY(3);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 3);
int nr = srcReg[ai];
if (nr != 0xff) {
// Set neighbour when first valid neighbour is
// encoutered.
if (sweeps[sid].ns == 0)
sweeps[sid].nei = nr;
if (sweeps[sid].nei == nr) {
// Update existing neighbour
sweeps[sid].ns++;
prevCount[nr]++;
} else {
// This is hit if there is nore than one
// neighbour.
// Invalidate the neighbour.
sweeps[sid].nei = 0xff;
}
}
}
srcReg[i] = sid;
}
}
// Create unique ID.
for (int i = 0; i < sweepId; ++i) {
// If the neighbour is set and there is only one continuous
// connection to it,
// the sweep will be merged with the previous one, else new
// region is created.
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns) {
sweeps[i].id = sweeps[i].nei;
} else {
if (regId == 255) {
throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
}
sweeps[i].id = regId++;
}
}
// Remap local sweep ids to region ids.
for (int x = borderSize; x < w - borderSize; ++x) {
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
if (srcReg[i] != 0xff)
srcReg[i] = sweeps[srcReg[i]].id;
}
}
}
int nregs = regId;
LayerRegion[] regs = new LayerRegion[nregs];
// Construct regions
for (int i = 0; i < nregs; ++i) {
regs[i] = new LayerRegion(i);
}
// Find region neighbours and overlapping regions.
List<int> lregs = new();
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
CompactCell c = chf.cells[x + y * w];
lregs.Clear();
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
CompactSpan s = chf.spans[i];
int ri = srcReg[i];
if (ri == 0xff)
continue;
regs[ri].ymin = Math.Min(regs[ri].ymin, s.y);
regs[ri].ymax = Math.Max(regs[ri].ymax, s.y);
// Collect all region layers.
lregs.Add(ri);
// Update neighbours
for (int dir = 0; dir < 4; ++dir) {
if (GetCon(s, dir) != RC_NOT_CONNECTED) {
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int rai = srcReg[ai];
if (rai != 0xff && rai != ri)
addUnique(regs[ri].neis, rai);
}
}
}
// Update overlapping regions.
for (int i = 0; i < lregs.Count - 1; ++i) {
for (int j = i + 1; j < lregs.Count; ++j) {
if (lregs[i] != lregs[j]) {
LayerRegion ri = regs[lregs[i]];
LayerRegion rj = regs[lregs[j]];
addUnique(ri.layers, lregs[j]);
addUnique(rj.layers, lregs[i]);
}
}
}
}
}
// Create 2D layers from regions.
int layerId = 0;
List<int> stack = new();
for (int i = 0; i < nregs; ++i) {
LayerRegion root = regs[i];
// Skip already visited.
if (root.layerId != 0xff)
continue;
// Start search.
root.layerId = layerId;
root.@base = true;
stack.Add(i);
while (stack.Count != 0) {
// Pop front
int pop = stack[0]; // TODO : 여기에 stack 처럼 작동하게 했는데, 스택인지는 모르겠음
stack.RemoveAt(0);
LayerRegion reg = regs[pop];
foreach (int nei in reg.neis) {
LayerRegion regn = regs[nei];
// Skip already visited.
if (regn.layerId != 0xff)
continue;
// Skip if the neighbour is overlapping root region.
if (contains(root.layers, nei))
continue;
// Skip if the height range would become too large.
int ymin = Math.Min(root.ymin, regn.ymin);
int ymax = Math.Max(root.ymax, regn.ymax);
if ((ymax - ymin) >= 255)
continue;
// Deepen
stack.Add(nei);
// Mark layer id
regn.layerId = layerId;
// Merge current layers to root.
foreach (int layer in regn.layers)
addUnique(root.layers, layer);
root.ymin = Math.Min(root.ymin, regn.ymin);
root.ymax = Math.Max(root.ymax, regn.ymax);
}
}
layerId++;
}
// Merge non-overlapping regions that are close in height.
int mergeHeight = walkableHeight * 4;
for (int i = 0; i < nregs; ++i) {
LayerRegion ri = regs[i];
if (!ri.@base)
continue;
int newId = ri.layerId;
for (;;) {
int oldId = 0xff;
for (int j = 0; j < nregs; ++j) {
if (i == j)
continue;
LayerRegion rj = regs[j];
if (!rj.@base)
continue;
// Skip if the regions are not close to each other.
if (!overlapRange(ri.ymin, ri.ymax + mergeHeight, rj.ymin, rj.ymax + mergeHeight))
continue;
// Skip if the height range would become too large.
int ymin = Math.Min(ri.ymin, rj.ymin);
int ymax = Math.Max(ri.ymax, rj.ymax);
if ((ymax - ymin) >= 255)
continue;
// Make sure that there is no overlap when merging 'ri' and
// 'rj'.
bool overlap = false;
// Iterate over all regions which have the same layerId as
// 'rj'
for (int k = 0; k < nregs; ++k) {
if (regs[k].layerId != rj.layerId)
continue;
// Check if region 'k' is overlapping region 'ri'
// Index to 'regs' is the same as region id.
if (contains(ri.layers, k)) {
overlap = true;
break;
}
}
// Cannot merge of regions overlap.
if (overlap)
continue;
// Can merge i and j.
oldId = rj.layerId;
break;
}
// Could not find anything to merge with, stop.
if (oldId == 0xff)
break;
// Merge
for (int j = 0; j < nregs; ++j) {
LayerRegion rj = regs[j];
if (rj.layerId == oldId) {
rj.@base = false;
// Remap layerIds.
rj.layerId = newId;
// Add overlaid layers from 'rj' to 'ri'.
foreach (int layer in rj.layers)
addUnique(ri.layers, layer);
// Update height bounds.
ri.ymin = Math.Min(ri.ymin, rj.ymin);
ri.ymax = Math.Max(ri.ymax, rj.ymax);
}
}
}
}
// Compact layerIds
int[] remap = new int[256];
// Find number of unique layers.
layerId = 0;
for (int i = 0; i < nregs; ++i)
remap[regs[i].layerId] = 1;
for (int i = 0; i < 256; ++i) {
if (remap[i] != 0)
remap[i] = layerId++;
else
remap[i] = 0xff;
}
// Remap ids.
for (int i = 0; i < nregs; ++i)
regs[i].layerId = remap[regs[i].layerId];
// No layers, return empty.
if (layerId == 0) {
// ctx.stopTimer(RC_TIMER_BUILD_LAYERS);
return null;
}
// Create layers.
// rcAssert(lset.layers == 0);
int lw = w - borderSize * 2;
int lh = h - borderSize * 2;
// Build contracted bbox for layers.
float[] bmin = new float[3];
float[] bmax = new float[3];
copy(bmin, chf.bmin);
copy(bmax, chf.bmax);
bmin[0] += borderSize * chf.cs;
bmin[2] += borderSize * chf.cs;
bmax[0] -= borderSize * chf.cs;
bmax[2] -= borderSize * chf.cs;
HeightfieldLayerSet lset = new HeightfieldLayerSet();
lset.layers = new HeightfieldLayerSet.HeightfieldLayer[layerId];
for (int i = 0; i < lset.layers.Length; i++) {
lset.layers[i] = new HeightfieldLayerSet.HeightfieldLayer();
}
// Store layers.
for (int i = 0; i < lset.layers.Length; ++i) {
int curId = i;
HeightfieldLayerSet.HeightfieldLayer layer = lset.layers[i];
int gridSize = lw * lh;
layer.heights = new int[gridSize];
Array.Fill(layer.heights, 0xFF);
layer.areas = new int[gridSize];
layer.cons = new int[gridSize];
// Find layer height bounds.
int hmin = 0, hmax = 0;
for (int j = 0; j < nregs; ++j) {
if (regs[j].@base && regs[j].layerId == curId) {
hmin = regs[j].ymin;
hmax = regs[j].ymax;
}
}
layer.width = lw;
layer.height = lh;
layer.cs = chf.cs;
layer.ch = chf.ch;
// Adjust the bbox to fit the heightfield.
copy(layer.bmin, bmin);
copy(layer.bmax, bmax);
layer.bmin[1] = bmin[1] + hmin * chf.ch;
layer.bmax[1] = bmin[1] + hmax * chf.ch;
layer.hmin = hmin;
layer.hmax = hmax;
// Update usable data region.
layer.minx = layer.width;
layer.maxx = 0;
layer.miny = layer.height;
layer.maxy = 0;
// Copy height and area from compact heightfield.
for (int y = 0; y < lh; ++y) {
for (int x = 0; x < lw; ++x) {
int cx = borderSize + x;
int cy = borderSize + y;
CompactCell c = chf.cells[cx + cy * w];
for (int j = c.index, nj = c.index + c.count; j < nj; ++j) {
CompactSpan s = chf.spans[j];
// Skip unassigned regions.
if (srcReg[j] == 0xff)
continue;
// Skip of does nto belong to current layer.
int lid = regs[srcReg[j]].layerId;
if (lid != curId)
continue;
// Update data bounds.
layer.minx = Math.Min(layer.minx, x);
layer.maxx = Math.Max(layer.maxx, x);
layer.miny = Math.Min(layer.miny, y);
layer.maxy = Math.Max(layer.maxy, y);
// Store height and area type.
int idx = x + y * lw;
layer.heights[idx] = (char) (s.y - hmin);
layer.areas[idx] = chf.areas[j];
// Check connection.
char portal = (char)0;
char con = (char)0;
for (int dir = 0; dir < 4; ++dir) {
if (GetCon(s, dir) != RC_NOT_CONNECTED) {
int ax = cx + GetDirOffsetX(dir);
int ay = cy + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
// Portal mask
if (chf.areas[ai] != RC_NULL_AREA && lid != alid) {
portal |= (char)(1 << dir);
// Update height so that it matches on both
// sides of the portal.
CompactSpan @as = chf.spans[ai];
if (@as.y > hmin)
layer.heights[idx] = Math.Max(layer.heights[idx], (char) (@as.y - hmin));
}
// Valid connection mask
if (chf.areas[ai] != RC_NULL_AREA && lid == alid) {
int nx = ax - borderSize;
int ny = ay - borderSize;
if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
con |= (char)(1 << dir);
}
}
}
layer.cons[idx] = (portal << 4) | con;
}
}
}
if (layer.minx > layer.maxx)
layer.minx = layer.maxx = 0;
if (layer.miny > layer.maxy)
layer.miny = layer.maxy = 0;
}
// ctx->stopTimer(RC_TIMER_BUILD_LAYERS);
return lset;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,483 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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;
namespace DotRecast.Recast;
using static RecastConstants;
public class RecastRasterization
{
/**
* Check whether two bounding boxes overlap
*
* @param amin
* Min axis extents of bounding box A
* @param amax
* Max axis extents of bounding box A
* @param bmin
* Min axis extents of bounding box B
* @param bmax
* Max axis extents of bounding box B
* @returns true if the two bounding boxes overlap. False otherwise
*/
private static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax)
{
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap;
}
/**
* Adds a span to the heightfield. If the new span overlaps existing spans, it will merge the new span with the
* existing ones. The span addition can be set to favor flags. If the span is merged to another span and the new
* spanMax is within flagMergeThreshold units from the existing span, the span flags are merged.
*
* @param heightfield
* An initialized heightfield.
* @param x
* The width index where the span is to be added. [Limits: 0 <= value < Heightfield::width]
* @param y
* The height index where the span is to be added. [Limits: 0 <= value < Heightfield::height]
* @param spanMin
* The minimum height of the span. [Limit: < spanMax] [Units: vx]
* @param spanMax
* The minimum height of the span. [Limit: <= RecastConstants.SPAN_MAX_HEIGHT] [Units: vx]
* @param areaId
* The area id of the span. [Limit: <= WALKABLE_AREA)
* @param flagMergeThreshold
* The merge theshold. [Limit: >= 0] [Units: vx]
* @see Heightfield, Span.
*/
public static void addSpan(Heightfield heightfield, int x, int y, int spanMin, int spanMax, int areaId,
int flagMergeThreshold)
{
int idx = x + y * heightfield.width;
Span s = new Span();
s.smin = spanMin;
s.smax = spanMax;
s.area = areaId;
s.next = null;
// Empty cell, add the first span.
if (heightfield.spans[idx] == null)
{
heightfield.spans[idx] = s;
return;
}
Span prev = null;
Span cur = heightfield.spans[idx];
// Insert and merge spans.
while (cur != null)
{
if (cur.smin > s.smax)
{
// Current span is further than the new span, break.
break;
}
else if (cur.smax < s.smin)
{
// Current span is before the new span advance.
prev = cur;
cur = cur.next;
}
else
{
// Merge spans.
if (cur.smin < s.smin)
s.smin = cur.smin;
if (cur.smax > s.smax)
s.smax = cur.smax;
// Merge flags.
if (Math.Abs(s.smax - cur.smax) <= flagMergeThreshold)
s.area = Math.Max(s.area, cur.area);
// Remove current span.
Span next = cur.next;
if (prev != null)
prev.next = next;
else
heightfield.spans[idx] = next;
cur = next;
}
}
// Insert new span.
if (prev != null)
{
s.next = prev.next;
prev.next = s;
}
else
{
s.next = heightfield.spans[idx];
heightfield.spans[idx] = s;
}
}
/**
* Divides a convex polygon of max 12 vertices into two convex polygons across a separating axis.
*
* @param inVerts
* The input polygon vertices
* @param inVertsOffset
* The offset of the first polygon vertex
* @param inVertsCount
* The number of input polygon vertices
* @param outVerts1
* The offset of the resulting polygon 1's vertices
* @param outVerts2
* The offset of the resulting polygon 2's vertices
* @param axisOffset
* The offset along the specified axis
* @param axis
* The separating axis
* @return The number of resulting polygon 1 and polygon 2 vertices
*/
private static int[] dividePoly(float[] inVerts, int inVertsOffset, int inVertsCount, int outVerts1, int outVerts2, float axisOffset,
int axis)
{
float[] d = new float[12];
for (int i = 0; i < inVertsCount; ++i)
d[i] = axisOffset - inVerts[inVertsOffset + i * 3 + axis];
int m = 0, n = 0;
for (int i = 0, j = inVertsCount - 1; i < inVertsCount; j = i, ++i)
{
bool ina = d[j] >= 0;
bool inb = d[i] >= 0;
if (ina != inb)
{
float s = d[j] / (d[j] - d[i]);
inVerts[outVerts1 + m * 3 + 0] = inVerts[inVertsOffset + j * 3 + 0]
+ (inVerts[inVertsOffset + i * 3 + 0] - inVerts[inVertsOffset + j * 3 + 0]) * s;
inVerts[outVerts1 + m * 3 + 1] = inVerts[inVertsOffset + j * 3 + 1]
+ (inVerts[inVertsOffset + i * 3 + 1] - inVerts[inVertsOffset + j * 3 + 1]) * s;
inVerts[outVerts1 + m * 3 + 2] = inVerts[inVertsOffset + j * 3 + 2]
+ (inVerts[inVertsOffset + i * 3 + 2] - inVerts[inVertsOffset + j * 3 + 2]) * s;
RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, outVerts1 + m * 3);
m++;
n++;
// add the i'th point to the right polygon. Do NOT add points that are on the dividing line
// since these were already added above
if (d[i] > 0)
{
RecastVectors.copy(inVerts, outVerts1 + m * 3, inVerts, inVertsOffset + i * 3);
m++;
}
else if (d[i] < 0)
{
RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, inVertsOffset + i * 3);
n++;
}
}
else // same side
{
// add the i'th point to the right polygon. Addition is done even for points on the dividing line
if (d[i] >= 0)
{
RecastVectors.copy(inVerts, outVerts1 + m * 3, inVerts, inVertsOffset + i * 3);
m++;
if (d[i] != 0)
continue;
}
RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, inVertsOffset + i * 3);
n++;
}
}
return new int[] { m, n };
}
/**
* Rasterize a single triangle to the heightfield. This code is extremely hot, so much care should be given to
* maintaining maximum perf here.
*
* @param verts
* An array with vertex coordinates [(x, y, z) * N]
* @param v0
* Index of triangle vertex 0, will be multiplied by 3 to get vertex coordinates
* @param v1
* Triangle vertex 1 index
* @param v2
* Triangle vertex 2 index
* @param area
* The area ID to assign to the rasterized spans
* @param hf
* Heightfield to rasterize into
* @param hfBBMin
* The min extents of the heightfield bounding box
* @param hfBBMax
* The max extents of the heightfield bounding box
* @param cellSize
* The x and z axis size of a voxel in the heightfield
* @param inverseCellSize
* 1 / cellSize
* @param inverseCellHeight
* 1 / cellHeight
* @param flagMergeThreshold
* The threshold in which area flags will be merged
*/
private static void rasterizeTri(float[] verts, int v0, int v1, int v2, int area, Heightfield hf, float[] hfBBMin,
float[] hfBBMax, float cellSize, float inverseCellSize, float inverseCellHeight, int flagMergeThreshold)
{
float[] tmin = new float[3];
float[] tmax = new float[3];
float by = hfBBMax[1] - hfBBMin[1];
// Calculate the bounding box of the triangle.
RecastVectors.copy(tmin, verts, v0 * 3);
RecastVectors.copy(tmax, verts, v0 * 3);
RecastVectors.min(tmin, verts, v1 * 3);
RecastVectors.min(tmin, verts, v2 * 3);
RecastVectors.max(tmax, verts, v1 * 3);
RecastVectors.max(tmax, verts, v2 * 3);
// If the triangle does not touch the bbox of the heightfield, skip the triagle.
if (!overlapBounds(hfBBMin, hfBBMax, tmin, tmax))
return;
// Calculate the footprint of the triangle on the grid's y-axis
int z0 = (int)((tmin[2] - hfBBMin[2]) * inverseCellSize);
int z1 = (int)((tmax[2] - hfBBMin[2]) * inverseCellSize);
int w = hf.width;
int h = hf.height;
// use -1 rather than 0 to cut the polygon properly at the start of the tile
z0 = RecastCommon.clamp(z0, -1, h - 1);
z1 = RecastCommon.clamp(z1, 0, h - 1);
// Clip the triangle into all grid cells it touches.
float[] buf = new float[7 * 3 * 4];
int @in = 0;
int inRow = 7 * 3;
int p1 = inRow + 7 * 3;
int p2 = p1 + 7 * 3;
RecastVectors.copy(buf, 0, verts, v0 * 3);
RecastVectors.copy(buf, 3, verts, v1 * 3);
RecastVectors.copy(buf, 6, verts, v2 * 3);
int nvRow, nvIn = 3;
for (int z = z0; z <= z1; ++z)
{
// Clip polygon to row. Store the remaining polygon as well
float cellZ = hfBBMin[2] + z * cellSize;
int[] nvrowin = dividePoly(buf, @in, nvIn, inRow, p1, cellZ + cellSize, 2);
nvRow = nvrowin[0];
nvIn = nvrowin[1];
{
int temp = @in;
@in = p1;
p1 = temp;
}
if (nvRow < 3)
continue;
if (z < 0)
{
continue;
}
// find the horizontal bounds in the row
float minX = buf[inRow], maxX = buf[inRow];
for (int i = 1; i < nvRow; ++i)
{
float v = buf[inRow + i * 3];
minX = Math.Min(minX, v);
maxX = Math.Max(maxX, v);
}
int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize);
int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize);
if (x1 < 0 || x0 >= w)
{
continue;
}
x0 = RecastCommon.clamp(x0, -1, w - 1);
x1 = RecastCommon.clamp(x1, 0, w - 1);
int nv, nv2 = nvRow;
for (int x = x0; x <= x1; ++x)
{
// Clip polygon to column. store the remaining polygon as well
float cx = hfBBMin[0] + x * cellSize;
int[] nvnv2 = dividePoly(buf, inRow, nv2, p1, p2, cx + cellSize, 0);
nv = nvnv2[0];
nv2 = nvnv2[1];
{
int temp = inRow;
inRow = p2;
p2 = temp;
}
if (nv < 3)
continue;
if (x < 0)
{
continue;
}
// Calculate min and max of the span.
float spanMin = buf[p1 + 1];
float spanMax = buf[p1 + 1];
for (int i = 1; i < nv; ++i)
{
spanMin = Math.Min(spanMin, buf[p1 + i * 3 + 1]);
spanMax = Math.Max(spanMax, buf[p1 + i * 3 + 1]);
}
spanMin -= hfBBMin[1];
spanMax -= hfBBMin[1];
// Skip the span if it is outside the heightfield bbox
if (spanMax < 0.0f)
continue;
if (spanMin > by)
continue;
// Clamp the span to the heightfield bbox.
if (spanMin < 0.0f)
spanMin = 0;
if (spanMax > by)
spanMax = by;
// Snap the span to the heightfield height grid.
int spanMinCellIndex = RecastCommon.clamp((int)Math.Floor(spanMin * inverseCellHeight), 0, SPAN_MAX_HEIGHT);
int spanMaxCellIndex = RecastCommon.clamp((int)Math.Ceiling(spanMax * inverseCellHeight), spanMinCellIndex + 1, SPAN_MAX_HEIGHT);
addSpan(hf, x, z, spanMinCellIndex, spanMaxCellIndex, area, flagMergeThreshold);
}
}
}
/**
* Rasterizes a single triangle into the specified heightfield. Calling this for each triangle in a mesh is less
* efficient than calling rasterizeTriangles. No spans will be added if the triangle does not overlap the
* heightfield grid.
*
* @param heightfield
* An initialized heightfield.
* @param verts
* An array with vertex coordinates [(x, y, z) * N]
* @param v0
* Index of triangle vertex 0, will be multiplied by 3 to get vertex coordinates
* @param v1
* Triangle vertex 1 index
* @param v2
* Triangle vertex 2 index
* @param areaId
* The area id of the triangle. [Limit: <= WALKABLE_AREA)
* @param flagMergeThreshold
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield
*/
public static void rasterizeTriangle(Heightfield heightfield, float[] verts, int v0, int v1, int v2, int area,
int flagMergeThreshold, Telemetry ctx)
{
ctx.startTimer("RASTERIZE_TRIANGLES");
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
rasterizeTri(verts, v0, v1, v2, area, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize,
inverseCellHeight, flagMergeThreshold);
ctx.stopTimer("RASTERIZE_TRIANGLES");
}
/**
* Rasterizes an indexed triangle mesh into the specified heightfield. Spans will only be added for triangles that
* overlap the heightfield grid.
*
* @param heightfield
* An initialized heightfield.
* @param verts
* The vertices. [(x, y, z) * N]
* @param tris
* The triangle indices. [(vertA, vertB, vertC) * nt]
* @param areaIds
* The area id's of the triangles. [Limit: <= WALKABLE_AREA] [Size: numTris]
* @param numTris
* The number of triangles.
* @param flagMergeThreshold
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield
*/
public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] tris, int[] areaIds, int numTris,
int flagMergeThreshold, Telemetry ctx)
{
ctx.startTimer("RASTERIZE_TRIANGLES");
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
for (int triIndex = 0; triIndex < numTris; ++triIndex)
{
int v0 = tris[triIndex * 3 + 0];
int v1 = tris[triIndex * 3 + 1];
int v2 = tris[triIndex * 3 + 2];
rasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold);
}
ctx.stopTimer("RASTERIZE_TRIANGLES");
}
/**
* Rasterizes a triangle list into the specified heightfield. Expects each triangle to be specified as three
* sequential vertices of 3 floats. Spans will only be added for triangles that overlap the heightfield grid.
*
* @param heightfield
* An initialized heightfield.
* @param verts
* The vertices. [(x, y, z) * numVerts]
* @param areaIds
* The area id's of the triangles. [Limit: <= WALKABLE_AREA] [Size: numTris]
* @param tris
* The triangle indices. [(vertA, vertB, vertC) * nt]
* @param numTris
* The number of triangles.
* @param flagMergeThreshold
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield
*/
public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] areaIds, int numTris,
int flagMergeThreshold, Telemetry ctx)
{
ctx.startTimer("RASTERIZE_TRIANGLES");
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
for (int triIndex = 0; triIndex < numTris; ++triIndex)
{
int v0 = (triIndex * 3 + 0);
int v1 = (triIndex * 3 + 1);
int v2 = (triIndex * 3 + 2);
rasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold);
}
ctx.stopTimer("RASTERIZE_TRIANGLES");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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;
namespace DotRecast.Recast;
public static class RecastVectors
{
public static void min(float[] a, float[] b, int i)
{
a[0] = Math.Min(a[0], b[i + 0]);
a[1] = Math.Min(a[1], b[i + 1]);
a[2] = Math.Min(a[2], b[i + 2]);
}
public static void max(float[] a, float[] b, int i)
{
a[0] = Math.Max(a[0], b[i + 0]);
a[1] = Math.Max(a[1], b[i + 1]);
a[2] = Math.Max(a[2], b[i + 2]);
}
public static void copy(float[] @out, float[] @in, int i)
{
copy(@out, 0, @in, i);
}
public static void copy(float[] @out, float[] @in)
{
copy(@out, 0, @in, 0);
}
public static void copy(float[] @out, int n, float[] @in, int m)
{
@out[n] = @in[m];
@out[n + 1] = @in[m + 1];
@out[n + 2] = @in[m + 2];
}
public static void add(float[] e0, float[] a, float[] verts, int i)
{
e0[0] = a[0] + verts[i];
e0[1] = a[1] + verts[i + 1];
e0[2] = a[2] + verts[i + 2];
}
public static void sub(float[] e0, float[] verts, int i, int j)
{
e0[0] = verts[i] - verts[j];
e0[1] = verts[i + 1] - verts[j + 1];
e0[2] = verts[i + 2] - verts[j + 2];
}
public static void sub(float[] e0, float[] i, float[] verts, int j)
{
e0[0] = i[0] - verts[j];
e0[1] = i[1] - verts[j + 1];
e0[2] = i[2] - verts[j + 2];
}
public static void cross(float[] dest, float[] v1, float[] v2)
{
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
}
public static void normalize(float[] v)
{
float d = (float)(1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
v[0] *= d;
v[1] *= d;
v[2] *= d;
}
public static float dot(float[] v1, float[] v2)
{
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
}

View File

@ -0,0 +1,73 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using DotRecast.Recast.Geom;
namespace DotRecast.Recast;
public class RecastVoxelization {
public static Heightfield buildSolidHeightfield(InputGeomProvider geomProvider, RecastBuilderConfig builderCfg,
Telemetry ctx) {
RecastConfig cfg = builderCfg.cfg;
// Allocate voxel heightfield where we rasterize our input data to.
Heightfield solid = new Heightfield(builderCfg.width, builderCfg.height, builderCfg.bmin, builderCfg.bmax, cfg.cs,
cfg.ch, cfg.borderSize);
// Allocate array that can hold triangle area types.
// If you have multiple meshes you need to process, allocate
// and array which can hold the max number of triangles you need to
// process.
// Find triangles which are walkable based on their slope and rasterize
// them.
// If your input data is multiple meshes, you can transform them here,
// calculate
// the are type for each of the meshes and rasterize them.
foreach (TriMesh geom in geomProvider.meshes()) {
float[] verts = geom.getVerts();
if (cfg.useTiles) {
float[] tbmin = new float[2];
float[] tbmax = new float[2];
tbmin[0] = builderCfg.bmin[0];
tbmin[1] = builderCfg.bmin[2];
tbmax[0] = builderCfg.bmax[0];
tbmax[1] = builderCfg.bmax[2];
List<ChunkyTriMeshNode> nodes = geom.getChunksOverlappingRect(tbmin, tbmax);
foreach (ChunkyTriMeshNode node in nodes) {
int[] tris = node.tris;
int ntris = tris.Length / 3;
int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
cfg.walkableAreaMod);
RecastRasterization.rasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.walkableClimb, ctx);
}
} else {
int[] tris = geom.getTris();
int ntris = tris.Length / 3;
int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
cfg.walkableAreaMod);
RecastRasterization.rasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.walkableClimb, ctx);
}
}
return solid;
}
}

View File

@ -0,0 +1,33 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast;
/** Represents a span in a heightfield. */
public class Span {
/** The lower limit of the span. [Limit: &lt; smax] */
public int smin;
/** 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 Span next;
}

View File

@ -0,0 +1,57 @@
/*
recast4j copyright (c) 2021 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using DotRecast.Core;
namespace DotRecast.Recast;
public class Telemetry
{
private readonly ThreadLocal<Dictionary<string, AtomicLong>> timerStart = new(() => new Dictionary<string, AtomicLong>());
private readonly ConcurrentDictionary<string, AtomicLong> timerAccum = new();
public void startTimer(string name)
{
timerStart.Value[name] = new AtomicLong(Stopwatch.GetTimestamp());
}
public void stopTimer(string name)
{
timerAccum
.GetOrAdd(name, _ => new AtomicLong(0))
.AddAndGet(Stopwatch.GetTimestamp() - timerStart.Value?[name].Read() ?? 0);
}
public void warn(string @string)
{
Console.WriteLine(@string);
}
public void print()
{
foreach (var (n, v) in timerAccum)
{
Console.WriteLine(n + ": " + v.Read() / 1000000);
}
}
}

View File

@ -0,0 +1,149 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
using static DetourCommon;
public class AbstractCrowdTest {
protected readonly long[] startRefs = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L,
281474976710733L };
protected readonly long[] endRefs = { 281474976710721L, 281474976710767L, 281474976710758L, 281474976710731L, 281474976710772L };
protected readonly float[][] startPoss = {
new[] { 22.60652f, 10.197294f, -45.918674f },
new[] { 22.331268f, 10.197294f, -1.0401875f },
new[] { 18.694363f, 15.803535f, -73.090416f },
new[] { 0.7453353f, 10.197294f, -5.94005f },
new[] { -20.651257f, 5.904126f, -13.712508f } };
protected readonly float[][] endPoss = {
new[] { 6.4576626f, 10.197294f, -18.33406f },
new[] { -5.8023443f, 0.19729415f, 3.008419f },
new[] { 38.423977f, 10.197294f, -0.116066754f },
new[] { 0.8635526f, 10.197294f, -10.31032f },
new[] { 18.784092f, 10.197294f, 3.0543678f } };
protected MeshData nmd;
protected NavMeshQuery query;
protected NavMesh navmesh;
protected Crowd crowd;
protected List<CrowdAgent> agents;
[SetUp]
public void setUp() {
nmd = new RecastTestMeshBuilder().getMeshData();
navmesh = new NavMesh(nmd, 6, 0);
query = new NavMeshQuery(navmesh);
CrowdConfig config = new CrowdConfig(0.6f);
crowd = new Crowd(config, navmesh);
ObstacleAvoidanceQuery.ObstacleAvoidanceParams option = new ObstacleAvoidanceQuery.ObstacleAvoidanceParams();
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 1;
crowd.setObstacleAvoidanceParams(0, option);
option = new ObstacleAvoidanceQuery.ObstacleAvoidanceParams();
option.velBias = 0.5f;
option.adaptiveDivs = 5;
option.adaptiveRings = 2;
option.adaptiveDepth = 2;
crowd.setObstacleAvoidanceParams(1, option);
option = new ObstacleAvoidanceQuery.ObstacleAvoidanceParams();
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 2;
option.adaptiveDepth = 3;
crowd.setObstacleAvoidanceParams(2, option);
option = new ObstacleAvoidanceQuery.ObstacleAvoidanceParams();
option.velBias = 0.5f;
option.adaptiveDivs = 7;
option.adaptiveRings = 3;
option.adaptiveDepth = 3;
crowd.setObstacleAvoidanceParams(3, option);
agents = new();
}
protected CrowdAgentParams getAgentParams(int updateFlags, int obstacleAvoidanceType) {
CrowdAgentParams ap = new CrowdAgentParams();
ap.radius = 0.6f;
ap.height = 2f;
ap.maxAcceleration = 8.0f;
ap.maxSpeed = 3.5f;
ap.collisionQueryRange = ap.radius * 12f;
ap.pathOptimizationRange = ap.radius * 30f;
ap.updateFlags = updateFlags;
ap.obstacleAvoidanceType = obstacleAvoidanceType;
ap.separationWeight = 2f;
return ap;
}
protected void addAgentGrid(int size, float distance, int updateFlags, int obstacleAvoidanceType, float[] startPos) {
CrowdAgentParams ap = getAgentParams(updateFlags, obstacleAvoidanceType);
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
float[] pos = new float[3];
pos[0] = startPos[0] + i * distance;
pos[1] = startPos[1];
pos[2] = startPos[2] + j * distance;
agents.Add(crowd.addAgent(pos, ap));
}
}
}
protected void setMoveTarget(float[] pos, bool adjust) {
float[] ext = crowd.getQueryExtents();
QueryFilter filter = crowd.getFilter(0);
if (adjust) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
float[] vel = calcVel(ag.npos, pos, ag.option.maxSpeed);
crowd.requestMoveVelocity(ag, vel);
}
} else {
Result<FindNearestPolyResult> nearest = query.findNearestPoly(pos, ext, filter);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
crowd.requestMoveTarget(ag, nearest.result.getNearestRef(), nearest.result.getNearestPos());
}
}
}
protected float[] calcVel(float[] pos, float[] tgt, float speed) {
float[] vel = vSub(tgt, pos);
vel[1] = 0.0f;
vNormalize(vel);
vel = vScale(vel, speed);
return vel;
}
protected void dumpActiveAgents(int i) {
Console.WriteLine(crowd.getActiveAgents().Count);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Console.WriteLine(ag.state + ", " + ag.targetState);
Console.WriteLine(ag.npos[0] + ", " + ag.npos[1] + ", " + ag.npos[2]);
Console.WriteLine(ag.nvel[0] + ", " + ag.nvel[1] + ", " + ag.nvel[2]);
}
}
}

View File

@ -0,0 +1,706 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class Crowd1Test : AbstractCrowdTest {
static readonly float[][] EXPECTED_A1Q0TVTA = {
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
new[] { 20.512094f, 10.197294f, -44.831028f, -3.105014f, 0.000000f, 1.615205f },
new[] { 19.891315f, 10.197294f, -44.507557f, -3.103898f, 0.000000f, 1.617350f },
new[] { 19.270782f, 10.197294f, -44.183617f, -3.102670f, 0.000000f, 1.619704f },
new[] { 18.650520f, 10.197294f, -43.859158f, -3.101315f, 0.000000f, 1.622296f },
new[] { 18.030558f, 10.197294f, -43.534126f, -3.099816f, 0.000000f, 1.625158f },
new[] { 17.410927f, 10.197294f, -43.208462f, -3.098152f, 0.000000f, 1.628329f },
new[] { 16.791668f, 10.197294f, -42.882092f, -3.096297f, 0.000000f, 1.631854f },
new[] { 16.172823f, 10.197294f, -42.554935f, -3.094221f, 0.000000f, 1.635786f },
new[] { 15.554445f, 10.197294f, -42.226898f, -3.091888f, 0.000000f, 1.640191f },
new[] { 14.936594f, 10.197294f, -41.897869f, -3.089255f, 0.000000f, 1.645145f },
new[] { 14.319340f, 10.197294f, -41.567722f, -3.086269f, 0.000000f, 1.650741f },
new[] { 13.702767f, 10.197294f, -41.236305f, -3.082862f, 0.000000f, 1.657094f },
new[] { 13.086977f, 10.197294f, -40.903435f, -3.078954f, 0.000000f, 1.664345f },
new[] { 12.472089f, 10.197294f, -40.568901f, -3.074442f, 0.000000f, 1.672665f },
new[] { 11.858250f, 10.197294f, -40.232449f, -3.069196f, 0.000000f, 1.682271f },
new[] { 11.245640f, 10.197294f, -39.893761f, -3.063048f, 0.000000f, 1.693439f },
new[] { 10.634483f, 10.197294f, -39.552460f, -3.055783f, 0.000000f, 1.706514f },
new[] { 10.025061f, 10.197294f, -39.208069f, -3.047112f, 0.000000f, 1.721948f },
new[] { 9.417730f, 10.197294f, -38.860004f, -3.036654f, 0.000000f, 1.740326f },
new[] { 8.812954f, 10.197294f, -38.507519f, -3.023880f, 0.000000f, 1.762429f },
new[] { 8.211343f, 10.197294f, -38.149658f, -3.008055f, 0.000000f, 1.789302f },
new[] { 7.613717f, 10.197294f, -37.785183f, -2.988129f, 0.000000f, 1.822385f },
new[] { 7.021209f, 10.197294f, -37.412445f, -2.962540f, 0.000000f, 1.863695f },
new[] { 6.435429f, 10.197294f, -37.029221f, -2.928901f, 0.000000f, 1.916126f },
new[] { 5.858753f, 10.197294f, -36.632427f, -2.883380f, 0.000000f, 1.983965f },
new[] { 5.294861f, 10.197294f, -36.217667f, -2.819461f, 0.000000f, 2.073798f },
new[] { 4.749827f, 10.197294f, -35.778419f, -2.725170f, 0.000000f, 2.196236f },
new[] { 4.234636f, 10.197294f, -35.304523f, -2.575954f, 0.000000f, 2.369486f },
new[] { 3.772264f, 10.197294f, -34.778965f, -2.311863f, 0.000000f, 2.627792f },
new[] { 3.428036f, 10.197294f, -34.169453f, -1.721137f, 0.000000f, 3.047571f },
new[] { 3.033963f, 10.197294f, -33.590916f, -1.970364f, 0.000000f, 2.892692f },
new[] { 2.656040f, 10.197294f, -33.001701f, -1.889616f, 0.000000f, 2.946074f },
new[] { 2.307335f, 10.197294f, -32.394737f, -1.743529f, 0.000000f, 3.034816f },
new[] { 2.021275f, 10.197294f, -31.755856f, -1.430300f, 0.000000f, 3.194408f },
new[] { 1.935164f, 10.197294f, -31.061172f, -0.430555f, 0.000000f, 3.473417f },
new[] { 1.847493f, 10.197294f, -30.366684f, -0.438351f, 0.000000f, 3.472441f },
new[] { 1.943488f, 10.197294f, -29.673298f, 0.479975f, 0.000000f, 3.466933f },
new[] { 2.170555f, 10.197294f, -29.011150f, 1.135336f, 0.000000f, 3.310742f },
new[] { 2.415170f, 10.197294f, -28.355282f, 1.223072f, 0.000000f, 3.279344f },
new[] { 2.677041f, 10.197294f, -27.706110f, 1.309357f, 0.000000f, 3.245857f },
new[] { 2.938912f, 10.197294f, -27.056938f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.200784f, 10.197294f, -26.407766f, 1.309357f, 0.000000f, 3.245856f },
new[] { 3.462655f, 10.197294f, -25.758595f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.724526f, 10.197294f, -25.109423f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.986398f, 10.197294f, -24.460251f, 1.309357f, 0.000000f, 3.245857f },
new[] { 4.248269f, 10.197294f, -23.811079f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.510140f, 10.197294f, -23.161907f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.772012f, 10.197294f, -22.512735f, 1.309357f, 0.000000f, 3.245856f },
new[] { 5.033883f, 10.197294f, -21.863564f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.295755f, 10.197294f, -21.214392f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.557627f, 10.197294f, -20.565220f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.819499f, 10.197294f, -19.916048f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.081370f, 10.197294f, -19.266876f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.322058f, 10.197294f, -18.670219f, 1.203439f, 0.000000f, 2.983284f },
new[] { 6.372280f, 10.197294f, -18.330706f, -0.014387f, 0.000000f, 1.339125f },
new[] { 6.398773f, 10.197294f, -18.310312f, 0.124516f, 0.000000f, -0.004893f },
new[] { 6.415949f, 10.197294f, -18.317240f, 0.085881f, 0.000000f, -0.034633f },
new[] { 6.428115f, 10.197294f, -18.322145f, 0.060833f, 0.000000f, -0.024530f },
new[] { 6.436733f, 10.197294f, -18.325621f, 0.043090f, 0.000000f, -0.017376f },
new[] { 6.442838f, 10.197294f, -18.328083f, 0.030522f, 0.000000f, -0.012308f },
new[] { 6.447162f, 10.197294f, -18.329826f, 0.021620f, 0.000000f, -0.008717f },
new[] { 6.450224f, 10.197294f, -18.331062f, 0.015314f, 0.000000f, -0.006175f },
new[] { 6.450224f, 10.197294f, -18.331062f, 0.000000f, 0.000000f, 0.000000f } };
static readonly float[][] EXPECTED_A1Q0TVT = {
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
new[] { 20.512094f, 10.197294f, -44.831028f, -3.105014f, 0.000000f, 1.615205f },
new[] { 19.891315f, 10.197294f, -44.507557f, -3.103898f, 0.000000f, 1.617350f },
new[] { 19.270782f, 10.197294f, -44.183617f, -3.102670f, 0.000000f, 1.619704f },
new[] { 18.650520f, 10.197294f, -43.859158f, -3.101315f, 0.000000f, 1.622296f },
new[] { 18.030558f, 10.197294f, -43.534126f, -3.099816f, 0.000000f, 1.625158f },
new[] { 17.410927f, 10.197294f, -43.208462f, -3.098152f, 0.000000f, 1.628329f },
new[] { 16.791668f, 10.197294f, -42.882092f, -3.096297f, 0.000000f, 1.631854f },
new[] { 16.172823f, 10.197294f, -42.554935f, -3.094221f, 0.000000f, 1.635786f },
new[] { 15.554445f, 10.197294f, -42.226898f, -3.091888f, 0.000000f, 1.640191f },
new[] { 14.936594f, 10.197294f, -41.897869f, -3.089255f, 0.000000f, 1.645145f },
new[] { 14.319340f, 10.197294f, -41.567722f, -3.086269f, 0.000000f, 1.650741f },
new[] { 13.702767f, 10.197294f, -41.236305f, -3.082862f, 0.000000f, 1.657094f },
new[] { 13.086977f, 10.197294f, -40.903435f, -3.078953f, 0.000000f, 1.664345f },
new[] { 12.472089f, 10.197294f, -40.568901f, -3.074442f, 0.000000f, 1.672665f },
new[] { 11.858250f, 10.197294f, -40.232449f, -3.069196f, 0.000000f, 1.682271f },
new[] { 11.245640f, 10.197294f, -39.893761f, -3.063048f, 0.000000f, 1.693439f },
new[] { 10.634483f, 10.197294f, -39.552460f, -3.055783f, 0.000000f, 1.706514f },
new[] { 10.025061f, 10.197294f, -39.208069f, -3.047112f, 0.000000f, 1.721948f },
new[] { 9.417730f, 10.197294f, -38.860004f, -3.036654f, 0.000000f, 1.740326f },
new[] { 8.812955f, 10.197294f, -38.507519f, -3.023879f, 0.000000f, 1.762429f },
new[] { 8.211344f, 10.197294f, -38.149658f, -3.008055f, 0.000000f, 1.789302f },
new[] { 7.613718f, 10.197294f, -37.785183f, -2.988129f, 0.000000f, 1.822385f },
new[] { 7.021210f, 10.197294f, -37.412445f, -2.962540f, 0.000000f, 1.863695f },
new[] { 6.435430f, 10.197294f, -37.029221f, -2.928901f, 0.000000f, 1.916126f },
new[] { 5.858754f, 10.197294f, -36.632427f, -2.883381f, 0.000000f, 1.983965f },
new[] { 5.294862f, 10.197294f, -36.217667f, -2.819461f, 0.000000f, 2.073798f },
new[] { 4.749828f, 10.197294f, -35.778419f, -2.725170f, 0.000000f, 2.196235f },
new[] { 4.234637f, 10.197294f, -35.304523f, -2.575955f, 0.000000f, 2.369484f },
new[] { 3.772264f, 10.197294f, -34.778965f, -2.311864f, 0.000000f, 2.627791f },
new[] { 3.428036f, 10.197294f, -34.169453f, -1.721139f, 0.000000f, 3.047570f },
new[] { 3.033963f, 10.197294f, -33.590916f, -1.970364f, 0.000000f, 2.892692f },
new[] { 2.656040f, 10.197294f, -33.001701f, -1.889616f, 0.000000f, 2.946074f },
new[] { 2.307335f, 10.197294f, -32.394737f, -1.743529f, 0.000000f, 3.034816f },
new[] { 2.021275f, 10.197294f, -31.755856f, -1.430300f, 0.000000f, 3.194408f },
new[] { 1.935164f, 10.197294f, -31.061172f, -0.430555f, 0.000000f, 3.473417f },
new[] { 1.847493f, 10.197294f, -30.366684f, -0.438351f, 0.000000f, 3.472441f },
new[] { 1.943488f, 10.197294f, -29.673298f, 0.479975f, 0.000000f, 3.466933f },
new[] { 2.170555f, 10.197294f, -29.011150f, 1.135336f, 0.000000f, 3.310742f },
new[] { 2.415170f, 10.197294f, -28.355282f, 1.223072f, 0.000000f, 3.279344f },
new[] { 2.677041f, 10.197294f, -27.706110f, 1.309357f, 0.000000f, 3.245857f },
new[] { 2.938912f, 10.197294f, -27.056938f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.200784f, 10.197294f, -26.407766f, 1.309357f, 0.000000f, 3.245856f },
new[] { 3.462655f, 10.197294f, -25.758595f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.724526f, 10.197294f, -25.109423f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.986398f, 10.197294f, -24.460251f, 1.309357f, 0.000000f, 3.245857f },
new[] { 4.248269f, 10.197294f, -23.811079f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.510140f, 10.197294f, -23.161907f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.772012f, 10.197294f, -22.512735f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.033883f, 10.197294f, -21.863564f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.295755f, 10.197294f, -21.214392f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.557627f, 10.197294f, -20.565220f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.819499f, 10.197294f, -19.916048f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.081370f, 10.197294f, -19.266876f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.300874f, 10.197294f, -18.722734f, 1.097519f, 0.000000f, 2.720711f },
new[] { 6.400665f, 10.197294f, -18.475355f, 0.457299f, 0.000000f, 1.133632f },
new[] { 6.433914f, 10.197294f, -18.392933f, 0.166243f, 0.000000f, 0.412109f },
new[] { 6.447767f, 10.197294f, -18.358591f, 0.069268f, 0.000000f, 0.171711f },
new[] { 6.453539f, 10.197294f, -18.344282f, 0.028861f, 0.000000f, 0.071547f },
new[] { 6.455945f, 10.197294f, -18.338320f, 0.012026f, 0.000000f, 0.029813f },
new[] { 6.455945f, 10.197294f, -18.338320f, 0.000000f, 0.000000f, 0.000000f } };
static readonly float[][] EXPECTED_A1Q0TV = {
new[] { 22.333418f, 10.197294f, -45.751896f, -2.987050f, 0.000000f, 1.824153f },
new[] { 21.787214f, 10.197294f, -45.418335f, -2.987049f, 0.000000f, 1.824153f },
new[] { 21.189804f, 10.197294f, -45.053505f, -2.987050f, 0.000000f, 1.824153f },
new[] { 20.592394f, 10.197294f, -44.688675f, -2.987049f, 0.000000f, 1.824153f },
new[] { 19.994984f, 10.197294f, -44.323845f, -2.987049f, 0.000000f, 1.824153f },
new[] { 19.397573f, 10.197294f, -43.959015f, -2.987049f, 0.000000f, 1.824154f },
new[] { 18.800163f, 10.197294f, -43.594185f, -2.987049f, 0.000000f, 1.824154f },
new[] { 18.202753f, 10.197294f, -43.229355f, -2.987049f, 0.000000f, 1.824154f },
new[] { 17.605343f, 10.197294f, -42.864525f, -2.987049f, 0.000000f, 1.824154f },
new[] { 17.007933f, 10.197294f, -42.499695f, -2.987049f, 0.000000f, 1.824154f },
new[] { 16.410522f, 10.197294f, -42.134865f, -2.987049f, 0.000000f, 1.824155f },
new[] { 15.813112f, 10.197294f, -41.770035f, -2.987049f, 0.000000f, 1.824155f },
new[] { 15.215703f, 10.197294f, -41.405205f, -2.987048f, 0.000000f, 1.824155f },
new[] { 14.618294f, 10.197294f, -41.040375f, -2.987048f, 0.000000f, 1.824155f },
new[] { 14.020885f, 10.197294f, -40.675545f, -2.987048f, 0.000000f, 1.824155f },
new[] { 13.423475f, 10.197294f, -40.310715f, -2.987048f, 0.000000f, 1.824155f },
new[] { 12.826066f, 10.197294f, -39.945885f, -2.987048f, 0.000000f, 1.824156f },
new[] { 12.228657f, 10.197294f, -39.581055f, -2.987048f, 0.000000f, 1.824156f },
new[] { 11.631248f, 10.197294f, -39.216225f, -2.987048f, 0.000000f, 1.824156f },
new[] { 11.033838f, 10.197294f, -38.851395f, -2.987048f, 0.000000f, 1.824156f },
new[] { 10.436429f, 10.197294f, -38.486565f, -2.987047f, 0.000000f, 1.824157f },
new[] { 9.839020f, 10.197294f, -38.121735f, -2.987047f, 0.000000f, 1.824157f },
new[] { 9.241611f, 10.197294f, -37.756905f, -2.987047f, 0.000000f, 1.824157f },
new[] { 8.644201f, 10.197294f, -37.392075f, -2.987047f, 0.000000f, 1.824158f },
new[] { 8.046792f, 10.197294f, -37.027245f, -2.987046f, 0.000000f, 1.824158f },
new[] { 7.449383f, 10.197294f, -36.662415f, -2.987046f, 0.000000f, 1.824160f },
new[] { 6.851974f, 10.197294f, -36.297581f, -2.987045f, 0.000000f, 1.824160f },
new[] { 6.254564f, 10.197294f, -35.932751f, -2.987046f, 0.000000f, 1.824159f },
new[] { 5.657155f, 10.197294f, -35.567917f, -2.987045f, 0.000000f, 1.824161f },
new[] { 5.059746f, 10.197294f, -35.203087f, -2.987046f, 0.000000f, 1.824160f },
new[] { 4.462337f, 10.197294f, -34.838253f, -2.987044f, 0.000000f, 1.824162f },
new[] { 3.864928f, 10.197294f, -34.473423f, -2.987046f, 0.000000f, 1.824159f },
new[] { 3.267520f, 10.197294f, -34.108589f, -2.987040f, 0.000000f, 1.824169f },
new[] { 2.910351f, 10.197294f, -33.532368f, -1.429270f, 0.000000f, 3.194869f },
new[] { 2.648150f, 10.197294f, -32.883331f, -1.311005f, 0.000000f, 3.245191f },
new[] { 2.385949f, 10.197294f, -32.234291f, -1.311003f, 0.000000f, 3.245192f },
new[] { 2.123748f, 10.197294f, -31.585253f, -1.311006f, 0.000000f, 3.245191f },
new[] { 1.861547f, 10.197294f, -30.936214f, -1.311005f, 0.000000f, 3.245191f },
new[] { 1.916189f, 10.197294f, -30.242338f, 0.359062f, 0.000000f, 3.481533f },
new[] { 2.011134f, 10.197294f, -29.548807f, 0.474725f, 0.000000f, 3.467656f },
new[] { 2.246398f, 10.197294f, -28.889526f, 1.176323f, 0.000000f, 3.296402f },
new[] { 2.481663f, 10.197294f, -28.230246f, 1.176324f, 0.000000f, 3.296402f },
new[] { 2.716928f, 10.197294f, -27.570965f, 1.176325f, 0.000000f, 3.296401f },
new[] { 2.979683f, 10.197294f, -26.922150f, 1.313774f, 0.000000f, 3.244071f },
new[] { 3.242438f, 10.197294f, -26.273335f, 1.313774f, 0.000000f, 3.244071f },
new[] { 3.505193f, 10.197294f, -25.624521f, 1.313775f, 0.000000f, 3.244071f },
new[] { 3.767948f, 10.197294f, -24.975708f, 1.313774f, 0.000000f, 3.244071f },
new[] { 4.030703f, 10.197294f, -24.326893f, 1.313774f, 0.000000f, 3.244071f },
new[] { 4.293458f, 10.197294f, -23.678078f, 1.313775f, 0.000000f, 3.244071f },
new[] { 4.556212f, 10.197294f, -23.029263f, 1.313775f, 0.000000f, 3.244071f },
new[] { 4.818967f, 10.197294f, -22.380449f, 1.313775f, 0.000000f, 3.244071f },
new[] { 5.081722f, 10.197294f, -21.731636f, 1.313775f, 0.000000f, 3.244071f },
new[] { 5.344477f, 10.197294f, -21.082821f, 1.313775f, 0.000000f, 3.244071f },
new[] { 5.607232f, 10.197294f, -20.434008f, 1.313775f, 0.000000f, 3.244071f },
new[] { 5.869987f, 10.197294f, -19.785194f, 1.313775f, 0.000000f, 3.244071f },
new[] { 6.132742f, 10.197294f, -19.136379f, 1.313774f, 0.000000f, 3.244071f },
new[] { 6.322279f, 10.197294f, -18.668360f, 0.947685f, 0.000000f, 2.340096f },
new[] { 6.401253f, 10.197294f, -18.473352f, 0.394869f, 0.000000f, 0.975039f },
new[] { 6.434158f, 10.197294f, -18.392099f, 0.164529f, 0.000000f, 0.406268f },
new[] { 6.447869f, 10.197294f, -18.358244f, 0.068554f, 0.000000f, 0.169280f },
new[] { 6.453582f, 10.197294f, -18.344137f, 0.028564f, 0.000000f, 0.070535f },
new[] { 6.455962f, 10.197294f, -18.338259f, 0.011902f, 0.000000f, 0.029390f },
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f } };
static readonly float[][] EXPECTED_A1Q0T = {
new[] { 22.333418f, 10.197294f, -45.751896f, -2.987050f, 0.000000f, 1.824153f },
new[] { 21.787214f, 10.197294f, -45.418335f, -2.987049f, 0.000000f, 1.824153f },
new[] { 21.189804f, 10.197294f, -45.053505f, -2.987050f, 0.000000f, 1.824153f },
new[] { 20.592394f, 10.197294f, -44.688675f, -2.987049f, 0.000000f, 1.824153f },
new[] { 19.994984f, 10.197294f, -44.323845f, -2.987049f, 0.000000f, 1.824153f },
new[] { 19.397573f, 10.197294f, -43.959015f, -2.987049f, 0.000000f, 1.824154f },
new[] { 18.800163f, 10.197294f, -43.594185f, -2.987049f, 0.000000f, 1.824154f },
new[] { 18.202753f, 10.197294f, -43.229355f, -2.987049f, 0.000000f, 1.824154f },
new[] { 17.605343f, 10.197294f, -42.864525f, -2.987049f, 0.000000f, 1.824154f },
new[] { 17.007933f, 10.197294f, -42.499695f, -2.987049f, 0.000000f, 1.824154f },
new[] { 16.410522f, 10.197294f, -42.134865f, -2.987049f, 0.000000f, 1.824155f },
new[] { 15.813112f, 10.197294f, -41.770035f, -2.987049f, 0.000000f, 1.824155f },
new[] { 15.215703f, 10.197294f, -41.405205f, -2.987048f, 0.000000f, 1.824155f },
new[] { 14.618294f, 10.197294f, -41.040375f, -2.987048f, 0.000000f, 1.824155f },
new[] { 14.020885f, 10.197294f, -40.675545f, -2.987048f, 0.000000f, 1.824155f },
new[] { 13.423475f, 10.197294f, -40.310715f, -2.987048f, 0.000000f, 1.824155f },
new[] { 12.826066f, 10.197294f, -39.945885f, -2.987048f, 0.000000f, 1.824156f },
new[] { 12.228657f, 10.197294f, -39.581055f, -2.987048f, 0.000000f, 1.824156f },
new[] { 11.631248f, 10.197294f, -39.216225f, -2.987048f, 0.000000f, 1.824156f },
new[] { 11.033838f, 10.197294f, -38.851395f, -2.987048f, 0.000000f, 1.824156f },
new[] { 10.436429f, 10.197294f, -38.486565f, -2.987047f, 0.000000f, 1.824157f },
new[] { 9.839020f, 10.197294f, -38.121735f, -2.987047f, 0.000000f, 1.824157f },
new[] { 9.241611f, 10.197294f, -37.756905f, -2.987047f, 0.000000f, 1.824157f },
new[] { 8.644201f, 10.197294f, -37.392075f, -2.987047f, 0.000000f, 1.824158f },
new[] { 8.046792f, 10.197294f, -37.027245f, -2.987046f, 0.000000f, 1.824158f },
new[] { 7.449383f, 10.197294f, -36.662415f, -2.987046f, 0.000000f, 1.824160f },
new[] { 6.851974f, 10.197294f, -36.297581f, -2.987045f, 0.000000f, 1.824160f },
new[] { 6.254564f, 10.197294f, -35.932751f, -2.987046f, 0.000000f, 1.824159f },
new[] { 5.657155f, 10.197294f, -35.567917f, -2.987045f, 0.000000f, 1.824161f },
new[] { 5.059746f, 10.197294f, -35.203087f, -2.987046f, 0.000000f, 1.824160f },
new[] { 4.462337f, 10.197294f, -34.838253f, -2.987044f, 0.000000f, 1.824162f },
new[] { 3.864928f, 10.197294f, -34.473423f, -2.987046f, 0.000000f, 1.824159f },
new[] { 3.267520f, 10.197294f, -34.108589f, -2.987040f, 0.000000f, 1.824169f },
new[] { 2.910351f, 10.197294f, -33.532368f, -1.429270f, 0.000000f, 3.194869f },
new[] { 2.648150f, 10.197294f, -32.883331f, -1.311005f, 0.000000f, 3.245191f },
new[] { 2.385949f, 10.197294f, -32.234291f, -1.311003f, 0.000000f, 3.245192f },
new[] { 2.123748f, 10.197294f, -31.585253f, -1.311006f, 0.000000f, 3.245191f },
new[] { 1.861547f, 10.197294f, -30.936214f, -1.311005f, 0.000000f, 3.245191f },
new[] { 1.916189f, 10.197294f, -30.242338f, 0.359062f, 0.000000f, 3.481533f },
new[] { 2.011134f, 10.197294f, -29.548807f, 0.474725f, 0.000000f, 3.467656f },
new[] { 2.246398f, 10.197294f, -28.889526f, 1.176323f, 0.000000f, 3.296402f },
new[] { 2.481663f, 10.197294f, -28.230246f, 1.176324f, 0.000000f, 3.296402f },
new[] { 2.716928f, 10.197294f, -27.570965f, 1.176325f, 0.000000f, 3.296401f },
new[] { 2.979683f, 10.197294f, -26.922150f, 1.313774f, 0.000000f, 3.244071f },
new[] { 3.242438f, 10.197294f, -26.273335f, 1.313774f, 0.000000f, 3.244071f },
new[] { 3.505193f, 10.197294f, -25.624521f, 1.313775f, 0.000000f, 3.244071f },
new[] { 3.767948f, 10.197294f, -24.975708f, 1.313774f, 0.000000f, 3.244071f },
new[] { 4.030703f, 10.197294f, -24.326893f, 1.313774f, 0.000000f, 3.244071f },
new[] { 4.293458f, 10.197294f, -23.678078f, 1.313775f, 0.000000f, 3.244071f },
new[] { 4.556212f, 10.197294f, -23.029263f, 1.313775f, 0.000000f, 3.244071f },
new[] { 4.818967f, 10.197294f, -22.380449f, 1.313775f, 0.000000f, 3.244071f },
new[] { 5.081722f, 10.197294f, -21.731636f, 1.313775f, 0.000000f, 3.244071f },
new[] { 5.344477f, 10.197294f, -21.082821f, 1.313775f, 0.000000f, 3.244071f },
new[] { 5.607232f, 10.197294f, -20.434008f, 1.313775f, 0.000000f, 3.244071f },
new[] { 5.869987f, 10.197294f, -19.785194f, 1.313775f, 0.000000f, 3.244071f },
new[] { 6.132742f, 10.197294f, -19.136379f, 1.313774f, 0.000000f, 3.244071f },
new[] { 6.322279f, 10.197294f, -18.668360f, 0.947685f, 0.000000f, 2.340096f },
new[] { 6.401253f, 10.197294f, -18.473352f, 0.394869f, 0.000000f, 0.975039f },
new[] { 6.434158f, 10.197294f, -18.392099f, 0.164529f, 0.000000f, 0.406268f },
new[] { 6.447869f, 10.197294f, -18.358244f, 0.068554f, 0.000000f, 0.169280f },
new[] { 6.453582f, 10.197294f, -18.344137f, 0.028564f, 0.000000f, 0.070535f },
new[] { 6.455962f, 10.197294f, -18.338259f, 0.011902f, 0.000000f, 0.029390f },
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f } };
static readonly float[][] EXPECTED_A1Q1TVTA = {
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
new[] { 20.512094f, 10.197294f, -44.831028f, -3.105014f, 0.000000f, 1.615205f },
new[] { 19.891315f, 10.197294f, -44.507557f, -3.103898f, 0.000000f, 1.617350f },
new[] { 19.270782f, 10.197294f, -44.183617f, -3.102670f, 0.000000f, 1.619704f },
new[] { 18.650520f, 10.197294f, -43.859158f, -3.101315f, 0.000000f, 1.622296f },
new[] { 18.030558f, 10.197294f, -43.534126f, -3.099816f, 0.000000f, 1.625158f },
new[] { 17.410927f, 10.197294f, -43.208462f, -3.098152f, 0.000000f, 1.628329f },
new[] { 16.791668f, 10.197294f, -42.882092f, -3.096297f, 0.000000f, 1.631854f },
new[] { 16.172823f, 10.197294f, -42.554935f, -3.094221f, 0.000000f, 1.635786f },
new[] { 15.554445f, 10.197294f, -42.226898f, -3.091888f, 0.000000f, 1.640191f },
new[] { 14.936594f, 10.197294f, -41.897869f, -3.089255f, 0.000000f, 1.645145f },
new[] { 14.319340f, 10.197294f, -41.567722f, -3.086269f, 0.000000f, 1.650741f },
new[] { 13.702767f, 10.197294f, -41.236305f, -3.082862f, 0.000000f, 1.657094f },
new[] { 13.086977f, 10.197294f, -40.903435f, -3.078954f, 0.000000f, 1.664345f },
new[] { 12.472089f, 10.197294f, -40.568901f, -3.074442f, 0.000000f, 1.672665f },
new[] { 11.858250f, 10.197294f, -40.232449f, -3.069196f, 0.000000f, 1.682271f },
new[] { 11.245640f, 10.197294f, -39.893761f, -3.063048f, 0.000000f, 1.693439f },
new[] { 10.634483f, 10.197294f, -39.552460f, -3.055783f, 0.000000f, 1.706514f },
new[] { 10.025061f, 10.197294f, -39.208069f, -3.047112f, 0.000000f, 1.721948f },
new[] { 9.417730f, 10.197294f, -38.860004f, -3.036654f, 0.000000f, 1.740326f },
new[] { 8.812954f, 10.197294f, -38.507519f, -3.023880f, 0.000000f, 1.762429f },
new[] { 8.211343f, 10.197294f, -38.149658f, -3.008055f, 0.000000f, 1.789302f },
new[] { 7.613717f, 10.197294f, -37.785183f, -2.988129f, 0.000000f, 1.822385f },
new[] { 7.021209f, 10.197294f, -37.412445f, -2.962540f, 0.000000f, 1.863695f },
new[] { 6.435429f, 10.197294f, -37.029221f, -2.928901f, 0.000000f, 1.916126f },
new[] { 5.858753f, 10.197294f, -36.632427f, -2.883380f, 0.000000f, 1.983965f },
new[] { 5.294861f, 10.197294f, -36.217667f, -2.819461f, 0.000000f, 2.073798f },
new[] { 4.749827f, 10.197294f, -35.778419f, -2.725170f, 0.000000f, 2.196236f },
new[] { 4.234636f, 10.197294f, -35.304523f, -2.575954f, 0.000000f, 2.369486f },
new[] { 3.772264f, 10.197294f, -34.778965f, -2.311863f, 0.000000f, 2.627792f },
new[] { 3.428036f, 10.197294f, -34.169453f, -1.721137f, 0.000000f, 3.047571f },
new[] { 3.033963f, 10.197294f, -33.590916f, -1.970364f, 0.000000f, 2.892692f },
new[] { 2.656040f, 10.197294f, -33.001701f, -1.889616f, 0.000000f, 2.946074f },
new[] { 2.307335f, 10.197294f, -32.394737f, -1.743529f, 0.000000f, 3.034816f },
new[] { 2.021275f, 10.197294f, -31.755856f, -1.430300f, 0.000000f, 3.194408f },
new[] { 1.935164f, 10.197294f, -31.061172f, -0.430555f, 0.000000f, 3.473417f },
new[] { 1.847493f, 10.197294f, -30.366684f, -0.438351f, 0.000000f, 3.472441f },
new[] { 1.943488f, 10.197294f, -29.673298f, 0.479975f, 0.000000f, 3.466933f },
new[] { 2.170555f, 10.197294f, -29.011150f, 1.135336f, 0.000000f, 3.310742f },
new[] { 2.415170f, 10.197294f, -28.355282f, 1.223072f, 0.000000f, 3.279344f },
new[] { 2.677041f, 10.197294f, -27.706110f, 1.309357f, 0.000000f, 3.245857f },
new[] { 2.938912f, 10.197294f, -27.056938f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.200784f, 10.197294f, -26.407766f, 1.309357f, 0.000000f, 3.245856f },
new[] { 3.462655f, 10.197294f, -25.758595f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.724526f, 10.197294f, -25.109423f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.986398f, 10.197294f, -24.460251f, 1.309357f, 0.000000f, 3.245857f },
new[] { 4.248269f, 10.197294f, -23.811079f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.510140f, 10.197294f, -23.161907f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.772012f, 10.197294f, -22.512735f, 1.309357f, 0.000000f, 3.245856f },
new[] { 5.033883f, 10.197294f, -21.863564f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.295755f, 10.197294f, -21.214392f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.557627f, 10.197294f, -20.565220f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.819499f, 10.197294f, -19.916048f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.081370f, 10.197294f, -19.266876f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.322058f, 10.197294f, -18.670219f, 1.203439f, 0.000000f, 2.983284f },
new[] { 6.429398f, 10.197294f, -18.364454f, 0.320910f, 0.000000f, 1.058088f },
new[] { 6.437642f, 10.197294f, -18.355589f, 0.041219f, 0.000000f, 0.044324f },
new[] { 6.443481f, 10.197294f, -18.349310f, 0.029197f, 0.000000f, 0.031395f },
new[] { 6.447618f, 10.197294f, -18.344862f, 0.020681f, 0.000000f, 0.022238f },
new[] { 6.450547f, 10.197294f, -18.341711f, 0.014649f, 0.000000f, 0.015752f },
new[] { 6.452622f, 10.197294f, -18.339479f, 0.010377f, 0.000000f, 0.011157f },
new[] { 6.452622f, 10.197294f, -18.339479f, 0.000000f, 0.000000f, 0.000000f }, };
static readonly float[][] EXPECTED_A1Q2TVTA = {
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
new[] { 20.512094f, 10.197294f, -44.831028f, -3.105014f, 0.000000f, 1.615205f },
new[] { 19.891315f, 10.197294f, -44.507557f, -3.103898f, 0.000000f, 1.617350f },
new[] { 19.270782f, 10.197294f, -44.183617f, -3.102670f, 0.000000f, 1.619704f },
new[] { 18.650520f, 10.197294f, -43.859158f, -3.101315f, 0.000000f, 1.622296f },
new[] { 18.030558f, 10.197294f, -43.534126f, -3.099816f, 0.000000f, 1.625158f },
new[] { 17.410927f, 10.197294f, -43.208462f, -3.098152f, 0.000000f, 1.628329f },
new[] { 16.791668f, 10.197294f, -42.882092f, -3.096297f, 0.000000f, 1.631854f },
new[] { 16.172823f, 10.197294f, -42.554935f, -3.094221f, 0.000000f, 1.635786f },
new[] { 15.554445f, 10.197294f, -42.226898f, -3.091888f, 0.000000f, 1.640191f },
new[] { 14.936594f, 10.197294f, -41.897869f, -3.089255f, 0.000000f, 1.645145f },
new[] { 14.319340f, 10.197294f, -41.567722f, -3.086269f, 0.000000f, 1.650741f },
new[] { 13.702767f, 10.197294f, -41.236305f, -3.082862f, 0.000000f, 1.657094f },
new[] { 13.086977f, 10.197294f, -40.903435f, -3.078954f, 0.000000f, 1.664345f },
new[] { 12.472089f, 10.197294f, -40.568901f, -3.074442f, 0.000000f, 1.672665f },
new[] { 11.858250f, 10.197294f, -40.232449f, -3.069196f, 0.000000f, 1.682271f },
new[] { 11.245640f, 10.197294f, -39.893761f, -3.063048f, 0.000000f, 1.693439f },
new[] { 10.634483f, 10.197294f, -39.552460f, -3.055783f, 0.000000f, 1.706514f },
new[] { 10.025061f, 10.197294f, -39.208069f, -3.047112f, 0.000000f, 1.721948f },
new[] { 9.417730f, 10.197294f, -38.860004f, -3.036654f, 0.000000f, 1.740326f },
new[] { 8.812954f, 10.197294f, -38.507519f, -3.023880f, 0.000000f, 1.762429f },
new[] { 8.211343f, 10.197294f, -38.149658f, -3.008055f, 0.000000f, 1.789302f },
new[] { 7.613717f, 10.197294f, -37.785183f, -2.988129f, 0.000000f, 1.822385f },
new[] { 7.021209f, 10.197294f, -37.412445f, -2.962540f, 0.000000f, 1.863695f },
new[] { 6.435429f, 10.197294f, -37.029221f, -2.928901f, 0.000000f, 1.916126f },
new[] { 5.858753f, 10.197294f, -36.632427f, -2.883380f, 0.000000f, 1.983965f },
new[] { 5.294861f, 10.197294f, -36.217667f, -2.819461f, 0.000000f, 2.073798f },
new[] { 4.749827f, 10.197294f, -35.778419f, -2.725170f, 0.000000f, 2.196236f },
new[] { 4.234636f, 10.197294f, -35.304523f, -2.575954f, 0.000000f, 2.369486f },
new[] { 3.772264f, 10.197294f, -34.778965f, -2.311863f, 0.000000f, 2.627792f },
new[] { 3.428036f, 10.197294f, -34.169453f, -1.721137f, 0.000000f, 3.047571f },
new[] { 3.033963f, 10.197294f, -33.590916f, -1.970364f, 0.000000f, 2.892692f },
new[] { 2.656040f, 10.197294f, -33.001701f, -1.889616f, 0.000000f, 2.946074f },
new[] { 2.307335f, 10.197294f, -32.394737f, -1.743529f, 0.000000f, 3.034816f },
new[] { 2.021275f, 10.197294f, -31.755856f, -1.430300f, 0.000000f, 3.194408f },
new[] { 1.935164f, 10.197294f, -31.061172f, -0.430555f, 0.000000f, 3.473417f },
new[] { 1.847493f, 10.197294f, -30.366684f, -0.438351f, 0.000000f, 3.472441f },
new[] { 1.943488f, 10.197294f, -29.673298f, 0.479975f, 0.000000f, 3.466933f },
new[] { 2.170555f, 10.197294f, -29.011150f, 1.135336f, 0.000000f, 3.310742f },
new[] { 2.415170f, 10.197294f, -28.355282f, 1.223072f, 0.000000f, 3.279344f },
new[] { 2.677041f, 10.197294f, -27.706110f, 1.309357f, 0.000000f, 3.245857f },
new[] { 2.938912f, 10.197294f, -27.056938f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.200784f, 10.197294f, -26.407766f, 1.309357f, 0.000000f, 3.245856f },
new[] { 3.462655f, 10.197294f, -25.758595f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.724526f, 10.197294f, -25.109423f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.986398f, 10.197294f, -24.460251f, 1.309357f, 0.000000f, 3.245857f },
new[] { 4.248269f, 10.197294f, -23.811079f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.510140f, 10.197294f, -23.161907f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.772012f, 10.197294f, -22.512735f, 1.309357f, 0.000000f, 3.245856f },
new[] { 5.033883f, 10.197294f, -21.863564f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.295755f, 10.197294f, -21.214392f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.557627f, 10.197294f, -20.565220f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.819499f, 10.197294f, -19.916048f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.081370f, 10.197294f, -19.266876f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.305691f, 10.197294f, -18.710793f, 1.121604f, 0.000000f, 2.780417f },
new[] { 6.420408f, 10.197294f, -18.455353f, 0.541385f, 0.000000f, 1.188869f },
new[] { 6.450006f, 10.197294f, -18.371796f, 0.147987f, 0.000000f, 0.417789f },
new[] { 6.452239f, 10.197294f, -18.360790f, 0.011167f, 0.000000f, 0.055030f },
new[] { 6.453821f, 10.197294f, -18.352995f, 0.007909f, 0.000000f, 0.038981f },
new[] { 6.454941f, 10.197294f, -18.347473f, 0.005603f, 0.000000f, 0.027612f },
new[] { 6.455735f, 10.197294f, -18.343561f, 0.003969f, 0.000000f, 0.019560f },
new[] { 6.455735f, 10.197294f, -18.343561f, 0.000000f, 0.000000f, 0.000000f }, };
static readonly float[][] EXPECTED_A1Q3TVTA = {
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
new[] { 20.512094f, 10.197294f, -44.831028f, -3.105014f, 0.000000f, 1.615205f },
new[] { 19.891315f, 10.197294f, -44.507557f, -3.103898f, 0.000000f, 1.617350f },
new[] { 19.270782f, 10.197294f, -44.183617f, -3.102670f, 0.000000f, 1.619704f },
new[] { 18.650520f, 10.197294f, -43.859158f, -3.101315f, 0.000000f, 1.622296f },
new[] { 18.030558f, 10.197294f, -43.534126f, -3.099816f, 0.000000f, 1.625158f },
new[] { 17.410927f, 10.197294f, -43.208462f, -3.098152f, 0.000000f, 1.628329f },
new[] { 16.791668f, 10.197294f, -42.882092f, -3.096297f, 0.000000f, 1.631854f },
new[] { 16.172823f, 10.197294f, -42.554935f, -3.094221f, 0.000000f, 1.635786f },
new[] { 15.554445f, 10.197294f, -42.226898f, -3.091888f, 0.000000f, 1.640191f },
new[] { 14.936594f, 10.197294f, -41.897869f, -3.089255f, 0.000000f, 1.645145f },
new[] { 14.319340f, 10.197294f, -41.567722f, -3.086269f, 0.000000f, 1.650741f },
new[] { 13.702767f, 10.197294f, -41.236305f, -3.082862f, 0.000000f, 1.657094f },
new[] { 13.086977f, 10.197294f, -40.903435f, -3.078954f, 0.000000f, 1.664345f },
new[] { 12.472089f, 10.197294f, -40.568901f, -3.074442f, 0.000000f, 1.672665f },
new[] { 11.858250f, 10.197294f, -40.232449f, -3.069196f, 0.000000f, 1.682271f },
new[] { 11.245640f, 10.197294f, -39.893761f, -3.063048f, 0.000000f, 1.693439f },
new[] { 10.634483f, 10.197294f, -39.552460f, -3.055783f, 0.000000f, 1.706514f },
new[] { 10.025061f, 10.197294f, -39.208069f, -3.047112f, 0.000000f, 1.721948f },
new[] { 9.417730f, 10.197294f, -38.860004f, -3.036654f, 0.000000f, 1.740326f },
new[] { 8.812954f, 10.197294f, -38.507519f, -3.023880f, 0.000000f, 1.762429f },
new[] { 8.211343f, 10.197294f, -38.149658f, -3.008055f, 0.000000f, 1.789302f },
new[] { 7.613717f, 10.197294f, -37.785183f, -2.988129f, 0.000000f, 1.822385f },
new[] { 7.021209f, 10.197294f, -37.412445f, -2.962540f, 0.000000f, 1.863695f },
new[] { 6.435429f, 10.197294f, -37.029221f, -2.928901f, 0.000000f, 1.916126f },
new[] { 5.858753f, 10.197294f, -36.632427f, -2.883380f, 0.000000f, 1.983965f },
new[] { 5.294861f, 10.197294f, -36.217667f, -2.819461f, 0.000000f, 2.073798f },
new[] { 4.749827f, 10.197294f, -35.778419f, -2.725170f, 0.000000f, 2.196236f },
new[] { 4.234636f, 10.197294f, -35.304523f, -2.575954f, 0.000000f, 2.369486f },
new[] { 3.772264f, 10.197294f, -34.778965f, -2.311863f, 0.000000f, 2.627792f },
new[] { 3.428036f, 10.197294f, -34.169453f, -1.721137f, 0.000000f, 3.047571f },
new[] { 3.033963f, 10.197294f, -33.590916f, -1.970364f, 0.000000f, 2.892692f },
new[] { 2.656040f, 10.197294f, -33.001701f, -1.889616f, 0.000000f, 2.946074f },
new[] { 2.307335f, 10.197294f, -32.394737f, -1.743529f, 0.000000f, 3.034816f },
new[] { 2.021275f, 10.197294f, -31.755856f, -1.430300f, 0.000000f, 3.194408f },
new[] { 1.935164f, 10.197294f, -31.061172f, -0.430555f, 0.000000f, 3.473417f },
new[] { 1.847493f, 10.197294f, -30.366684f, -0.438351f, 0.000000f, 3.472441f },
new[] { 1.943488f, 10.197294f, -29.673298f, 0.479975f, 0.000000f, 3.466933f },
new[] { 2.170555f, 10.197294f, -29.011150f, 1.135336f, 0.000000f, 3.310742f },
new[] { 2.415170f, 10.197294f, -28.355282f, 1.223072f, 0.000000f, 3.279344f },
new[] { 2.677041f, 10.197294f, -27.706110f, 1.309357f, 0.000000f, 3.245857f },
new[] { 2.938912f, 10.197294f, -27.056938f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.200784f, 10.197294f, -26.407766f, 1.309357f, 0.000000f, 3.245856f },
new[] { 3.462655f, 10.197294f, -25.758595f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.724526f, 10.197294f, -25.109423f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.986398f, 10.197294f, -24.460251f, 1.309357f, 0.000000f, 3.245857f },
new[] { 4.248269f, 10.197294f, -23.811079f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.510140f, 10.197294f, -23.161907f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.772012f, 10.197294f, -22.512735f, 1.309357f, 0.000000f, 3.245856f },
new[] { 5.033883f, 10.197294f, -21.863564f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.295755f, 10.197294f, -21.214392f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.557627f, 10.197294f, -20.565220f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.819499f, 10.197294f, -19.916048f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.081370f, 10.197294f, -19.266876f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.297070f, 10.197294f, -18.723810f, 1.078498f, 0.000000f, 2.715334f },
new[] { 6.397289f, 10.197294f, -18.479179f, 0.456429f, 0.000000f, 1.107728f },
new[] { 6.439916f, 10.197294f, -18.384853f, 0.213137f, 0.000000f, 0.471627f },
new[] { 6.454712f, 10.197294f, -18.342505f, 0.073981f, 0.000000f, 0.211745f },
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f } };
static readonly float[][] EXPECTED_A1Q3TVTAS = {
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
new[] { 20.512094f, 10.197294f, -44.831028f, -3.105014f, 0.000000f, 1.615205f },
new[] { 19.891315f, 10.197294f, -44.507557f, -3.103898f, 0.000000f, 1.617350f },
new[] { 19.270782f, 10.197294f, -44.183617f, -3.102670f, 0.000000f, 1.619704f },
new[] { 18.650520f, 10.197294f, -43.859158f, -3.101315f, 0.000000f, 1.622296f },
new[] { 18.030558f, 10.197294f, -43.534126f, -3.099816f, 0.000000f, 1.625158f },
new[] { 17.410927f, 10.197294f, -43.208462f, -3.098152f, 0.000000f, 1.628329f },
new[] { 16.791668f, 10.197294f, -42.882092f, -3.096297f, 0.000000f, 1.631854f },
new[] { 16.172823f, 10.197294f, -42.554935f, -3.094221f, 0.000000f, 1.635786f },
new[] { 15.554445f, 10.197294f, -42.226898f, -3.091888f, 0.000000f, 1.640191f },
new[] { 14.936594f, 10.197294f, -41.897869f, -3.089255f, 0.000000f, 1.645145f },
new[] { 14.319340f, 10.197294f, -41.567722f, -3.086269f, 0.000000f, 1.650741f },
new[] { 13.702767f, 10.197294f, -41.236305f, -3.082862f, 0.000000f, 1.657094f },
new[] { 13.086977f, 10.197294f, -40.903435f, -3.078954f, 0.000000f, 1.664345f },
new[] { 12.472089f, 10.197294f, -40.568901f, -3.074442f, 0.000000f, 1.672665f },
new[] { 11.858250f, 10.197294f, -40.232449f, -3.069196f, 0.000000f, 1.682271f },
new[] { 11.245640f, 10.197294f, -39.893761f, -3.063048f, 0.000000f, 1.693439f },
new[] { 10.634483f, 10.197294f, -39.552460f, -3.055783f, 0.000000f, 1.706514f },
new[] { 10.025061f, 10.197294f, -39.208069f, -3.047112f, 0.000000f, 1.721948f },
new[] { 9.417730f, 10.197294f, -38.860004f, -3.036654f, 0.000000f, 1.740326f },
new[] { 8.812954f, 10.197294f, -38.507519f, -3.023880f, 0.000000f, 1.762429f },
new[] { 8.211343f, 10.197294f, -38.149658f, -3.008055f, 0.000000f, 1.789302f },
new[] { 7.613717f, 10.197294f, -37.785183f, -2.988129f, 0.000000f, 1.822385f },
new[] { 7.021209f, 10.197294f, -37.412445f, -2.962540f, 0.000000f, 1.863695f },
new[] { 6.435429f, 10.197294f, -37.029221f, -2.928901f, 0.000000f, 1.916126f },
new[] { 5.858753f, 10.197294f, -36.632427f, -2.883380f, 0.000000f, 1.983965f },
new[] { 5.294861f, 10.197294f, -36.217667f, -2.819461f, 0.000000f, 2.073798f },
new[] { 4.749827f, 10.197294f, -35.778419f, -2.725170f, 0.000000f, 2.196236f },
new[] { 4.234636f, 10.197294f, -35.304523f, -2.575954f, 0.000000f, 2.369486f },
new[] { 3.772264f, 10.197294f, -34.778965f, -2.311863f, 0.000000f, 2.627792f },
new[] { 3.428036f, 10.197294f, -34.169453f, -1.721137f, 0.000000f, 3.047571f },
new[] { 3.033963f, 10.197294f, -33.590916f, -1.970364f, 0.000000f, 2.892692f },
new[] { 2.656040f, 10.197294f, -33.001701f, -1.889616f, 0.000000f, 2.946074f },
new[] { 2.307335f, 10.197294f, -32.394737f, -1.743529f, 0.000000f, 3.034816f },
new[] { 2.021275f, 10.197294f, -31.755856f, -1.430300f, 0.000000f, 3.194408f },
new[] { 1.935164f, 10.197294f, -31.061172f, -0.430555f, 0.000000f, 3.473417f },
new[] { 1.847493f, 10.197294f, -30.366684f, -0.438351f, 0.000000f, 3.472441f },
new[] { 1.943488f, 10.197294f, -29.673298f, 0.479975f, 0.000000f, 3.466933f },
new[] { 2.170555f, 10.197294f, -29.011150f, 1.135336f, 0.000000f, 3.310742f },
new[] { 2.415170f, 10.197294f, -28.355282f, 1.223072f, 0.000000f, 3.279344f },
new[] { 2.677041f, 10.197294f, -27.706110f, 1.309357f, 0.000000f, 3.245857f },
new[] { 2.938912f, 10.197294f, -27.056938f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.200784f, 10.197294f, -26.407766f, 1.309357f, 0.000000f, 3.245856f },
new[] { 3.462655f, 10.197294f, -25.758595f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.724526f, 10.197294f, -25.109423f, 1.309357f, 0.000000f, 3.245857f },
new[] { 3.986398f, 10.197294f, -24.460251f, 1.309357f, 0.000000f, 3.245857f },
new[] { 4.248269f, 10.197294f, -23.811079f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.510140f, 10.197294f, -23.161907f, 1.309357f, 0.000000f, 3.245856f },
new[] { 4.772012f, 10.197294f, -22.512735f, 1.309357f, 0.000000f, 3.245856f },
new[] { 5.033883f, 10.197294f, -21.863564f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.295755f, 10.197294f, -21.214392f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.557627f, 10.197294f, -20.565220f, 1.309358f, 0.000000f, 3.245856f },
new[] { 5.819499f, 10.197294f, -19.916048f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.081370f, 10.197294f, -19.266876f, 1.309358f, 0.000000f, 3.245856f },
new[] { 6.297070f, 10.197294f, -18.723810f, 1.078498f, 0.000000f, 2.715334f },
new[] { 6.397289f, 10.197294f, -18.479179f, 0.456429f, 0.000000f, 1.107728f },
new[] { 6.439916f, 10.197294f, -18.384853f, 0.213137f, 0.000000f, 0.471627f },
new[] { 6.454712f, 10.197294f, -18.342505f, 0.073981f, 0.000000f, 0.211745f },
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f } };
[Test]
public void testAgent1Quality0TVTA() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q0TVTA.Length; i++) {
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TVTA[i][2]).Within(0.001f));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q0TVTA[i][3]).Within(0.001f));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q0TVTA[i][4]).Within(0.001f));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q0TVTA[i][5]).Within(0.001f));
}
}
}
[Test]
public void testAgent1Quality0TVT() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q0TVT.Length; i++) {
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TVT[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TVT[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TVT[i][2]).Within(0.001f));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q0TVT[i][3]).Within(0.001f));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q0TVT[i][4]).Within(0.001f));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q0TVT[i][5]).Within(0.001f));
}
}
}
[Test]
public void testAgent1Quality0TV() {
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q0TV.Length; i++) {
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TV[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TV[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TV[i][2]).Within(0.001f));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q0TV[i][3]).Within(0.001f));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q0TV[i][4]).Within(0.001f));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q0TV[i][5]).Within(0.001f));
}
}
}
[Test]
public void testAgent1Quality0T() {
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q0T.Length; i++) {
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0T[i][0]).Within(0.001));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0T[i][1]).Within(0.001));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0T[i][2]).Within(0.001));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q0T[i][3]).Within(0.001));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q0T[i][4]).Within(0.001));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q0T[i][5]).Within(0.001));
}
}
}
[Test]
public void testAgent1Quality1TVTA() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 1, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q1TVTA.Length; i++) {
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q1TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q1TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q1TVTA[i][2]).Within(0.001f));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q1TVTA[i][3]).Within(0.001f));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q1TVTA[i][4]).Within(0.001f));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q1TVTA[i][5]).Within(0.001f));
}
}
}
[Test]
public void testAgent1Quality2TVTA() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++) {
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2TVTA[i][2]).Within(0.001f));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][3]).Within(0.001f));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2TVTA[i][4]).Within(0.001f));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2TVTA[i][5]).Within(0.001f));
}
}
}
[Test]
public void testAgent1Quality3TVTA() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++) {
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][2]).Within(0.001f));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][3]).Within(0.001f));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][4]).Within(0.001f));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][5]).Within(0.001f));
}
}
}
[Test]
public void testAgent1Quality3TVTAS() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION;
addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q3TVTAS.Length; i++) {
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][2]).Within(0.001f));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][3]).Within(0.001f));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][4]).Within(0.001f));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][5]).Within(0.001f));
}
}
}
}

View File

@ -0,0 +1,363 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Threading;
using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class Crowd4Test : AbstractCrowdTest {
static readonly float[][] EXPECTED_A1Q2TVTA = {
new[] { 23.275612f, 10.197294f, -46.233074f, 0.061640f, 0.000000f, 0.073828f },
new[] { 23.350517f, 10.197294f, -46.304905f, 0.030557f, 0.000000f, 0.118703f },
new[] { 23.347885f, 10.197294f, -46.331837f, -0.024102f, 0.000000f, -0.093108f },
new[] { 23.338102f, 10.197294f, -46.372726f, -0.048912f, 0.000000f, -0.204439f },
new[] { 23.158630f, 10.197294f, -46.386150f, -0.897364f, 0.000000f, -0.067119f },
new[] { 22.750568f, 10.197294f, -46.389927f, -2.040308f, 0.000000f, -0.018881f },
new[] { 22.173302f, 10.197294f, -46.272018f, -2.886330f, 0.000000f, 0.589539f },
new[] { 21.579432f, 10.197294f, -46.052692f, -2.969354f, 0.000000f, 1.096630f },
new[] { 20.957502f, 10.197294f, -45.761692f, -3.109650f, 0.000000f, 1.455011f },
new[] { 20.365976f, 10.197294f, -45.476425f, -2.957630f, 0.000000f, 1.426327f },
new[] { 19.775288f, 10.197294f, -45.189430f, -2.953444f, 0.000000f, 1.434977f },
new[] { 19.185482f, 10.197294f, -44.900627f, -2.949032f, 0.000000f, 1.444020f },
new[] { 18.596607f, 10.197294f, -44.609928f, -2.944376f, 0.000000f, 1.453491f },
new[] { 18.008717f, 10.197294f, -44.317242f, -2.939449f, 0.000000f, 1.463430f },
new[] { 17.421871f, 10.197294f, -44.022465f, -2.934223f, 0.000000f, 1.473880f },
new[] { 16.836138f, 10.197294f, -43.725487f, -2.928665f, 0.000000f, 1.484893f },
new[] { 16.237518f, 10.197294f, -43.413433f, -2.993098f, 0.000000f, 1.560270f },
new[] { 15.662568f, 10.197294f, -43.098225f, -2.874751f, 0.000000f, 1.576043f },
new[] { 15.094946f, 10.197294f, -42.777035f, -2.838109f, 0.000000f, 1.605944f },
new[] { 14.528272f, 10.197294f, -42.454178f, -2.833370f, 0.000000f, 1.614288f },
new[] { 13.962630f, 10.197294f, -42.129513f, -2.828207f, 0.000000f, 1.623317f },
new[] { 13.391782f, 10.197294f, -41.806934f, -2.854241f, 0.000000f, 1.612890f },
new[] { 12.799945f, 10.197294f, -41.482201f, -2.959186f, 0.000000f, 1.623668f },
new[] { 12.197092f, 10.197294f, -41.139999f, -3.014262f, 0.000000f, 1.711000f },
new[] { 11.594053f, 10.197294f, -40.811634f, -3.015195f, 0.000000f, 1.641821f },
new[] { 10.996500f, 10.197294f, -40.460262f, -2.987766f, 0.000000f, 1.756859f },
new[] { 10.401724f, 10.197294f, -40.104210f, -2.973881f, 0.000000f, 1.780260f },
new[] { 9.810114f, 10.197294f, -39.742920f, -2.958048f, 0.000000f, 1.806445f },
new[] { 9.222152f, 10.197294f, -39.375725f, -2.939809f, 0.000000f, 1.835979f },
new[] { 8.638441f, 10.197294f, -39.001808f, -2.918552f, 0.000000f, 1.869585f },
new[] { 8.059751f, 10.197294f, -38.620167f, -2.893450f, 0.000000f, 1.908203f },
new[] { 7.536088f, 10.197294f, -38.213108f, -2.618314f, 0.000000f, 2.035301f },
new[] { 7.008043f, 10.197294f, -37.798481f, -2.640225f, 0.000000f, 2.073141f },
new[] { 6.484523f, 10.197294f, -37.378155f, -2.617601f, 0.000000f, 2.101635f },
new[] { 5.957591f, 10.197294f, -36.962112f, -2.634658f, 0.000000f, 2.080211f },
new[] { 5.458399f, 10.197294f, -36.525387f, -2.495958f, 0.000000f, 2.183624f },
new[] { 4.952830f, 10.197294f, -36.083633f, -2.527847f, 0.000000f, 2.208776f },
new[] { 4.445582f, 10.197294f, -35.638187f, -2.536238f, 0.000000f, 2.227235f },
new[] { 3.997866f, 10.197294f, -35.100090f, -2.238582f, 0.000000f, 2.690493f },
new[] { 3.604921f, 10.197294f, -34.520786f, -1.964725f, 0.000000f, 2.896524f },
new[] { 3.216267f, 10.197294f, -33.938595f, -1.943270f, 0.000000f, 2.910962f },
new[] { 2.839496f, 10.197294f, -33.348644f, -1.883856f, 0.000000f, 2.949760f },
new[] { 2.482899f, 10.197294f, -32.746284f, -1.782983f, 0.000000f, 3.011806f },
new[] { 2.165045f, 10.197294f, -32.122612f, -1.589272f, 0.000000f, 3.118367f },
new[] { 1.968905f, 10.197294f, -31.542366f, -0.980699f, 0.000000f, 2.901230f },
new[] { 1.830858f, 10.197294f, -30.899487f, -0.690238f, 0.000000f, 3.214399f },
new[] { 1.790072f, 10.197294f, -30.240873f, -0.203930f, 0.000000f, 3.293068f },
new[] { 1.860591f, 10.197294f, -29.561634f, 0.352598f, 0.000000f, 3.396194f },
new[] { 2.125831f, 10.197294f, -28.913832f, 1.326197f, 0.000000f, 3.239013f },
new[] { 2.391070f, 10.197294f, -28.266029f, 1.326197f, 0.000000f, 3.239013f },
new[] { 2.656309f, 10.197294f, -27.618227f, 1.326197f, 0.000000f, 3.239013f },
new[] { 2.921549f, 10.197294f, -26.970425f, 1.326197f, 0.000000f, 3.239013f },
new[] { 3.186788f, 10.197294f, -26.322622f, 1.326197f, 0.000000f, 3.239013f },
new[] { 3.452027f, 10.197294f, -25.674820f, 1.326197f, 0.000000f, 3.239013f },
new[] { 3.717267f, 10.197294f, -25.027018f, 1.326197f, 0.000000f, 3.239013f },
new[] { 3.982506f, 10.197294f, -24.379215f, 1.326197f, 0.000000f, 3.239013f },
new[] { 4.247745f, 10.197294f, -23.731413f, 1.326197f, 0.000000f, 3.239013f },
new[] { 4.512984f, 10.197294f, -23.083611f, 1.326197f, 0.000000f, 3.239013f },
new[] { 4.778224f, 10.197294f, -22.435808f, 1.326197f, 0.000000f, 3.239013f },
new[] { 5.043463f, 10.197294f, -21.788006f, 1.326197f, 0.000000f, 3.239013f },
new[] { 5.308702f, 10.197294f, -21.140203f, 1.326197f, 0.000000f, 3.239013f },
new[] { 5.573941f, 10.197294f, -20.492401f, 1.326197f, 0.000000f, 3.239013f },
new[] { 5.839180f, 10.197294f, -19.844599f, 1.326197f, 0.000000f, 3.239012f },
new[] { 6.173420f, 10.197294f, -19.342373f, 1.706390f, 0.000000f, 2.813494f },
new[] { 6.501637f, 10.197294f, -19.076807f, 1.451270f, 0.000000f, 2.463549f },
new[] { 6.803561f, 10.197294f, -18.729990f, 1.002026f, 0.000000f, 2.679962f },
new[] { 6.799414f, 10.197294f, -18.366598f, -0.993883f, 0.000000f, 1.390529f },
new[] { 6.990967f, 10.197294f, -18.240343f, 2.109758f, 0.000000f, 0.180499f },
new[] { 7.157530f, 10.197294f, -18.320511f, 0.758936f, 0.000000f, 0.228570f },
new[] { 7.349619f, 10.197294f, -18.347782f, 0.926001f, 0.000000f, 0.008640f },
new[] { 7.448954f, 10.197294f, -18.390112f, 0.496676f, 0.000000f, -0.211644f },
new[] { 7.524421f, 10.197294f, -18.477961f, 0.377335f, 0.000000f, -0.439247f },
new[] { 7.628986f, 10.197294f, -18.484478f, 0.522823f, 0.000000f, -0.032589f },
new[] { 7.751089f, 10.197294f, -18.466608f, 0.610513f, 0.000000f, 0.089349f },
new[] { 7.891870f, 10.197294f, -18.490461f, 0.703905f, 0.000000f, -0.119263f },
new[] { 8.032493f, 10.197294f, -18.515228f, 0.703115f, 0.000000f, -0.123836f },
new[] { 8.172967f, 10.1862135f, -18.540827f, 0.702371f, 0.000000f, -0.127992f },
new[] { 8.182732f, 10.1862135f, -18.626057f, 0.048824f, 0.000000f, -0.426151f },
new[] { 8.165586f, 10.188671f, -18.846558f, -0.085729f, 0.000000f, -1.102502f },
new[] { 8.048562f, 10.197294f, -18.763044f, -0.785845f, 0.000000f, 1.028553f },
new[] { 7.802718f, 10.197294f, -18.943884f, -1.229222f, 0.000000f, -0.904202f },
new[] { 7.655485f, 10.197294f, -19.137533f, -0.736162f, 0.000000f, -0.968250f },
new[] { 7.542105f, 10.197294f, -19.244339f, -0.566899f, 0.000000f, -0.534033f },
new[] { 7.427822f, 10.197294f, -19.031147f, -0.572634f, 0.000000f, 1.497748f },
new[] { 7.339287f, 10.197294f, -18.757444f, -0.442677f, 0.000000f, 1.368514f },
new[] { 7.255370f, 10.197294f, -18.471191f, -0.419582f, 0.000000f, 1.431268f },
new[] { 7.153419f, 10.197294f, -18.314838f, -0.509755f, 0.000000f, 0.781767f },
new[] { 7.129016f, 10.197294f, -18.194052f, -0.122013f, 0.000000f, 0.603930f },
new[] { 7.099013f, 10.197294f, -18.064573f, -0.150015f, 0.000000f, 0.647393f },
new[] { 7.085871f, 10.197294f, -17.946556f, -0.065714f, 0.000000f, 0.590090f },
new[] { 7.067722f, 10.197294f, -17.801628f, -0.090745f, 0.000000f, 0.724639f },
new[] { 7.048226f, 10.197294f, -17.673695f, -0.097479f, 0.000000f, 0.639666f } };
static readonly float[][] EXPECTED_A1Q2TVTAS = {
new[] { 23.253357f, 10.197294f, -46.279934f, 0.074597f, 0.000000f, -0.017069f },
new[] { 23.336805f, 10.197294f, -46.374985f, 0.119401f, 0.000000f, -0.031009f },
new[] { 23.351542f, 10.197294f, -46.410728f, 0.053426f, 0.000000f, -0.093556f },
new[] { 23.362417f, 10.197294f, -46.429638f, 0.054377f, 0.000000f, -0.094549f },
new[] { 23.216995f, 10.197294f, -46.446442f, -0.727108f, 0.000000f, -0.084018f },
new[] { 22.902132f, 10.197294f, -46.426094f, -1.574317f, 0.000000f, 0.101733f },
new[] { 22.491152f, 10.197294f, -46.376247f, -2.054897f, 0.000000f, 0.249239f },
new[] { 22.058567f, 10.197294f, -46.289536f, -2.162925f, 0.000000f, 0.433569f },
new[] { 21.588501f, 10.197294f, -46.183846f, -2.350327f, 0.000000f, 0.528449f },
new[] { 21.116070f, 10.197294f, -46.072765f, -2.362152f, 0.000000f, 0.555402f },
new[] { 20.642447f, 10.197294f, -45.954391f, -2.368116f, 0.000000f, 0.591867f },
new[] { 20.168785f, 10.197294f, -45.827782f, -2.368307f, 0.000000f, 0.633050f },
new[] { 19.695480f, 10.197294f, -45.692959f, -2.366527f, 0.000000f, 0.674118f },
new[] { 19.221388f, 10.197294f, -45.549664f, -2.370462f, 0.000000f, 0.716482f },
new[] { 18.747715f, 10.197294f, -45.401028f, -2.368369f, 0.000000f, 0.743186f },
new[] { 18.274532f, 10.197294f, -45.247219f, -2.365911f, 0.000000f, 0.769049f },
new[] { 17.801916f, 10.197294f, -45.088371f, -2.363083f, 0.000000f, 0.794239f },
new[] { 17.329977f, 10.197294f, -44.924515f, -2.359692f, 0.000000f, 0.819281f },
new[] { 16.858923f, 10.197294f, -44.755489f, -2.355273f, 0.000000f, 0.845136f },
new[] { 16.389112f, 10.197294f, -44.580818f, -2.349057f, 0.000000f, 0.873354f },
new[] { 15.904601f, 10.197294f, -44.397808f, -2.422558f, 0.000000f, 0.915057f },
new[] { 15.422479f, 10.197294f, -44.204182f, -2.410610f, 0.000000f, 0.968130f },
new[] { 14.943066f, 10.197294f, -44.000332f, -2.397065f, 0.000000f, 1.019240f },
new[] { 14.466599f, 10.197294f, -43.786751f, -2.382330f, 0.000000f, 1.067903f },
new[] { 13.993264f, 10.197294f, -43.563339f, -2.366675f, 0.000000f, 1.117066f },
new[] { 13.539879f, 10.197294f, -43.333740f, -2.266925f, 0.000000f, 1.147989f },
new[] { 13.089582f, 10.197294f, -43.096500f, -2.251482f, 0.000000f, 1.186200f },
new[] { 12.642441f, 10.197294f, -42.852135f, -2.235710f, 0.000000f, 1.221827f },
new[] { 12.198518f, 10.197294f, -42.601135f, -2.219616f, 0.000000f, 1.254998f },
new[] { 11.757890f, 10.197294f, -42.343952f, -2.203139f, 0.000000f, 1.285906f },
new[] { 11.320659f, 10.197294f, -42.080994f, -2.186156f, 0.000000f, 1.314789f },
new[] { 10.886962f, 10.197294f, -41.812611f, -2.168484f, 0.000000f, 1.341919f },
new[] { 10.456985f, 10.197294f, -41.539093f, -2.149882f, 0.000000f, 1.367595f },
new[] { 10.030977f, 10.197294f, -41.260666f, -2.130040f, 0.000000f, 1.392133f },
new[] { 9.609450f, 10.197294f, -40.977509f, -2.107634f, 0.000000f, 1.415778f },
new[] { 9.193022f, 10.197294f, -40.690060f, -2.082145f, 0.000000f, 1.437238f },
new[] { 8.782290f, 10.197294f, -40.398022f, -2.053656f, 0.000000f, 1.460193f },
new[] { 8.378143f, 10.197294f, -40.101349f, -2.020734f, 0.000000f, 1.483373f },
new[] { 7.981577f, 10.197294f, -39.799294f, -1.982833f, 0.000000f, 1.510276f },
new[] { 7.592501f, 10.197294f, -39.491383f, -1.945382f, 0.000000f, 1.539564f },
new[] { 7.235472f, 10.197294f, -39.191612f, -1.785145f, 0.000000f, 1.498847f },
new[] { 6.856690f, 10.197294f, -38.869774f, -1.893909f, 0.000000f, 1.609192f },
new[] { 6.501227f, 10.197294f, -38.570526f, -1.777313f, 0.000000f, 1.496231f },
new[] { 6.224775f, 10.197294f, -38.301174f, -1.382261f, 0.000000f, 1.346764f },
new[] { 5.929498f, 10.197294f, -37.995701f, -1.476386f, 0.000000f, 1.527363f },
new[] { 5.629647f, 10.197294f, -37.639618f, -1.499255f, 0.000000f, 1.780412f },
new[] { 5.328491f, 10.197294f, -37.296764f, -1.505784f, 0.000000f, 1.714271f },
new[] { 5.023998f, 10.197294f, -36.908840f, -1.522466f, 0.000000f, 1.939620f },
new[] { 4.744634f, 10.197294f, -36.502831f, -1.396819f, 0.000000f, 2.030053f },
new[] { 4.492529f, 10.197294f, -36.078068f, -1.260521f, 0.000000f, 2.123809f },
new[] { 4.239073f, 10.197294f, -35.631050f, -1.267281f, 0.000000f, 2.235098f },
new[] { 3.989349f, 10.197294f, -35.178169f, -1.248621f, 0.000000f, 2.264413f },
new[] { 3.736778f, 10.197294f, -34.723938f, -1.262857f, 0.000000f, 2.271152f },
new[] { 3.491473f, 10.197294f, -34.264828f, -1.226526f, 0.000000f, 2.295557f },
new[] { 3.177553f, 10.197294f, -33.848518f, -1.569597f, 0.000000f, 2.081553f },
new[] { 2.866278f, 10.197294f, -33.427658f, -1.556375f, 0.000000f, 2.104302f },
new[] { 2.566121f, 10.197294f, -32.995960f, -1.500786f, 0.000000f, 2.158491f },
new[] { 2.291139f, 10.197294f, -32.544334f, -1.374910f, 0.000000f, 2.258132f },
new[] { 2.066509f, 10.197294f, -32.063156f, -1.123153f, 0.000000f, 2.405891f },
new[] { 1.949471f, 10.197294f, -31.544592f, -0.585186f, 0.000000f, 2.592823f },
new[] { 1.841830f, 10.197294f, -31.020411f, -0.538206f, 0.000000f, 2.620903f },
new[] { 1.804908f, 10.197294f, -30.484823f, -0.184613f, 0.000000f, 2.677938f },
new[] { 1.848811f, 10.197294f, -29.937574f, 0.219515f, 0.000000f, 2.736246f },
new[] { 1.946300f, 10.197294f, -29.417610f, 0.487449f, 0.000000f, 2.599819f },
new[] { 2.111085f, 10.197294f, -28.884066f, 0.823926f, 0.000000f, 2.667721f },
new[] { 2.288109f, 10.197294f, -28.358007f, 0.885119f, 0.000000f, 2.630293f },
new[] { 2.499196f, 10.197294f, -27.812208f, 1.055435f, 0.000000f, 2.728995f },
new[] { 2.709832f, 10.197294f, -27.265604f, 1.053180f, 0.000000f, 2.733017f },
new[] { 2.920712f, 10.197294f, -26.718000f, 1.054401f, 0.000000f, 2.738015f },
new[] { 3.166102f, 10.197294f, -26.183590f, 1.226950f, 0.000000f, 2.672051f },
new[] { 3.409404f, 10.197294f, -25.647873f, 1.216508f, 0.000000f, 2.678585f },
new[] { 3.604466f, 10.197294f, -25.169361f, 0.975311f, 0.000000f, 2.392557f },
new[] { 3.799465f, 10.197294f, -24.688942f, 0.974994f, 0.000000f, 2.402100f },
new[] { 4.008917f, 10.197294f, -24.179743f, 1.047259f, 0.000000f, 2.545998f },
new[] { 4.254181f, 10.197294f, -23.667099f, 1.226322f, 0.000000f, 2.563215f },
new[] { 4.411407f, 10.197294f, -23.188858f, 0.786131f, 0.000000f, 2.391205f },
new[] { 4.630627f, 10.197294f, -22.706358f, 1.096096f, 0.000000f, 2.412497f },
new[] { 4.733336f, 10.197294f, -22.348816f, 0.513548f, 0.000000f, 1.787710f },
new[] { 4.886934f, 10.197294f, -22.007650f, 0.767987f, 0.000000f, 1.705831f },
new[] { 4.930230f, 10.197294f, -21.656166f, 0.216483f, 0.000000f, 1.757425f },
new[] { 5.044151f, 10.197294f, -21.343338f, 0.569606f, 0.000000f, 1.564138f },
new[] { 5.169365f, 10.197294f, -21.074562f, 0.626069f, 0.000000f, 1.343884f },
new[] { 5.241940f, 10.197294f, -20.714607f, 0.362874f, 0.000000f, 1.799778f },
new[] { 5.323618f, 10.197294f, -20.576834f, 0.408392f, 0.000000f, 0.688868f },
new[] { 5.316272f, 10.197294f, -20.384502f, -0.036732f, 0.000000f, 0.961652f },
new[] { 5.301373f, 10.197294f, -20.182859f, -0.074500f, 0.000000f, 1.008217f },
new[] { 5.295803f, 10.197294f, -19.982578f, -0.027847f, 0.000000f, 1.001405f },
new[] { 5.286469f, 10.197294f, -19.742945f, -0.046671f, 0.000000f, 1.198163f },
new[] { 5.275438f, 10.197294f, -19.603636f, -0.055155f, 0.000000f, 0.696540f },
new[] { 5.288567f, 10.197294f, -19.521788f, 0.065643f, 0.000000f, 0.409236f },
new[] { 5.440337f, 10.197294f, -19.479048f, 0.758851f, 0.000000f, 0.213696f },
new[] { 5.633360f, 10.197294f, -19.464310f, 0.965118f, 0.000000f, 0.073694f },
new[] { 5.825089f, 10.197294f, -19.461432f, 0.958646f, 0.000000f, 0.014389f },
new[] { 5.962496f, 10.197294f, -19.404169f, 0.687035f, 0.000000f, 0.286309f },
new[] { 6.051316f, 10.197294f, -19.417604f, 0.444099f, 0.000000f, -0.067175f },
new[] { 6.134418f, 10.197294f, -19.466887f, 0.415510f, 0.000000f, -0.246414f },
new[] { 6.195343f, 10.197294f, -19.520611f, 0.304628f, 0.000000f, -0.268624f },
new[] { 6.233473f, 10.197294f, -19.589584f, 0.190648f, 0.000000f, -0.344869f },
new[] { 6.143032f, 10.197294f, -19.644495f, -0.452208f, 0.000000f, -0.274552f },
new[] { 6.066758f, 10.197294f, -19.669443f, -0.381368f, 0.000000f, -0.124736f },
new[] { 5.988646f, 10.197294f, -19.691917f, -0.390560f, 0.000000f, -0.112371f },
new[] { 5.880603f, 10.197294f, -19.641851f, -0.540214f, 0.000000f, 0.250327f },
new[] { 5.768869f, 10.197294f, -19.553535f, -0.558670f, 0.000000f, 0.441585f },
new[] { 5.773450f, 10.197294f, -19.545095f, 0.022906f, 0.000000f, 0.042200f },
new[] { 5.764818f, 10.197294f, -19.506109f, -0.043161f, 0.000000f, 0.194928f },
new[] { 5.769928f, 10.197294f, -19.497072f, 0.025550f, 0.000000f, 0.045183f },
new[] { 5.753877f, 10.197294f, -19.524942f, -0.080258f, 0.000000f, -0.139354f },
new[] { 5.731219f, 10.197294f, -19.533819f, -0.113286f, 0.000000f, -0.044384f } };
static readonly float[][] EXPECTED_A1Q2T = {
new[] { 22.990597f, 10.197294f, -46.112606f, -2.999564f, 0.000000f, 1.803501f },
new[] { 22.524744f, 10.197294f, -45.867702f, -2.989815f, 0.000000f, 1.819617f },
new[] { 21.946421f, 10.197294f, -45.530769f, -2.987121f, 0.000000f, 1.824035f },
new[] { 21.357445f, 10.197294f, -45.183083f, -2.985955f, 0.000000f, 1.825945f },
new[] { 20.766855f, 10.197294f, -44.833454f, -2.985027f, 0.000000f, 1.827461f },
new[] { 20.176287f, 10.197294f, -44.483444f, -2.984109f, 0.000000f, 1.828960f },
new[] { 19.586048f, 10.197294f, -44.133289f, -2.983157f, 0.000000f, 1.830513f },
new[] { 18.996214f, 10.197294f, -43.783005f, -2.982163f, 0.000000f, 1.832130f },
new[] { 18.406816f, 10.197294f, -43.432552f, -2.981127f, 0.000000f, 1.833817f },
new[] { 17.817883f, 10.197294f, -43.081890f, -2.980047f, 0.000000f, 1.835571f },
new[] { 17.229431f, 10.197294f, -42.730961f, -2.978924f, 0.000000f, 1.837393f },
new[] { 16.641487f, 10.197294f, -42.379704f, -2.977759f, 0.000000f, 1.839280f },
new[] { 16.054066f, 10.197294f, -42.028053f, -2.976555f, 0.000000f, 1.841228f },
new[] { 15.467182f, 10.197294f, -41.675930f, -2.975313f, 0.000000f, 1.843233f },
new[] { 14.880149f, 10.197294f, -41.325222f, -2.974040f, 0.000000f, 1.845288f },
new[] { 14.292006f, 10.197294f, -40.972252f, -2.972459f, 0.000000f, 1.847835f },
new[] { 13.702817f, 10.197294f, -40.616268f, -2.970898f, 0.000000f, 1.850342f },
new[] { 13.113760f, 10.197294f, -40.258976f, -2.969465f, 0.000000f, 1.852640f },
new[] { 12.525526f, 10.197294f, -39.902176f, -2.968050f, 0.000000f, 1.854907f },
new[] { 11.938206f, 10.197294f, -39.545849f, -2.966453f, 0.000000f, 1.857459f },
new[] { 11.351962f, 10.197294f, -39.190098f, -2.964650f, 0.000000f, 1.860337f },
new[] { 10.767020f, 10.197294f, -38.835106f, -2.962592f, 0.000000f, 1.863611f },
new[] { 10.183690f, 10.197294f, -38.481129f, -2.960215f, 0.000000f, 1.867385f },
new[] { 9.602149f, 10.197294f, -38.128098f, -2.957422f, 0.000000f, 1.871805f },
new[] { 9.023255f, 10.197294f, -37.777180f, -2.954136f, 0.000000f, 1.876987f },
new[] { 8.448158f, 10.197294f, -37.429573f, -2.950055f, 0.000000f, 1.883394f },
new[] { 7.879329f, 10.197294f, -37.087776f, -2.944782f, 0.000000f, 1.891628f },
new[] { 7.319548f, 10.197294f, -36.753658f, -2.937588f, 0.000000f, 1.902782f },
new[] { 6.776391f, 10.197294f, -36.434063f, -2.927629f, 0.000000f, 1.918068f },
new[] { 6.269217f, 10.197294f, -36.142403f, -2.912834f, 0.000000f, 1.940462f },
new[] { 5.846630f, 10.197294f, -35.881496f, -2.890513f, 0.000000f, 1.973559f },
new[] { 5.411777f, 10.197294f, -35.547710f, -2.874764f, 0.000000f, 1.996430f },
new[] { 4.896369f, 10.197294f, -35.184120f, -2.896978f, 0.000000f, 1.964057f },
new[] { 4.367188f, 10.197294f, -34.833569f, -2.910470f, 0.000000f, 1.944008f },
new[] { 3.807542f, 10.197294f, -34.472218f, -2.906039f, 0.000000f, 1.950624f },
new[] { 3.238266f, 10.197294f, -34.064877f, -2.846376f, 0.000000f, 2.036700f },
new[] { 2.917057f, 10.197294f, -33.455391f, -1.420109f, 0.000000f, 3.198952f },
new[] { 2.645415f, 10.197294f, -32.810249f, -1.358214f, 0.000000f, 3.225718f },
new[] { 2.373772f, 10.197294f, -32.165104f, -1.358212f, 0.000000f, 3.225718f },
new[] { 2.103753f, 10.197294f, -31.530035f, -1.358215f, 0.000000f, 3.225717f },
new[] { 1.837103f, 10.197294f, -30.882812f, -1.333248f, 0.000000f, 3.236116f },
new[] { 1.887709f, 10.197294f, -30.193766f, 0.449056f, 0.000000f, 3.471073f },
new[] { 2.034543f, 10.197294f, -29.509338f, 0.734173f, 0.000000f, 3.422132f },
new[] { 2.266320f, 10.197294f, -28.848824f, 1.158885f, 0.000000f, 3.302572f },
new[] { 2.498098f, 10.197294f, -28.188309f, 1.158885f, 0.000000f, 3.302572f },
new[] { 2.729875f, 10.197294f, -27.527794f, 1.158886f, 0.000000f, 3.302572f },
new[] { 2.992905f, 10.197294f, -26.879091f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.255934f, 10.197294f, -26.230389f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.518964f, 10.197294f, -25.581686f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.781994f, 10.197294f, -24.932983f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.045024f, 10.197294f, -24.284281f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.308054f, 10.197294f, -23.635578f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.571084f, 10.197294f, -22.986876f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.834114f, 10.197294f, -22.338173f, 1.315149f, 0.000000f, 3.243514f },
new[] { 5.097065f, 10.197294f, -21.689661f, 1.315149f, 0.000000f, 3.243514f },
new[] { 5.349040f, 10.197294f, -21.068123f, 1.315149f, 0.000000f, 3.243514f },
new[] { 5.564819f, 10.197294f, -20.535385f, 1.315191f, 0.000000f, 3.243497f },
new[] { 5.742907f, 10.197294f, -20.094234f, 1.315492f, 0.000000f, 3.243375f },
new[] { 5.844197f, 10.197294f, -19.830488f, 1.316821f, 0.000000f, 3.242836f },
new[] { 5.890507f, 10.197294f, -19.651239f, 1.327608f, 0.000000f, 3.238434f },
new[] { 5.895338f, 10.197294f, -19.433264f, 1.384180f, 0.000000f, 3.214661f },
new[] { 5.917773f, 10.197294f, -19.188416f, 1.594034f, 0.000000f, 3.115936f },
new[] { 5.947361f, 10.197294f, -19.047245f, 1.574677f, 0.000000f, 2.491868f },
new[] { 5.960892f, 10.197294f, -18.990824f, 1.488381f, 0.000000f, 2.080121f },
new[] { 5.971087f, 10.197294f, -18.963556f, 1.448915f, 0.000000f, 1.915559f },
new[] { 5.977089f, 10.197294f, -18.950905f, 1.419177f, 0.000000f, 1.836029f } };
[Test]
public void testAgent1Quality2TVTA() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++) {
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f), $"{i}");
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2TVTA[i][1]).Within(0.001f), $"{i}");
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2TVTA[i][2]).Within(0.001f), $"{i}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][3]).Within(0.001f), $"{i}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2TVTA[i][4]).Within(0.001f), $"{i}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2TVTA[i][5]).Within(0.001f), $"{i}");
}
}
[Test]
public void testAgent1Quality2TVTAS() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++) {
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][0]).Within(0.001f), $"{i}");
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][1]).Within(0.001f), $"{i}");
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][2]).Within(0.001f), $"{i}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][3]).Within(0.001f), $"{i}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][4]).Within(0.001f), $"{i}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][5]).Within(0.001f), $"{i}");
}
}
[Test]
public void testAgent1Quality2T() {
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2T.Length; i++)
{
if (i == 37)
{
int a = 3;
}
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}"); Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}"); Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}"); Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}"); Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}"); Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}"); Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Thread.Sleep(1);
}
}
}

View File

@ -0,0 +1,118 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class Crowd4VelocityTest : AbstractCrowdTest {
static readonly float[][] EXPECTED_A1Q3TVTA = {
new[] { 6.101694f, 10.197294f, -17.678480f, 0.000000f, 0.000000f, 0.000000f },
new[] { 6.024141f, 10.197294f, -17.589798f, -0.107331f, 0.000000f, 0.098730f },
new[] { 6.004839f, 10.197294f, -17.554886f, -0.096506f, 0.000000f, 0.174561f },
new[] { 5.744515f, 10.197294f, -17.309479f, -2.590961f, 0.000000f, 2.353066f },
new[] { 5.253671f, 10.197294f, -16.842125f, -2.534360f, 0.000000f, 2.413922f },
new[] { 4.789658f, 10.197294f, -16.318014f, -2.320063f, 0.000000f, 2.620555f },
new[] { 4.407527f, 10.197294f, -15.731520f, -1.910654f, 0.000000f, 2.932474f },
new[] { 4.023476f, 10.197294f, -15.146280f, -1.920256f, 0.000000f, 2.926195f },
new[] { 3.645756f, 10.197294f, -14.556935f, -1.888601f, 0.000000f, 2.946725f },
new[] { 3.277534f, 10.197294f, -13.961610f, -1.841108f, 0.000000f, 2.976629f },
new[] { 2.924562f, 10.197294f, -13.357118f, -1.764861f, 0.000000f, 3.022460f },
new[] { 2.598673f, 10.197294f, -12.737605f, -1.629447f, 0.000000f, 3.097564f },
new[] { 2.330214f, 10.197294f, -12.091130f, -1.342291f, 0.000000f, 3.232376f },
new[] { 2.174726f, 10.197294f, -11.408617f, -0.777438f, 0.000000f, 3.412564f },
new[] { 2.040282f, 10.197294f, -10.721649f, -0.672225f, 0.000000f, 3.434838f },
new[] { 1.958181f, 10.197294f, -10.026481f, -0.410503f, 0.000000f, 3.475843f },
new[] { 1.653389f, 10.197294f, -9.396320f, -1.523958f, 0.000000f, 3.150802f },
new[] { 1.642715f, 10.197294f, -8.696402f, -0.053371f, 0.000000f, 3.499593f },
new[] { 1.937517f, 10.197294f, -8.091795f, 2.033996f, 0.000000f, 2.848308f },
new[] { 2.364934f, 10.197294f, -7.537435f, 2.137084f, 0.000000f, 2.771799f },
new[] { 2.802262f, 10.197294f, -6.990860f, 2.186641f, 0.000000f, 2.732875f },
new[] { 3.186367f, 10.197294f, -6.759828f, 1.617082f, 0.000000f, -0.643841f },
new[] { 3.460433f, 10.197294f, -6.829281f, 1.002684f, 0.000000f, -1.351205f },
new[] { 3.605715f, 10.197294f, -6.794649f, 0.726412f, 0.000000f, 0.173160f },
new[] { 3.796394f, 10.197294f, -6.840563f, 0.953395f, 0.000000f, -0.229571f },
new[] { 3.882745f, 10.197294f, -6.956440f, 0.431757f, 0.000000f, -0.579388f },
new[] { 3.983807f, 10.197294f, -7.160242f, 0.505308f, 0.000000f, -1.019009f },
new[] { 4.031534f, 10.197294f, -7.358752f, 0.238635f, 0.000000f, -0.992549f },
new[] { 4.081295f, 10.197294f, -7.517536f, 0.248804f, 0.000000f, -0.793922f },
new[] { 4.108567f, 10.197294f, -7.630970f, 0.136363f, 0.000000f, -0.567171f },
new[] { 4.092495f, 10.197294f, -7.727181f, -0.080361f, 0.000000f, -0.481056f },
new[] { 4.096027f, 10.197294f, -7.807384f, 0.017662f, 0.000000f, -0.401016f },
new[] { 4.131466f, 10.197294f, -7.874563f, 0.177196f, 0.000000f, -0.335895f },
new[] { 4.102508f, 10.197294f, -7.917174f, -0.144795f, 0.000000f, -0.213056f },
new[] { 4.073549f, 10.197294f, -7.959785f, -0.144795f, 0.000000f, -0.213056f },
new[] { 4.044590f, 10.197294f, -8.002397f, -0.144795f, 0.000000f, -0.213056f },
new[] { 3.983432f, 10.197294f, -8.032723f, -0.305791f, 0.000000f, -0.151636f },
new[] { 3.948404f, 10.197294f, -8.050093f, -0.175139f, 0.000000f, -0.086848f },
new[] { 3.935988f, 10.197294f, -8.078673f, -0.062080f, 0.000000f, -0.142903f },
new[] { 3.943177f, 10.197294f, -8.091246f, 0.035943f, 0.000000f, -0.062864f },
new[] { 3.950365f, 10.197294f, -8.103818f, 0.035943f, 0.000000f, -0.062864f },
new[] { 3.957554f, 10.197294f, -8.116390f, 0.035943f, 0.000000f, -0.062864f },
new[] { 3.964742f, 10.197294f, -8.128963f, 0.035943f, 0.000000f, -0.062864f },
new[] { 4.003838f, 10.197294f, -8.128510f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.042933f, 10.197294f, -8.128058f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.082028f, 10.197294f, -8.127606f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.121124f, 10.197294f, -8.127154f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.160219f, 10.197294f, -8.126702f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.199315f, 10.197294f, -8.126250f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.206211f, 10.197294f, -8.113515f, 0.034481f, 0.000000f, 0.063677f },
new[] { 4.213107f, 10.197294f, -8.100780f, 0.034481f, 0.000000f, 0.063677f },
new[] { 4.230340f, 10.197294f, -8.092234f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.247572f, 10.197294f, -8.083688f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.246885f, 10.197294f, -8.098154f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.264118f, 10.197294f, -8.089608f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.281351f, 10.197294f, -8.081062f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.280663f, 10.197294f, -8.095529f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.297896f, 10.197294f, -8.086983f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.315129f, 10.197294f, -8.078437f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.332362f, 10.197294f, -8.069891f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.322824f, 10.197294f, -8.046370f, -0.047688f, 0.000000f, 0.117607f },
new[] { 4.322136f, 10.197294f, -8.060836f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.321449f, 10.197294f, -8.075302f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.320761f, 10.197294f, -8.089768f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.337994f, 10.197294f, -8.081223f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.328456f, 10.197294f, -8.057701f, -0.047688f, 0.000000f, 0.117607f },
new[] { 4.327769f, 10.197294f, -8.072167f, -0.003438f, 0.000000f, -0.072332f } };
[Test]
public void testAgent1Quality3TVTA() {
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 3, endPoss[0]);
setMoveTarget(endPoss[4], false);
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++) {
crowd.update(1 / 5f, null);
if (i == 20) {
setMoveTarget(startPoss[2], true);
}
CrowdAgent ag = agents[1];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][2]).Within(0.001f));
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][3]).Within(0.001f));
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][4]).Within(0.001f));
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][5]).Within(0.001f));
}
}
}

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,80 @@
/*
recast4j copyright (c) 2021 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.Collections.Generic;
using Moq;
using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class PathCorridorTest {
private readonly PathCorridor corridor = new PathCorridor();
private readonly QueryFilter filter = new DefaultQueryFilter();
[SetUp]
public void setUp() {
corridor.reset(0, new float[] {10,20,30});
}
[Test]
public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned() {
List<StraightPathItem> straightPath = new();
straightPath.Add(new StraightPathItem(new float[] { 11, 20, 30.00001f }, 0, 0));
straightPath.Add(new StraightPathItem(new float[] { 12, 20, 30.00002f }, 0, 0));
straightPath.Add(new StraightPathItem(new float[] { 11f, 21, 32f }, 0, 0));
straightPath.Add(new StraightPathItem(new float[] { 11f, 21, 32f }, 0, 0));
Result<List<StraightPathItem>> result = Results.success(straightPath);
var mockQuery = new Mock<NavMeshQuery>(It.IsAny<NavMesh>());
mockQuery.Setup(q => q.findStraightPath(
It.IsAny<float[]>(),
It.IsAny<float[]>(),
It.IsAny<List<long>>(),
It.IsAny<int>(),
It.IsAny<int>())
).Returns(result);
List<StraightPathItem> path = corridor.findCorners(int.MaxValue, mockQuery.Object, filter);
Assert.That(path.Count, Is.EqualTo(4));
Assert.That(path, Is.EqualTo(straightPath));
}
[Test]
public void shouldPrunePathInFindCorners() {
List<StraightPathItem> straightPath = new();
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00001f }, 0, 0)); // too close
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00002f }, 0, 0)); // too close
straightPath.Add(new StraightPathItem(new float[] { 11f, 21, 32f }, 0, 0));
straightPath.Add(new StraightPathItem(new float[] { 12f, 22, 33f }, NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh
straightPath.Add(new StraightPathItem(new float[] { 11f, 21, 32f }, NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION, 0)); // offmesh
Result<List<StraightPathItem>> result = Results.success(straightPath);
var mockQuery = new Mock<NavMeshQuery>(It.IsAny<NavMesh>());
var s = mockQuery.Setup(q => q.findStraightPath(
It.IsAny<float[]>(),
It.IsAny<float[]>(),
It.IsAny<List<long>>(),
It.IsAny<int>(),
It.IsAny<int>())
).Returns(result);
List<StraightPathItem> path = corridor.findCorners(int.MaxValue, mockQuery.Object, filter);
Assert.That(path.Count, Is.EqualTo(2));
Assert.That(path, Is.EqualTo(new List<StraightPathItem> { straightPath[2], straightPath[3] }));
}
}

View File

@ -0,0 +1,110 @@
/*
recast4j Copyright (c) 2015-2019 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 DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Crowd.Test;
public class RecastTestMeshBuilder {
private readonly MeshData meshData;
public const float m_cellSize = 0.3f;
public const float m_cellHeight = 0.2f;
public const float m_agentHeight = 2.0f;
public const float m_agentRadius = 0.6f;
public const float m_agentMaxClimb = 0.9f;
public const float m_agentMaxSlope = 45.0f;
public const int m_regionMinSize = 8;
public const int m_regionMergeSize = 20;
public const float m_edgeMaxLen = 12.0f;
public const float m_edgeMaxError = 1.3f;
public const int m_vertsPerPoly = 6;
public const float m_detailSampleDist = 6.0f;
public const float m_detailSampleMaxError = 1.0f;
public RecastTestMeshBuilder() : this(ObjImporter.load(Loader.ToBytes("dungeon.obj")),
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope,
m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist,
m_detailSampleMaxError)
{
}
public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError) {
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax());
RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
PolyMesh m_pmesh = rcResult.getMesh();
for (int i = 0; i < m_pmesh.npolys; ++i) {
m_pmesh.flags[i] = 1;
}
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = m_pmesh.verts;
option.vertCount = m_pmesh.nverts;
option.polys = m_pmesh.polys;
option.polyAreas = m_pmesh.areas;
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
option.detailMeshes = m_dmesh.meshes;
option.detailVerts = m_dmesh.verts;
option.detailVertsCount = m_dmesh.nverts;
option.detailTris = m_dmesh.tris;
option.detailTriCount = m_dmesh.ntris;
option.walkableHeight = m_agentHeight;
option.walkableRadius = m_agentRadius;
option.walkableClimb = m_agentMaxClimb;
option.bmin = m_pmesh.bmin;
option.bmax = m_pmesh.bmax;
option.cs = m_cellSize;
option.ch = m_cellHeight;
option.buildBvTree = true;
option.offMeshConVerts = new float[6];
option.offMeshConVerts[0] = 0.1f;
option.offMeshConVerts[1] = 0.2f;
option.offMeshConVerts[2] = 0.3f;
option.offMeshConVerts[3] = 0.4f;
option.offMeshConVerts[4] = 0.5f;
option.offMeshConVerts[5] = 0.6f;
option.offMeshConRad = new float[1];
option.offMeshConRad[0] = 0.1f;
option.offMeshConDir = new int[1];
option.offMeshConDir[0] = 1;
option.offMeshConAreas = new int[1];
option.offMeshConAreas[0] = 2;
option.offMeshConFlags = new int[1];
option.offMeshConFlags[0] = 12;
option.offMeshConUserID = new int[1];
option.offMeshConUserID[0] = 0x4567;
option.offMeshConCount = 1;
meshData = NavMeshBuilder.createNavMeshData(option);
}
public MeshData getMeshData() {
return meshData;
}
}

View File

@ -0,0 +1,52 @@
/*
recast4j Copyright (c) 2015-2019 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 DotRecast.Recast;
namespace DotRecast.Detour.Crowd.Test;
public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x3;
public const int SAMPLE_POLYAREA_TYPE_DOOR = 0x4;
public const int SAMPLE_POLYAREA_TYPE_GRASS = 0x5;
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP);
public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road)
public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water).
public const int SAMPLE_POLYFLAGS_DOOR = 0x04; // Ability to move through doors.
public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using DotRecast.Core;
using DotRecast.Detour.Dynamic.Colliders;
using DotRecast.Detour.Dynamic.Io;
using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test;
public class DynamicNavMeshTest {
private static readonly float[] START_POS = new float[] { 70.87453f, 0.0010070801f, 86.69021f };
private static readonly float[] END_POS = new float[] { -50.22061f, 0.0010070801f, -70.761444f };
private static readonly float[] EXTENT = new float[] { 0.1f, 0.1f, 0.1f };
private static readonly float[] SPHERE_POS = new float[] { 45.381645f, 0.0010070801f, 52.68981f };
[Test]
public void e2eTest() {
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
// load voxels from file
VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis);
// create dynamic navmesh
DynamicNavMesh mesh = new DynamicNavMesh(f);
// build navmesh asynchronously using multiple threads
Task<bool> future = mesh.build(Task.Factory);
// wait for build to complete
bool _ = future.Result;
// create new query
NavMeshQuery query = new NavMeshQuery(mesh.navMesh());
QueryFilter filter = new DefaultQueryFilter();
// find path
FindNearestPolyResult start = query.findNearestPoly(START_POS, EXTENT, filter).result;
FindNearestPolyResult end = query.findNearestPoly(END_POS, EXTENT, filter).result;
List<long> path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(),
end.getNearestPos(), filter, NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result;
// check path length without any obstacles
Assert.That(path.Count, Is.EqualTo(16));
// place obstacle
Collider colldier = new SphereCollider(SPHERE_POS, 20, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND, 0.1f);
long colliderId = mesh.addCollider(colldier);
// update navmesh asynchronously
future = mesh.update(Task.Factory);
// wait for update to complete
_ = future.Result;
// create new query
query = new NavMeshQuery(mesh.navMesh());
// find path again
start = query.findNearestPoly(START_POS, EXTENT, filter).result;
end = query.findNearestPoly(END_POS, EXTENT, filter).result;
path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(), end.getNearestPos(), filter,
NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result;
// check path length with obstacles
Assert.That(path.Count, Is.EqualTo(19));
// remove obstacle
mesh.removeCollider(colliderId);
// update navmesh asynchronously
future = mesh.update(Task.Factory);
// wait for update to complete
_ = future.Result;
// create new query
query = new NavMeshQuery(mesh.navMesh());
// find path one more time
start = query.findNearestPoly(START_POS, EXTENT, filter).result;
end = query.findNearestPoly(END_POS, EXTENT, filter).result;
path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(), end.getNearestPos(), filter,
NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result;
// path length should be back to the initial value
Assert.That(path.Count, Is.EqualTo(16));
}
}

View File

@ -0,0 +1,78 @@
/*
recast4j copyright (c) 2021 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Dynamic.Io;
using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io;
public class VoxelFileReaderTest {
[Test]
public void shouldReadSingleTileFile() {
byte[] bytes = Loader.ToBytes("test.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis);
Assert.That(f.useTiles, Is.False);
Assert.That(f.bounds, Is.EqualTo(new float[] {-100.0f, 0f, -100f, 100f, 5f, 100f}));
Assert.That(f.cellSize, Is.EqualTo(0.25f));
Assert.That(f.walkableRadius, Is.EqualTo(0.5f));
Assert.That(f.walkableHeight, Is.EqualTo(2f));
Assert.That(f.walkableClimb, Is.EqualTo(0.5f));
Assert.That(f.maxEdgeLen, Is.EqualTo(20f));
Assert.That(f.maxSimplificationError, Is.EqualTo(2f));
Assert.That(f.minRegionArea, Is.EqualTo(2f));
Assert.That(f.tiles.Count, Is.EqualTo(1));
Assert.That(f.tiles[0].cellHeight, Is.EqualTo(0.001f));
Assert.That(f.tiles[0].width, Is.EqualTo(810));
Assert.That(f.tiles[0].depth, Is.EqualTo(810));
Assert.That(f.tiles[0].boundsMin, Is.EqualTo(new float[] {-101.25f, 0f, -101.25f}));
Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new float[] {101.25f, 5.0f, 101.25f}));
}
[Test]
public void shouldReadMultiTileFile() {
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis);
Assert.That(f.useTiles, Is.True);
Assert.That(f.bounds, Is.EqualTo(new float[] { -100.0f, 0f, -100f, 100f, 5f, 100f }));
Assert.That(f.cellSize, Is.EqualTo(0.25f));
Assert.That(f.walkableRadius, Is.EqualTo(0.5f));
Assert.That(f.walkableHeight, Is.EqualTo(2f));
Assert.That(f.walkableClimb, Is.EqualTo(0.5f));
Assert.That(f.maxEdgeLen, Is.EqualTo(20f));
Assert.That(f.maxSimplificationError, Is.EqualTo(2f));
Assert.That(f.minRegionArea, Is.EqualTo(2f));
Assert.That(f.tiles.Count, Is.EqualTo(100));
Assert.That(f.tiles[0].cellHeight, Is.EqualTo(0.001f));
Assert.That(f.tiles[0].width, Is.EqualTo(90));
Assert.That(f.tiles[0].depth, Is.EqualTo(90));
Assert.That(f.tiles[0].boundsMin, Is.EqualTo(new float[] { -101.25f, 0f, -101.25f }));
Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new float[] { -78.75f, 5.0f, -78.75f }));
}
}

View File

@ -0,0 +1,100 @@
/*
recast4j copyright (c) 2021 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Dynamic.Io;
using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io;
public class VoxelFileReaderWriterTest {
[TestCase(false)]
[TestCase(true)]
public void shouldReadSingleTileFile(bool compression) {
byte[] bytes = Loader.ToBytes("test.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
VoxelFile f = readWriteRead(bis, compression);
Assert.That(f.useTiles, Is.False);
Assert.That(f.bounds, Is.EqualTo(new[] { -100.0f, 0f, -100f, 100f, 5f, 100f }));
Assert.That(f.cellSize, Is.EqualTo(0.25f));
Assert.That(f.walkableRadius, Is.EqualTo(0.5f));
Assert.That(f.walkableHeight, Is.EqualTo(2f));
Assert.That(f.walkableClimb, Is.EqualTo(0.5f));
Assert.That(f.maxEdgeLen, Is.EqualTo(20f));
Assert.That(f.maxSimplificationError, Is.EqualTo(2f));
Assert.That(f.minRegionArea, Is.EqualTo(2f));
Assert.That(f.regionMergeArea, Is.EqualTo(12f));
Assert.That(f.tiles.Count, Is.EqualTo(1));
Assert.That(f.tiles[0].cellHeight, Is.EqualTo(0.001f));
Assert.That(f.tiles[0].width, Is.EqualTo(810));
Assert.That(f.tiles[0].depth, Is.EqualTo(810));
Assert.That(f.tiles[0].spanData.Length, Is.EqualTo(9021024));
Assert.That(f.tiles[0].boundsMin, Is.EqualTo(new[] { -101.25f, 0f, -101.25f }));
Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new[] { 101.25f, 5.0f, 101.25f }));
}
[TestCase(false)]
[TestCase(true)]
public void shouldReadMultiTileFile(bool compression) {
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
VoxelFile f = readWriteRead(bis, compression);
Assert.That(f.useTiles, Is.True);
Assert.That(f.bounds, Is.EqualTo(new[] {-100.0f, 0f, -100f, 100f, 5f, 100f}));
Assert.That(f.cellSize, Is.EqualTo(0.25f));
Assert.That(f.walkableRadius, Is.EqualTo(0.5f));
Assert.That(f.walkableHeight, Is.EqualTo(2f));
Assert.That(f.walkableClimb, Is.EqualTo(0.5f));
Assert.That(f.maxEdgeLen, Is.EqualTo(20f));
Assert.That(f.maxSimplificationError, Is.EqualTo(2f));
Assert.That(f.minRegionArea, Is.EqualTo(2f));
Assert.That(f.regionMergeArea, Is.EqualTo(12f));
Assert.That(f.tiles.Count, Is.EqualTo(100));
Assert.That(f.tiles[0].cellHeight, Is.EqualTo(0.001f));
Assert.That(f.tiles[0].width, Is.EqualTo(90));
Assert.That(f.tiles[0].depth, Is.EqualTo(90));
Assert.That(f.tiles[0].spanData.Length, Is.EqualTo(104952));
Assert.That(f.tiles[5].spanData.Length, Is.EqualTo(109080));
Assert.That(f.tiles[18].spanData.Length, Is.EqualTo(113400));
Assert.That(f.tiles[0].boundsMin, Is.EqualTo(new[] {-101.25f, 0f, -101.25f}));
Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new[] {-78.75f, 5.0f, -78.75f}));
}
private VoxelFile readWriteRead(BinaryReader bis, bool compression) {
VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis);
using var msOut = new MemoryStream();
using var bwOut = new BinaryWriter(msOut);
VoxelFileWriter writer = new VoxelFileWriter();
writer.write(bwOut, f, compression);
using var msIn = new MemoryStream(msOut.ToArray());
using var brIn = new BinaryReader(msIn);
return reader.read(brIn);
}
}

View File

@ -0,0 +1,52 @@
/*
recast4j Copyright (c) 2015-2019 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 DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Test;
public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x3;
public const int SAMPLE_POLYAREA_TYPE_DOOR = 0x4;
public const int SAMPLE_POLYAREA_TYPE_GRASS = 0x5;
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP);
public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road)
public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water).
public const int SAMPLE_POLYFLAGS_DOOR = 0x04; // Ability to move through doors.
public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
}

View File

@ -0,0 +1,105 @@
/*
recast4j copyright (c) 2021 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 System.Threading.Tasks;
using System.Collections.Generic;
using DotRecast.Core;
using DotRecast.Detour.Dynamic.Io;
using DotRecast.Recast;
using Moq;
using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test;
public class VoxelQueryTest {
private const int TILE_WIDTH = 100;
private const int TILE_DEPTH = 90;
private static readonly float[] ORIGIN = new float[] { 50, 10, 40 };
[Test]
public void shouldTraverseTiles()
{
var hfProvider = new Mock<Func<int, int, Heightfield>>();
// Given
List<int> captorX = new();
List<int> captorZ = new();
hfProvider
.Setup(e => e.Invoke(It.IsAny<int>(), It.IsAny<int>()))
.Returns((Heightfield)null)
.Callback<int, int>((x, z) =>
{
captorX.Add(x);
captorZ.Add(z);
});
VoxelQuery query = new VoxelQuery(ORIGIN, TILE_WIDTH, TILE_DEPTH, hfProvider.Object);
float[] start = { 120, 10, 365 };
float[] end = { 320, 10, 57 };
// When
query.raycast(start, end);
// Then
hfProvider.Verify(mock => mock.Invoke(It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(6));
Assert.That(captorX, Is.EqualTo(new[] { 0, 1, 1, 1, 2, 2}));
Assert.That(captorZ, Is.EqualTo(new[] { 3, 3, 2, 1, 1, 0}));
}
[Test]
public void shouldHandleRaycastWithoutObstacles() {
DynamicNavMesh mesh = createDynaMesh();
VoxelQuery query = mesh.voxelQuery();
float[] start = { 7.4f, 0.5f, -64.8f };
float[] end = { 31.2f, 0.5f, -75.3f };
float? hit = query.raycast(start, end);
Assert.That(hit, Is.Null);
}
[Test]
public void shouldHandleRaycastWithObstacles() {
DynamicNavMesh mesh = createDynaMesh();
VoxelQuery query = mesh.voxelQuery();
float[] start = { 32.3f, 0.5f, 47.9f };
float[] end = { -31.2f, 0.5f, -29.8f };
float? hit = query.raycast(start, end);
Assert.That(hit, Is.Not.Null);
Assert.That(hit.Value, Is.EqualTo(0.5263836f).Within(1e-7f));
}
private DynamicNavMesh createDynaMesh() {
var bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
// load voxels from file
VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis);
// create dynamic navmesh
var mesh = new DynamicNavMesh(f);
// build navmesh asynchronously using multiple threads
Task<bool> future = mesh.build(Task.Factory);
// wait for build to complete
var _ = future.Result;
return mesh;
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,128 @@
/*
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.IO;
using System.Linq;
using DotRecast.Core;
using DotRecast.Detour.Extras.Unity.Astar;
using DotRecast.Detour.Io;
using NUnit.Framework;
namespace DotRecast.Detour.Extras.Test.Unity.Astar;
public class UnityAStarPathfindingImporterTest {
[Test]
public void test_v4_0_6() {
NavMesh mesh = loadNavMesh("graph.zip");
float[] startPos = new float[] { 8.200293f, 2.155071f, -26.176147f };
float[] endPos = new float[] { 11.971109f, 0.000000f, 8.663261f };
Result<List<long>> path = findPath(mesh, startPos, endPos);
Assert.That(path.status, Is.EqualTo(Status.SUCCSESS));
Assert.That(path.result.Count, Is.EqualTo(57));
saveMesh(mesh, "v4_0_6");
}
[Test]
public void test_v4_1_16() {
NavMesh mesh = loadNavMesh("graph_v4_1_16.zip");
float[] startPos = new float[] { 22.93f, -2.37f, -5.11f };
float[] endPos = new float[] { 16.81f, -2.37f, 25.52f };
Result<List<long>> path = findPath(mesh, startPos, endPos);
Assert.That(path.status.isSuccess(), Is.True);
Assert.That(path.result.Count, Is.EqualTo(15));
saveMesh(mesh, "v4_1_16");
}
[Test]
public void testBoundsTree() {
NavMesh mesh = loadNavMesh("test_boundstree.zip");
float[] position = { 387.52988f, 19.997f, 368.86282f };
int[] tilePos = mesh.calcTileLoc(position);
long tileRef = mesh.getTileRefAt(tilePos[0], tilePos[1], 0);
MeshTile tile = mesh.getTileByRef(tileRef);
MeshData data = tile.data;
BVNode[] bvNodes = data.bvTree;
data.bvTree = null; // set BV-Tree empty to get 'clear' search poly without BV
FindNearestPolyResult clearResult = getNearestPolys(mesh, position)[0]; // check poly to exists
// restore BV-Tree and try search again
// important aspect in that test: BV result must equals result without BV
// if poly not found or found other poly - tile bounds is wrong!
data.bvTree = bvNodes;
FindNearestPolyResult bvResult = getNearestPolys(mesh, position)[0];
Assert.That(bvResult.getNearestRef(), Is.EqualTo(clearResult.getNearestRef()));
}
private NavMesh loadNavMesh(string filename) {
var filepath = Loader.ToRPath(filename);
using var fs = new FileStream(filepath, FileMode.Open);
// Import the graphs
UnityAStarPathfindingImporter importer = new UnityAStarPathfindingImporter();
NavMesh[] meshes = importer.load(fs);
return meshes[0];
}
private Result<List<long>> findPath(NavMesh mesh, float[] startPos, float[] endPos) {
// Perform a simple pathfinding
NavMeshQuery query = new NavMeshQuery(mesh);
QueryFilter filter = new DefaultQueryFilter();
FindNearestPolyResult[] polys = getNearestPolys(mesh, startPos, endPos);
return query.findPath(polys[0].getNearestRef(), polys[1].getNearestRef(), startPos, endPos, filter);
}
private FindNearestPolyResult[] getNearestPolys(NavMesh mesh, params float[][] positions) {
NavMeshQuery query = new NavMeshQuery(mesh);
QueryFilter filter = new DefaultQueryFilter();
float[] extents = new float[] { 0.1f, 0.1f, 0.1f };
FindNearestPolyResult[] results = new FindNearestPolyResult[positions.Length];
for (int i = 0; i < results.Length; i++) {
float[] position = positions[i];
Result<FindNearestPolyResult> result = query.findNearestPoly(position, extents, filter);
Assert.That(result.succeeded(), Is.True);
Assert.That(result.result.getNearestPos(), Is.Not.Null, "Nearest start position is null!");
results[i] = result.result;
}
return results;
}
private void saveMesh(NavMesh mesh, string filePostfix) {
// Set the flag to RecastDemo work properly
for (int i = 0; i < mesh.getTileCount(); i++) {
foreach (Poly p in mesh.getTile(i).data.polys) {
p.flags = 1;
}
}
// Save the mesh as recast file,
MeshSetWriter writer = new MeshSetWriter();
string filename = $"all_tiles_navmesh_{filePostfix}.bin";
string filepath = Path.Combine("test-output", filename);
using var fs = new FileStream(filename, FileMode.Create);
using var os = new BinaryWriter(fs);
writer.write(os, mesh, ByteOrder.LITTLE_ENDIAN, true);
}
}

View File

@ -0,0 +1,67 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public abstract class AbstractDetourTest
{
protected static readonly long[] startRefs =
{
281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L, 281474976710733L
};
protected static readonly long[] endRefs =
{
281474976710721L, 281474976710767L, 281474976710758L, 281474976710731L, 281474976710772L
};
protected static readonly float[][] startPoss =
{
new[] { 22.60652f, 10.197294f, -45.918674f },
new[] { 22.331268f, 10.197294f, -1.0401875f },
new[] { 18.694363f, 15.803535f, -73.090416f },
new[] { 0.7453353f, 10.197294f, -5.94005f },
new[] { -20.651257f, 5.904126f, -13.712508f }
};
protected static readonly float[][] endPoss =
{
new[] { 6.4576626f, 10.197294f, -18.33406f },
new[] { -5.8023443f, 0.19729415f, 3.008419f },
new[] { 38.423977f, 10.197294f, -0.116066754f },
new[] { 0.8635526f, 10.197294f, -10.31032f },
new[] { 18.784092f, 10.197294f, 3.0543678f }
};
protected NavMeshQuery query;
protected NavMesh navmesh;
[SetUp]
public void setUp()
{
navmesh = createNavMesh();
query = new NavMeshQuery(navmesh);
}
protected NavMesh createNavMesh()
{
return new NavMesh(new RecastTestMeshBuilder().getMeshData(), 6, 0);
}
}

View File

@ -0,0 +1,43 @@
/*
recast4j copyright (c) 2021 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class ConvexConvexIntersectionTest {
[Test]
public void shouldHandleSamePolygonIntersection() {
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[] intersection = ConvexConvexIntersection.intersect(p, q);
Assert.That(intersection.Length, Is.EqualTo(5 * 3));
Assert.That(intersection, Is.EqualTo(p));
}
[Test]
public void shouldHandleIntersection() {
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[] intersection = ConvexConvexIntersection.intersect(p, q);
Assert.That(intersection.Length, Is.EqualTo(5 * 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

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,66 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
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[][] HIT_POSITION =
{
new[] { 23.177608f, 10.197294f, -45.742954f },
new[] { 22.331268f, 10.197294f, -4.241272f },
new[] { 18.108675f, 15.743596f, -73.236839f },
new[] { 1.984785f, 10.197294f, -8.441269f },
new[] { -22.315216f, 4.997294f, -11.441269f }
};
private static readonly float[][] HIT_NORMAL =
{
new[] { -0.955779f, 0.0f, -0.29408592f },
new[] { 0.0f, 0.0f, 1.0f },
new[] { 0.97014254f, 0.0f, 0.24253564f },
new[] { -1.0f, 0.0f, 0.0f },
new[] { 1.0f, 0.0f, 0.0f }
};
[Test]
public void testFindDistanceToWall()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++)
{
float[] startPos = startPoss[i];
Result<FindDistanceToWallResult> result = query.findDistanceToWall(startRefs[i], startPos, 3.5f, filter);
FindDistanceToWallResult hit = result.result;
Assert.That(hit.getDistance(), Is.EqualTo(DISTANCES_TO_WALL[i]).Within(0.001f));
for (int v = 0; v < 3; v++)
{
Assert.That(hit.getPosition()[v], Is.EqualTo(HIT_POSITION[i][v]).Within(0.001f));
}
for (int v = 0; v < 3; v++)
{
Assert.That(hit.getNormal()[v], Is.EqualTo(HIT_NORMAL[i][v]).Within(0.001f));
}
}
}
}

View File

@ -0,0 +1,67 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindLocalNeighbourhoodTest : AbstractDetourTest
{
private static readonly long[][] REFS =
{
new[] { 281474976710696L, 281474976710695L, 281474976710691L, 281474976710697L },
new[] { 281474976710773L, 281474976710769L, 281474976710772L },
new[]
{
281474976710680L, 281474976710674L, 281474976710679L, 281474976710684L, 281474976710683L,
281474976710678L, 281474976710677L, 281474976710676L
},
new[] { 281474976710753L, 281474976710748L, 281474976710750L, 281474976710752L },
new[] { 281474976710733L, 281474976710735L, 281474976710736L }
};
private static readonly long[][] PARENT_REFS =
{
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L },
new[] { 0L, 281474976710773L, 281474976710773L },
new[]
{
0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710679L,
281474976710683L, 281474976710678L
},
new[] { 0L, 281474976710753L, 281474976710753L, 281474976710748L },
new[] { 0L, 281474976710733L, 281474976710733L }
};
[Test]
public void testFindNearestPoly()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++)
{
float[] startPos = startPoss[i];
Result<FindLocalNeighbourhoodResult> poly = query.findLocalNeighbourhood(startRefs[i], startPos, 3.5f,
filter);
Assert.That(poly.result.getRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < REFS[i].Length; v++)
{
Assert.That(poly.result.getRefs()[v], Is.EqualTo(REFS[i][v]));
}
}
}
}

View File

@ -0,0 +1,77 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindNearestPolyTest : AbstractDetourTest {
private static readonly long[] POLY_REFS = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L, 281474976710733L };
private static readonly float[][] POLY_POS = {
new[] { 22.606520f, 10.197294f, -45.918674f }, new[] { 22.331268f, 10.197294f, -1.040187f },
new[] { 18.694363f, 15.803535f, -73.090416f }, new[] { 0.745335f, 10.197294f, -5.940050f },
new[] { -20.651257f, 5.904126f, -13.712508f } };
[Test]
public void testFindNearestPoly() {
QueryFilter filter = new DefaultQueryFilter();
float[] extents = { 2, 4, 2 };
for (int i = 0; i < startRefs.Length; i++) {
float[] startPos = startPoss[i];
Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter);
Assert.That(poly.succeeded(), Is.True);
Assert.That(poly.result.getNearestRef(), Is.EqualTo(POLY_REFS[i]));
for (int v = 0; v < POLY_POS[i].Length; v++) {
Assert.That(poly.result.getNearestPos()[v], Is.EqualTo(POLY_POS[i][v]).Within(0.001f));
}
}
}
public class EmptyQueryFilter : QueryFilter
{
public bool passFilter(long refs, MeshTile tile, Poly poly)
{
return false;
}
public float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef, MeshTile curTile,
Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly)
{
return 0;
}
}
[Test]
public void shouldReturnStartPosWhenNoPolyIsValid() {
var filter = new EmptyQueryFilter();
float[] extents = { 2, 4, 2 };
for (int i = 0; i < startRefs.Length; i++) {
float[] startPos = startPoss[i];
Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter);
Assert.That(poly.succeeded(), Is.True);
Assert.That(0L, Is.EqualTo(poly.result.getNearestRef()));
for (int v = 0; v < POLY_POS[i].Length; v++) {
Assert.That(poly.result.getNearestPos()[v], Is.EqualTo(startPos[v]).Within(0.001f));
}
}
}
}

View File

@ -0,0 +1,158 @@
/*
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindPathTest : AbstractDetourTest {
private static readonly Status[] STATUSES = { Status.SUCCSESS, Status.PARTIAL_RESULT, Status.SUCCSESS, Status.SUCCSESS,
Status.SUCCSESS };
private static readonly long[][] RESULTS = {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L },
new[] { 281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L, 281474976710729L,
281474976710717L, 281474976710724L, 281474976710728L, 281474976710737L, 281474976710738L,
281474976710736L, 281474976710733L, 281474976710735L, 281474976710742L, 281474976710740L,
281474976710746L, 281474976710745L, 281474976710744L },
new[] { 281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
281474976710697L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710729L,
281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L,
281474976710754L, 281474976710768L, 281474976710772L, 281474976710773L, 281474976710770L,
281474976710757L, 281474976710761L, 281474976710758L },
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
new[] { 281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
281474976710724L, 281474976710717L, 281474976710729L, 281474976710731L, 281474976710752L,
281474976710748L, 281474976710753L, 281474976710755L, 281474976710754L, 281474976710768L,
281474976710772L } };
private static readonly StraightPathItem[][] STRAIGHT_PATHS = {
new[] { new StraightPathItem(new float[] { 22.606520f, 10.197294f, -45.918674f }, 1, 281474976710696L),
new StraightPathItem(new float[] { 3.484785f, 10.197294f, -34.241272f }, 0, 281474976710713L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -31.241272f }, 0, 281474976710712L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -29.741272f }, 0, 281474976710727L),
new StraightPathItem(new float[] { 2.584784f, 10.197294f, -27.941273f }, 0, 281474976710730L),
new StraightPathItem(new float[] { 6.457663f, 10.197294f, -18.334061f }, 2, 0L) },
new[] { new StraightPathItem(new float[] { 22.331268f, 10.197294f, -1.040187f }, 1, 281474976710773L),
new StraightPathItem(new float[] { 9.784786f, 10.197294f, -2.141273f }, 0, 281474976710755L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710752L),
new StraightPathItem(new float[] { -4.315216f, 10.197294f, -15.341270f }, 0, 281474976710724L),
new StraightPathItem(new float[] { -8.215216f, 10.197294f, -17.441269f }, 0, 281474976710728L),
new StraightPathItem(new float[] { -10.015216f, 10.197294f, -17.741272f }, 0, 281474976710738L),
new StraightPathItem(new float[] { -11.815216f, 9.997294f, -17.441269f }, 0, 281474976710736L),
new StraightPathItem(new float[] { -17.815216f, 5.197294f, -11.441269f }, 0, 281474976710735L),
new StraightPathItem(new float[] { -17.815216f, 5.197294f, -8.441269f }, 0, 281474976710746L),
new StraightPathItem(new float[] { -11.815216f, 0.197294f, 3.008419f }, 2, 0L) },
new[] { new StraightPathItem(new float[] { 18.694363f, 15.803535f, -73.090416f }, 1, 281474976710680L),
new StraightPathItem(new float[] { 17.584785f, 10.197294f, -49.841274f }, 0, 281474976710697L),
new StraightPathItem(new float[] { 17.284786f, 10.197294f, -48.041275f }, 0, 281474976710695L),
new StraightPathItem(new float[] { 16.084785f, 10.197294f, -45.341274f }, 0, 281474976710694L),
new StraightPathItem(new float[] { 3.484785f, 10.197294f, -34.241272f }, 0, 281474976710713L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -31.241272f }, 0, 281474976710712L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710755L),
new StraightPathItem(new float[] { 9.784786f, 10.197294f, -2.141273f }, 0, 281474976710768L),
new StraightPathItem(new float[] { 38.423977f, 10.197294f, -0.116067f }, 2, 0L) },
new[] { new StraightPathItem(new float[] { 0.745335f, 10.197294f, -5.940050f }, 1, 281474976710753L),
new StraightPathItem(new float[] { 0.863553f, 10.197294f, -10.310320f }, 2, 0L) },
new[] { new StraightPathItem(new float[] { -20.651257f, 5.904126f, -13.712508f }, 1, 281474976710733L),
new StraightPathItem(new float[] { -11.815216f, 9.997294f, -17.441269f }, 0, 281474976710738L),
new StraightPathItem(new float[] { -10.015216f, 10.197294f, -17.741272f }, 0, 281474976710728L),
new StraightPathItem(new float[] { -8.215216f, 10.197294f, -17.441269f }, 0, 281474976710724L),
new StraightPathItem(new float[] { -4.315216f, 10.197294f, -15.341270f }, 0, 281474976710729L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710755L),
new StraightPathItem(new float[] { 18.784092f, 10.197294f, 3.054368f }, 2, 0L) } };
[Test]
public void testFindPath() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
for (int j = 0; j < RESULTS[i].Length; j++) {
Assert.That(path.result[j], Is.EqualTo(RESULTS[i][j]));
}
}
}
[Test]
public void testFindPathSliced() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
query.initSlicedFindPath(startRef, endRef, startPos, endPos, filter, NavMeshQuery.DT_FINDPATH_ANY_ANGLE);
Status status = Status.IN_PROGRESS;
while (status == Status.IN_PROGRESS) {
Result<int> res = query.updateSlicedFindPath(10);
status = res.status;
}
Result<List<long>> path = query.finalizeSlicedFindPath();
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
for (int j = 0; j < RESULTS[i].Length; j++) {
Assert.That(path.result[j], Is.EqualTo(RESULTS[i][j]));
}
}
}
[Test]
public void testFindPathStraight() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < STRAIGHT_PATHS.Length; i++) {// startRefs.Length; i++) {
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Result<List<StraightPathItem>> result = query.findStraightPath(startPos, endPos, path.result,
int.MaxValue, 0);
List<StraightPathItem> straightPath = result.result;
Assert.That(straightPath.Count, Is.EqualTo(STRAIGHT_PATHS[i].Length));
for (int j = 0; j < STRAIGHT_PATHS[i].Length; j++) {
Assert.That(straightPath[j].refs, Is.EqualTo(STRAIGHT_PATHS[i][j].refs));
for (int v = 0; v < 3; v++) {
Assert.That(straightPath[j].pos[v], Is.EqualTo(STRAIGHT_PATHS[i][j].pos[v]).Within(0.01f));
}
Assert.That(straightPath[j].flags, Is.EqualTo(STRAIGHT_PATHS[i][j].flags));
}
}
}
}

View File

@ -0,0 +1,83 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindPolysAroundCircleTest : AbstractDetourTest {
private static readonly long[][] REFS = {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, 281474976710693L,
281474976710686L, 281474976710687L, 281474976710692L, 281474976710703L, 281474976710689L },
new[] { 281474976710773L, 281474976710770L, 281474976710769L, 281474976710772L, 281474976710771L },
new[] { 281474976710680L, 281474976710674L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710678L,
281474976710682L, 281474976710677L, 281474976710676L, 281474976710688L, 281474976710687L, 281474976710675L,
281474976710685L, 281474976710672L, 281474976710666L, 281474976710668L, 281474976710681L, 281474976710673L },
new[] { 281474976710753L, 281474976710748L, 281474976710755L, 281474976710756L, 281474976710750L, 281474976710752L,
281474976710731L, 281474976710729L, 281474976710749L, 281474976710719L, 281474976710717L, 281474976710726L },
new[] { 281474976710733L, 281474976710735L, 281474976710736L, 281474976710734L, 281474976710739L, 281474976710742L,
281474976710740L, 281474976710746L, 281474976710747L, } };
private static readonly long[][] PARENT_REFS = {
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710697L,
281474976710686L, 281474976710693L, 281474976710694L, 281474976710687L },
new[] { 0L, 281474976710773L, 281474976710773L, 281474976710773L, 281474976710772L },
new[] { 0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710679L, 281474976710683L,
281474976710683L, 281474976710678L, 281474976710684L, 281474976710688L, 281474976710677L, 281474976710687L,
281474976710682L, 281474976710672L, 281474976710672L, 281474976710675L, 281474976710666L },
new[] { 0L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710748L, 281474976710752L,
281474976710731L, 281474976710756L, 281474976710729L, 281474976710729L, 281474976710717L },
new[] { 0L, 281474976710733L, 281474976710733L, 281474976710736L, 281474976710736L, 281474976710735L, 281474976710742L,
281474976710740L, 281474976710746L } };
private static readonly float[][] COSTS = {
new[] { 0.000000f, 0.391453f, 6.764245f, 4.153431f, 3.721995f, 6.109188f, 5.378797f, 7.178796f, 7.009186f, 7.514245f,
12.655564f },
new[] { 0.000000f, 6.161580f, 2.824478f, 2.828730f, 8.035697f },
new[] { 0.000000f, 1.162604f, 1.954029f, 2.776051f, 2.046001f, 2.428367f, 6.429493f, 6.032851f, 2.878368f, 5.333885f,
6.394545f, 9.596563f, 12.457960f, 7.096575f, 10.413582f, 10.362305f, 10.665442f, 10.593861f },
new[] { 0.000000f, 2.483205f, 6.723722f, 5.727250f, 3.126022f, 3.543865f, 5.043865f, 6.843868f, 7.212173f, 10.602858f,
8.793867f, 13.146453f },
new[] { 0.000000f, 2.480514f, 0.823685f, 5.002500f, 8.229258f, 3.983844f, 5.483844f, 6.655379f, 11.996962f } };
[Test]
public void testFindPolysAroundCircle() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
long startRef = startRefs[i];
float[] startPos = startPoss[i];
Result<FindPolysAroundResult> result = query.findPolysAroundCircle(startRef, startPos, 7.5f, filter);
Assert.That(result.succeeded(), Is.True);
FindPolysAroundResult polys = result.result;
Assert.That(polys.getRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < REFS[i].Length; v++) {
bool found = false;
for (int w = 0; w < REFS[i].Length; w++) {
if (REFS[i][v] == polys.getRefs()[w]) {
Assert.That(polys.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
Assert.That(polys.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
found = true;
}
}
Assert.That(found, Is.True, $"Ref not found {REFS[i][v]}");
}
}
}
}

View File

@ -0,0 +1,131 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindPolysAroundShapeTest : AbstractDetourTest {
private static readonly long[][] REFS = {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L,
281474976710693L, 281474976710692L, 281474976710703L, 281474976710706L, 281474976710699L,
281474976710705L, 281474976710698L, 281474976710700L, 281474976710704L },
new[] { 281474976710773L, 281474976710769L, 281474976710772L, 281474976710768L, 281474976710771L,
281474976710754L, 281474976710755L, 281474976710753L, 281474976710751L, 281474976710756L,
281474976710749L },
new[] { 281474976710680L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710688L,
281474976710678L, 281474976710676L, 281474976710687L, 281474976710690L, 281474976710686L,
281474976710689L, 281474976710685L, 281474976710697L, 281474976710695L, 281474976710694L,
281474976710691L, 281474976710696L, 281474976710693L, 281474976710692L, 281474976710703L,
281474976710706L, 281474976710699L, 281474976710705L, 281474976710700L, 281474976710704L },
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
new[] { 281474976710733L, 281474976710735L, 281474976710736L, 281474976710742L, 281474976710734L,
281474976710739L, 281474976710738L, 281474976710740L, 281474976710746L, 281474976710743L,
281474976710745L, 281474976710741L, 281474976710747L, 281474976710737L, 281474976710732L,
281474976710728L, 281474976710724L, 281474976710744L, 281474976710725L, 281474976710717L,
281474976710729L, 281474976710726L, 281474976710721L, 281474976710719L, 281474976710731L,
281474976710720L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L,
281474976710756L, 281474976710750L, 281474976710749L, 281474976710754L, 281474976710751L,
281474976710768L, 281474976710772L, 281474976710773L, 281474976710771L, 281474976710769L } };
private static readonly long[][] PARENT_REFS = {
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L,
281474976710693L, 281474976710694L, 281474976710703L, 281474976710706L, 281474976710706L,
281474976710705L, 281474976710705L, 281474976710705L },
new[] { 0L, 281474976710773L, 281474976710773L, 281474976710772L, 281474976710772L, 281474976710768L,
281474976710754L, 281474976710755L, 281474976710755L, 281474976710753L, 281474976710756L },
new[] { 0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710684L, 281474976710679L,
281474976710678L, 281474976710688L, 281474976710687L, 281474976710687L, 281474976710687L,
281474976710687L, 281474976710686L, 281474976710697L, 281474976710695L, 281474976710695L,
281474976710695L, 281474976710695L, 281474976710693L, 281474976710694L, 281474976710703L,
281474976710706L, 281474976710706L, 281474976710705L, 281474976710705L },
new[] { 0L, 281474976710753L, 281474976710748L, 281474976710752L },
new[] { 0L, 281474976710733L, 281474976710733L, 281474976710735L, 281474976710736L, 281474976710736L,
281474976710736L, 281474976710742L, 281474976710740L, 281474976710746L, 281474976710746L,
281474976710746L, 281474976710746L, 281474976710738L, 281474976710738L, 281474976710737L,
281474976710728L, 281474976710745L, 281474976710724L, 281474976710724L, 281474976710717L,
281474976710717L, 281474976710717L, 281474976710729L, 281474976710729L, 281474976710721L,
281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710753L,
281474976710753L, 281474976710756L, 281474976710755L, 281474976710755L, 281474976710754L,
281474976710768L, 281474976710772L, 281474976710772L, 281474976710773L } };
private static readonly float[][] COSTS = {
new[] { 0.000000f, 16.188787f, 22.561579f, 19.950766f, 19.519329f, 21.906523f, 22.806520f, 23.311579f, 25.124035f,
28.454576f, 26.084503f, 36.438854f, 30.526634f, 31.942192f },
new[] { 0.000000f, 16.618738f, 12.136283f, 20.387646f, 17.343250f, 22.037645f, 22.787645f, 27.178831f, 26.501472f,
31.691311f, 33.176235f },
new[] { 0.000000f, 36.657764f, 35.197689f, 37.484924f, 37.755524f, 37.132103f, 37.582104f, 38.816185f, 52.426109f,
55.945839f, 51.882935f, 44.879601f, 57.745838f, 59.402641f, 65.063034f, 64.934372f, 62.733185f,
62.756744f, 63.656742f, 65.813034f, 67.625488f, 70.956032f, 68.585960f, 73.028091f, 74.443649f },
new[] { 0.000000f, 2.097958f, 3.158618f, 4.658618f },
new[] { 0.000000f, 20.495766f, 21.352942f, 21.999096f, 25.531757f, 28.758514f, 30.264732f, 23.499096f, 24.670631f,
33.166218f, 35.651184f, 34.371792f, 30.012215f, 33.886887f, 33.855347f, 34.643524f, 36.300327f,
38.203144f, 40.339203f, 40.203213f, 47.254810f, 50.043945f, 49.054485f, 49.804810f, 49.204811f,
52.813477f, 51.004814f, 52.504814f, 53.565475f, 62.748611f, 61.504147f, 57.915474f, 62.989071f,
67.139801f, 66.507599f, 67.889801f, 69.539803f, 77.791168f, 75.186256f, 83.111412f } };
[Test]
public void testFindPolysAroundShape() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
long startRef = startRefs[i];
float[] startPos = startPoss[i];
Result<FindPolysAroundResult> polys = query.findPolysAroundShape(startRef,
getQueryPoly(startPos, endPoss[i]), filter);
Assert.That(polys.result.getRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < REFS[i].Length; v++) {
bool found = false;
for (int w = 0; w < REFS[i].Length; w++) {
if (REFS[i][v] == polys.result.getRefs()[w]) {
Assert.That(polys.result.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
Assert.That(polys.result.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
found = true;
}
}
Assert.That(found, Is.True);
}
}
}
private float[] getQueryPoly(float[] m_spos, float[] m_epos) {
float nx = (m_epos[2] - m_spos[2]) * 0.25f;
float nz = -(m_epos[0] - m_spos[0]) * 0.25f;
float agentHeight = 2.0f;
float[] m_queryPoly = new float[12];
m_queryPoly[0] = m_spos[0] + nx * 1.2f;
m_queryPoly[1] = m_spos[1] + agentHeight / 2;
m_queryPoly[2] = m_spos[2] + nz * 1.2f;
m_queryPoly[3] = m_spos[0] - nx * 1.3f;
m_queryPoly[4] = m_spos[1] + agentHeight / 2;
m_queryPoly[5] = m_spos[2] - nz * 1.3f;
m_queryPoly[6] = m_epos[0] - nx * 0.8f;
m_queryPoly[7] = m_epos[1] + agentHeight / 2;
m_queryPoly[8] = m_epos[2] - nz * 0.8f;
m_queryPoly[9] = m_epos[0] + nx;
m_queryPoly[10] = m_epos[1] + agentHeight / 2;
m_queryPoly[11] = m_epos[2] + nz;
return m_queryPoly;
}
}

View File

@ -0,0 +1,75 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class GetPolyWallSegmentsTest : AbstractDetourTest {
private static readonly float[][] VERTICES = {
new[] { 22.084785f, 10.197294f, -48.341274f, 22.684784f, 10.197294f, -44.141273f, 22.684784f, 10.197294f,
-44.141273f, 23.884785f, 10.197294f, -48.041275f, 23.884785f, 10.197294f, -48.041275f, 22.084785f,
10.197294f, -48.341274f },
new[] { 27.784786f, 10.197294f, 4.158730f, 28.384785f, 10.197294f, 2.358727f, 28.384785f, 10.197294f, 2.358727f,
28.384785f, 10.197294f, -2.141273f, 28.384785f, 10.197294f, -2.141273f, 27.784786f, 10.197294f,
-2.741272f, 27.784786f, 10.197294f, -2.741272f, 19.684784f, 10.197294f, -4.241272f, 19.684784f,
10.197294f, -4.241272f, 19.684784f, 10.197294f, 4.158730f, 19.684784f, 10.197294f, 4.158730f,
27.784786f, 10.197294f, 4.158730f },
new[] { 22.384785f, 14.997294f, -71.741272f, 19.084785f, 16.597294f, -74.741272f, 19.084785f, 16.597294f,
-74.741272f, 18.184784f, 15.997294f, -73.541275f, 18.184784f, 15.997294f, -73.541275f, 17.884785f,
14.997294f, -72.341278f, 17.884785f, 14.997294f, -72.341278f, 17.584785f, 14.997294f, -70.841278f,
17.584785f, 14.997294f, -70.841278f, 22.084785f, 14.997294f, -70.541275f, 22.084785f, 14.997294f,
-70.541275f, 22.384785f, 14.997294f, -71.741272f },
new[] { 4.684784f, 10.197294f, -6.941269f, 1.984785f, 10.197294f, -8.441269f, 1.984785f, 10.197294f, -8.441269f,
-4.015217f, 10.197294f, -6.941269f, -4.015217f, 10.197294f, -6.941269f, -1.615215f, 10.197294f,
-1.541275f, -1.615215f, 10.197294f, -1.541275f, 1.384785f, 10.197294f, 1.458725f, 1.384785f,
10.197294f, 1.458725f, 7.984783f, 10.197294f, -2.441269f, 7.984783f, 10.197294f, -2.441269f,
4.684784f, 10.197294f, -6.941269f },
new[] { -22.315216f, 6.597294f, -17.141273f, -23.815216f, 5.397294f, -13.841270f, -23.815216f, 5.397294f,
-13.841270f, -24.115217f, 4.997294f, -12.041275f, -24.115217f, 4.997294f, -12.041275f, -22.315216f,
4.997294f, -11.441269f, -22.315216f, 4.997294f, -11.441269f, -17.815216f, 5.197294f, -11.441269f,
-17.815216f, 5.197294f, -11.441269f, -22.315216f, 6.597294f, -17.141273f } };
private static readonly long[][] REFS = {
new[] { 281474976710695L, 0L, 0L },
new[] { 0L, 281474976710770L, 0L, 281474976710769L, 281474976710772L, 0L },
new[] { 281474976710683L, 281474976710674L, 0L, 281474976710679L, 281474976710684L, 0L },
new[] { 281474976710750L, 281474976710748L, 0L, 0L, 281474976710755L, 281474976710756L },
new[] { 0L, 0L, 0L, 281474976710735L, 281474976710736L } };
[Test]
public void testFindDistanceToWall() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
Result<GetPolyWallSegmentsResult> result = query.getPolyWallSegments(startRefs[i], true, filter);
GetPolyWallSegmentsResult segments = result.result;
Assert.That(segments.getSegmentVerts().Count, Is.EqualTo(VERTICES[i].Length / 6));
Assert.That(segments.getSegmentRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < VERTICES[i].Length / 6; v++) {
for (int n = 0; n < 6; n++) {
Assert.That(segments.getSegmentVerts()[v][n], Is.EqualTo(VERTICES[i][v * 6 + n]).Within(0.001f));
}
}
for (int v = 0; v < REFS[i].Length; v++) {
Assert.That(segments.getSegmentRefs()[v], Is.EqualTo(REFS[i][v]));
}
}
}
}

View File

@ -0,0 +1,118 @@
/*
recast4j Copyright (c) 2015-2019 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.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
using NUnit.Framework;
namespace DotRecast.Detour.Test.Io;
public class MeshDataReaderWriterTest {
private const int VERTS_PER_POLYGON = 6;
private MeshData meshData;
[SetUp]
public void setUp() {
RecastTestMeshBuilder rcBuilder = new RecastTestMeshBuilder();
meshData = rcBuilder.getMeshData();
}
[Test]
public void testCCompatibility() {
test(true, ByteOrder.BIG_ENDIAN);
}
[Test]
public void testCompact() {
test(false, ByteOrder.BIG_ENDIAN);
}
[Test]
public void testCCompatibilityLE() {
test(true, ByteOrder.LITTLE_ENDIAN);
}
[Test]
public void testCompactLE() {
test(false, ByteOrder.LITTLE_ENDIAN);
}
public void test(bool cCompatibility, ByteOrder order) {
using var ms = new MemoryStream();
using var bwos = new BinaryWriter(ms);
MeshDataWriter writer = new MeshDataWriter();
writer.write(bwos, meshData, order, cCompatibility);
ms.Seek(0, SeekOrigin.Begin);
using var bris = new BinaryReader(ms);
MeshDataReader reader = new MeshDataReader();
MeshData readData = reader.read(bris, VERTS_PER_POLYGON);
Assert.That(readData.header.vertCount, Is.EqualTo(meshData.header.vertCount));
Assert.That(readData.header.polyCount, Is.EqualTo(meshData.header.polyCount));
Assert.That(readData.header.detailMeshCount, Is.EqualTo(meshData.header.detailMeshCount));
Assert.That(readData.header.detailTriCount, Is.EqualTo(meshData.header.detailTriCount));
Assert.That(readData.header.detailVertCount, Is.EqualTo(meshData.header.detailVertCount));
Assert.That(readData.header.bvNodeCount, Is.EqualTo(meshData.header.bvNodeCount));
Assert.That(readData.header.offMeshConCount, Is.EqualTo(meshData.header.offMeshConCount));
for (int i = 0; i < meshData.header.vertCount; i++) {
Assert.That(readData.verts[i], Is.EqualTo(meshData.verts[i]));
}
for (int i = 0; i < meshData.header.polyCount; i++) {
Assert.That(readData.polys[i].vertCount, Is.EqualTo(meshData.polys[i].vertCount));
Assert.That(readData.polys[i].areaAndtype, Is.EqualTo(meshData.polys[i].areaAndtype));
for (int j = 0; j < meshData.polys[i].vertCount; j++) {
Assert.That(readData.polys[i].verts[j], Is.EqualTo(meshData.polys[i].verts[j]));
Assert.That(readData.polys[i].neis[j], Is.EqualTo(meshData.polys[i].neis[j]));
}
}
for (int i = 0; i < meshData.header.detailMeshCount; i++) {
Assert.That(readData.detailMeshes[i].vertBase, Is.EqualTo(meshData.detailMeshes[i].vertBase));
Assert.That(readData.detailMeshes[i].vertCount, Is.EqualTo(meshData.detailMeshes[i].vertCount));
Assert.That(readData.detailMeshes[i].triBase, Is.EqualTo(meshData.detailMeshes[i].triBase));
Assert.That(readData.detailMeshes[i].triCount, Is.EqualTo(meshData.detailMeshes[i].triCount));
}
for (int i = 0; i < meshData.header.detailVertCount; i++) {
Assert.That(readData.detailVerts[i], Is.EqualTo(meshData.detailVerts[i]));
}
for (int i = 0; i < meshData.header.detailTriCount; i++) {
Assert.That(readData.detailTris[i], Is.EqualTo(meshData.detailTris[i]));
}
for (int i = 0; i < meshData.header.bvNodeCount; i++) {
Assert.That(readData.bvTree[i].i, Is.EqualTo(meshData.bvTree[i].i));
for (int j = 0; j < 3; j++) {
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++) {
Assert.That(readData.offMeshCons[i].flags, Is.EqualTo(meshData.offMeshCons[i].flags));
Assert.That(readData.offMeshCons[i].rad, Is.EqualTo(meshData.offMeshCons[i].rad));
Assert.That(readData.offMeshCons[i].poly, Is.EqualTo(meshData.offMeshCons[i].poly));
Assert.That(readData.offMeshCons[i].side, Is.EqualTo(meshData.offMeshCons[i].side));
Assert.That(readData.offMeshCons[i].userId, Is.EqualTo(meshData.offMeshCons[i].userId));
for (int j = 0; j < 6; j++) {
Assert.That(readData.offMeshCons[i].pos[j], Is.EqualTo(meshData.offMeshCons[i].pos[j]));
}
}
}
}

View File

@ -0,0 +1,113 @@
/*
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using System.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
using NUnit.Framework;
namespace DotRecast.Detour.Test.Io;
public class MeshSetReaderTest {
private readonly MeshSetReader reader = new MeshSetReader();
[Test]
public void testNavmesh() {
byte[] @is = Loader.ToBytes("all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms);
NavMesh mesh = reader.read(bris, 6);
Assert.That(mesh.getMaxTiles(), Is.EqualTo(128));
Assert.That(mesh.getParams().maxPolys, Is.EqualTo(0x8000));
Assert.That(mesh.getParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f));
List<MeshTile> tiles = mesh.getTilesAt(4, 7);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(7));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(22 * 3));
tiles = mesh.getTilesAt(1, 6);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(7));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(26 * 3));
tiles = mesh.getTilesAt(6, 2);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(1));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(4 * 3));
tiles = mesh.getTilesAt(7, 6);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(8));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(24 * 3));
}
[Test]
public void testDungeon() {
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms);
NavMesh mesh = reader.read(bris, 6);
Assert.That(mesh.getMaxTiles(), Is.EqualTo(128));
Assert.That(mesh.getParams().maxPolys, Is.EqualTo(0x8000));
Assert.That(mesh.getParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f));
List<MeshTile> tiles = mesh.getTilesAt(6, 9);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3));
tiles = mesh.getTilesAt(2, 9);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3));
tiles = mesh.getTilesAt(4, 3);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3));
tiles = mesh.getTilesAt(2, 8);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
}
[Test]
public void testDungeon32Bit() {
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh_32bit.bin");
using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms);
NavMesh mesh = reader.read32Bit(bris, 6);
Assert.That(mesh.getMaxTiles(), Is.EqualTo(128));
Assert.That(mesh.getParams().maxPolys, Is.EqualTo(0x8000));
Assert.That(mesh.getParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f));
List<MeshTile> tiles = mesh.getTilesAt(6, 9);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3));
tiles = mesh.getTilesAt(2, 9);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3));
tiles = mesh.getTilesAt(4, 3);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3));
tiles = mesh.getTilesAt(2, 8);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
}
}

View File

@ -0,0 +1,120 @@
/*
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using System.IO;
using DotRecast.Core;
using DotRecast.Detour.Io;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using NUnit.Framework;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Test.Io;
public class MeshSetReaderWriterTest {
private readonly MeshSetWriter writer = new MeshSetWriter();
private readonly MeshSetReader reader = new MeshSetReader();
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f;
private const float m_agentRadius = 0.6f;
private const float m_agentMaxClimb = 0.9f;
private const float m_agentMaxSlope = 45.0f;
private const int m_regionMinSize = 8;
private const int m_regionMergeSize = 20;
private const float m_regionMinArea = m_regionMinSize * m_regionMinSize * m_cellSize * m_cellSize;
private const float m_regionMergeArea = m_regionMergeSize * m_regionMergeSize * m_cellSize * m_cellSize;
private const float m_edgeMaxLen = 12.0f;
private const float m_edgeMaxError = 1.3f;
private const int m_vertsPerPoly = 6;
private const float m_detailSampleDist = 6.0f;
private const float m_detailSampleMaxError = 1.0f;
private const int m_tileSize = 32;
private const int m_maxTiles = 128;
private const int m_maxPolysPerTile = 0x8000;
[Test]
public void test() {
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
NavMeshSetHeader header = new NavMeshSetHeader();
header.magic = NavMeshSetHeader.NAVMESHSET_MAGIC;
header.version = NavMeshSetHeader.NAVMESHSET_VERSION;
vCopy(header.option.orig, geom.getMeshBoundsMin());
header.option.tileWidth = m_tileSize * m_cellSize;
header.option.tileHeight = m_tileSize * m_cellSize;
header.option.maxTiles = m_maxTiles;
header.option.maxPolys = m_maxPolysPerTile;
header.numTiles = 0;
NavMesh mesh = new NavMesh(header.option, 6);
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
int[] twh = DotRecast.Recast.Recast.calcTileCount(bmin, bmax, m_cellSize, m_tileSize, m_tileSize);
int tw = twh[0];
int th = twh[1];
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize,
RecastConfig.calcBorder(m_agentRadius, m_cellSize), PartitionType.WATERSHED, m_cellSize, m_cellHeight,
m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinArea,
m_regionMergeArea, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, true, m_detailSampleDist,
m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, bmin, bmax, x, y);
TestDetourBuilder db = new TestDetourBuilder();
MeshData data = db.build(geom, bcfg, m_agentHeight, m_agentRadius, m_agentMaxClimb, x, y, true);
if (data != null) {
mesh.removeTile(mesh.getTileRefAt(x, y, 0));
mesh.addTile(data, 0, 0);
}
}
}
using var ms = new MemoryStream();
using var os = new BinaryWriter(ms);
writer.write(os, mesh, ByteOrder.LITTLE_ENDIAN, true);
ms.Seek(0, SeekOrigin.Begin);
using var @is = new BinaryReader(ms);
mesh = reader.read(@is, 6);
Assert.That(mesh.getMaxTiles(), Is.EqualTo(128));
Assert.That(mesh.getParams().maxPolys, Is.EqualTo(0x8000));
Assert.That(mesh.getParams().tileWidth, Is.EqualTo(9.6f).Within(0.001f));
List<MeshTile> tiles = mesh.getTilesAt(6, 9);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(7 * 3));
tiles = mesh.getTilesAt(2, 9);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(2));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(9 * 3));
tiles = mesh.getTilesAt(4, 3);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(3));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(6 * 3));
tiles = mesh.getTilesAt(2, 8);
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
}
}

View File

@ -0,0 +1,68 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class MoveAlongSurfaceTest : AbstractDetourTest {
private static readonly long[][] VISITED = {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L },
new[] { 281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
281474976710753L },
new[] { 281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
281474976710697L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L,
281474976710718L },
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
new[] { 281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
281474976710724L, 281474976710717L, 281474976710729L, 281474976710731L, 281474976710752L,
281474976710748L, 281474976710753L, 281474976710755L, 281474976710754L, 281474976710768L,
281474976710772L } };
private static readonly float[][] POSITION = {
new[] { 6.457663f, 10.197294f, -18.334061f },
new[] { -1.433933f, 10.197294f, -1.359993f },
new[] { 12.184784f, 9.997294f, -18.941269f },
new[] { 0.863553f, 10.197294f, -10.310320f },
new[] { 18.784092f, 10.197294f, 3.054368f } };
[Test]
public void testMoveAlongSurface() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
long startRef = startRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
Result<MoveAlongSurfaceResult> result = query.moveAlongSurface(startRef, startPos, endPos, filter);
Assert.That(result.succeeded(), Is.True);
MoveAlongSurfaceResult path = result.result;
for (int v = 0; v < 3; v++) {
Assert.That(path.getResultPos()[v], Is.EqualTo(POSITION[i][v]).Within(0.01f));
}
Assert.That(path.getVisited().Count, Is.EqualTo(VISITED[i].Length));
for (int j = 0; j < POSITION[i].Length; j++) {
Assert.That(path.getVisited()[j], Is.EqualTo(VISITED[i][j]));
}
}
}
}

View File

@ -0,0 +1,64 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class NavMeshBuilderTest {
private MeshData nmd;
[SetUp]
public void setUp() {
nmd = new RecastTestMeshBuilder().getMeshData();
}
[Test]
public void testBVTree() {
Assert.That(nmd.verts.Length / 3, Is.EqualTo(225));
Assert.That(nmd.polys.Length, Is.EqualTo(119));
Assert.That(nmd.header.maxLinkCount, Is.EqualTo(457));
Assert.That(nmd.detailMeshes.Length, Is.EqualTo(118));
Assert.That(nmd.detailTris.Length / 4, Is.EqualTo(291));
Assert.That(nmd.detailVerts.Length / 3, Is.EqualTo(60));
Assert.That(nmd.offMeshCons.Length, Is.EqualTo(1));
Assert.That(nmd.header.offMeshBase, Is.EqualTo(118));
Assert.That(nmd.bvTree.Length, Is.EqualTo(236));
Assert.That(nmd.bvTree.Length, Is.GreaterThanOrEqualTo(nmd.header.bvNodeCount));
for (int i = 0; i < nmd.header.bvNodeCount; i++) {
Assert.That(nmd.bvTree[i], Is.Not.Null);
}
for (int i = 0; i < 6; 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].poly, Is.EqualTo(118));
Assert.That(nmd.offMeshCons[0].flags, Is.EqualTo(NavMesh.DT_OFFMESH_CON_BIDIR));
Assert.That(nmd.offMeshCons[0].side, Is.EqualTo(0xFF));
Assert.That(nmd.offMeshCons[0].userId, Is.EqualTo(0x4567));
Assert.That(nmd.polys[118].vertCount, Is.EqualTo(2));
Assert.That(nmd.polys[118].verts[0], Is.EqualTo(223));
Assert.That(nmd.polys[118].verts[1], Is.EqualTo(224));
Assert.That(nmd.polys[118].flags, Is.EqualTo(12));
Assert.That(nmd.polys[118].getArea(), Is.EqualTo(2));
Assert.That(nmd.polys[118].getType(), Is.EqualTo(Poly.DT_POLYTYPE_OFFMESH_CONNECTION));
}
}

View File

@ -0,0 +1,87 @@
/*
recast4j copyright (c) 2021 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 NUnit.Framework;
namespace DotRecast.Detour.Test;
public class PolygonByCircleConstraintTest {
private readonly PolygonByCircleConstraint constraint = new PolygonByCircleConstraint.StrictPolygonByCircleConstraint();
[Test]
public void shouldHandlePolygonFullyInsideCircle() {
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
float[] center = { 1, 0, 1 };
float[] constrained = constraint.aply(polygon, center, 6);
Assert.That(constrained, Is.EqualTo(polygon));
}
[Test]
public void shouldHandleVerticalSegment() {
int expectedSize = 21;
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
float[] center = { 2, 0, 0 };
float[] constrained = constraint.aply(polygon, center, 3);
Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained, Is.SupersetOf(new[] {2f, 0f, 2f, 2f, 0f, -2f}));
}
[Test]
public void shouldHandleCircleFullyInsidePolygon() {
int expectedSize = 12 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] center = { -1, 0, -1 };
float[] constrained = constraint.aply(polygon, center, 2);
Assert.That(constrained.Length, Is.EqualTo(expectedSize));
for (int i = 0; i < expectedSize; i += 3) {
float x = constrained[i] + 1;
float z = constrained[i + 2] + 1;
Assert.That(x * x + z * z, Is.EqualTo(4).Within(1e-4f));
}
}
[Test]
public void shouldHandleCircleInsidePolygon() {
int expectedSize = 9 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] center = { -2, 0, -1 };
float[] constrained = constraint.aply(polygon, center, 3);
Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained, Is.SupersetOf(new[] { -2f, 0f, -4f, -4f, 0f, 0f, -3.4641016f, 0.0f, 1.6076951f, -2.0f, 0.0f, 2.0f }));
}
[Test]
public void shouldHandleCircleOutsidePolygon()
{
int expectedSize = 7 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] center = { 4, 0, 0 };
float[] constrained = constraint.aply(polygon, center, 4);
Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained, Is.SupersetOf(new[] { 1.5358982f, 0f, 3f, 2f, 0f, 3f, 3f, 0f, -3f }));
}
}

View File

@ -0,0 +1,123 @@
/*
recast4j Copyright (c) 2015-2021 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.Diagnostics;
using NUnit.Framework;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Test;
public class RandomPointTest : AbstractDetourTest {
[Test]
public void testRandom() {
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < 1000; i++) {
Result<FindRandomPointResult> point = query.findRandomPoint(filter, f);
Assert.That(point.succeeded(), Is.True);
Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.result.getRandomRef()).result;
float[] bmin = new float[2];
float[] bmax = new float[2];
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++) {
int v = tileAndPoly.Item2.verts[j] * 3;
bmin[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Min(bmin[0], tileAndPoly.Item1.data.verts[v]);
bmax[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Max(bmax[0], tileAndPoly.Item1.data.verts[v]);
bmin[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Min(bmin[1], tileAndPoly.Item1.data.verts[v + 2]);
bmax[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Max(bmax[1], tileAndPoly.Item1.data.verts[v + 2]);
}
Assert.That(point.result.getRandomPt()[0] >= bmin[0], Is.True);
Assert.That(point.result.getRandomPt()[0] <= bmax[0], Is.True);
Assert.That(point.result.getRandomPt()[2] >= bmin[1], Is.True);
Assert.That(point.result.getRandomPt()[2] <= bmax[1], Is.True);
}
}
[Test]
public void testRandomAroundCircle() {
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
for (int i = 0; i < 1000; i++) {
Result<FindRandomPointResult> result = query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(),
5f, filter, f);
Assert.That(result.failed(), Is.False);
point = result.result;
Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.getRandomRef()).result;
float[] bmin = new float[2];
float[] bmax = new float[2];
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++) {
int v = tileAndPoly.Item2.verts[j] * 3;
bmin[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Min(bmin[0], tileAndPoly.Item1.data.verts[v]);
bmax[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Max(bmax[0], tileAndPoly.Item1.data.verts[v]);
bmin[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Min(bmin[1], tileAndPoly.Item1.data.verts[v + 2]);
bmax[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Max(bmax[1], tileAndPoly.Item1.data.verts[v + 2]);
}
Assert.That(point.getRandomPt()[0] >= bmin[0], Is.True);
Assert.That(point.getRandomPt()[0] <= bmax[0], Is.True);
Assert.That(point.getRandomPt()[2] >= bmin[1], Is.True);
Assert.That(point.getRandomPt()[2] <= bmax[1], Is.True);
}
}
[Test]
public void testRandomWithinCircle() {
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
float radius = 5f;
for (int i = 0; i < 1000; i++) {
Result<FindRandomPointResult> result = query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(),
radius, filter, f);
Assert.That(result.failed(), Is.False);
float distance = vDist2D(point.getRandomPt(), result.result.getRandomPt());
Assert.That(distance <= radius, Is.True);
point = result.result;
}
}
[Test]
public void testPerformance() {
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
float radius = 5f;
// jvm warmup
for (int i = 0; i < 1000; i++) {
query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
}
for (int i = 0; i < 1000; i++) {
query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
}
long t1 = Stopwatch.GetTimestamp();
for (int i = 0; i < 10000; i++) {
query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
}
long t2 = Stopwatch.GetTimestamp();
for (int i = 0; i < 10000; i++) {
query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
}
long t3 = Stopwatch.GetTimestamp();
Console.WriteLine("Random point around circle: " + (t2 - t1) / 1000000 + "ms");
Console.WriteLine("Random point within circle: " + (t3 - t2) / 1000000 + "ms");
}
}

View File

@ -0,0 +1,111 @@
/*
recast4j Copyright (c) 2015-2019 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 DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Test;
public class RecastTestMeshBuilder {
private readonly MeshData meshData;
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f;
private const float m_agentRadius = 0.6f;
private const float m_agentMaxClimb = 0.9f;
private const float m_agentMaxSlope = 45.0f;
private const int m_regionMinSize = 8;
private const int m_regionMergeSize = 20;
private const float m_edgeMaxLen = 12.0f;
private const float m_edgeMaxError = 1.3f;
private const int m_vertsPerPoly = 6;
private const float m_detailSampleDist = 6.0f;
private const float m_detailSampleMaxError = 1.0f;
public RecastTestMeshBuilder() :
this(ObjImporter.load(Loader.ToBytes("dungeon.obj")),
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope,
m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist,
m_detailSampleMaxError)
{
}
public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError) {
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax());
RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
PolyMesh m_pmesh = rcResult.getMesh();
for (int i = 0; i < m_pmesh.npolys; ++i) {
m_pmesh.flags[i] = 1;
}
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = m_pmesh.verts;
option.vertCount = m_pmesh.nverts;
option.polys = m_pmesh.polys;
option.polyAreas = m_pmesh.areas;
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
option.detailMeshes = m_dmesh.meshes;
option.detailVerts = m_dmesh.verts;
option.detailVertsCount = m_dmesh.nverts;
option.detailTris = m_dmesh.tris;
option.detailTriCount = m_dmesh.ntris;
option.walkableHeight = m_agentHeight;
option.walkableRadius = m_agentRadius;
option.walkableClimb = m_agentMaxClimb;
option.bmin = m_pmesh.bmin;
option.bmax = m_pmesh.bmax;
option.cs = m_cellSize;
option.ch = m_cellHeight;
option.buildBvTree = true;
option.offMeshConVerts = new float[6];
option.offMeshConVerts[0] = 0.1f;
option.offMeshConVerts[1] = 0.2f;
option.offMeshConVerts[2] = 0.3f;
option.offMeshConVerts[3] = 0.4f;
option.offMeshConVerts[4] = 0.5f;
option.offMeshConVerts[5] = 0.6f;
option.offMeshConRad = new float[1];
option.offMeshConRad[0] = 0.1f;
option.offMeshConDir = new int[1];
option.offMeshConDir[0] = 1;
option.offMeshConAreas = new int[1];
option.offMeshConAreas[0] = 2;
option.offMeshConFlags = new int[1];
option.offMeshConFlags[0] = 12;
option.offMeshConUserID = new int[1];
option.offMeshConUserID[0] = 0x4567;
option.offMeshConCount = 1;
meshData = NavMeshBuilder.createNavMeshData(option);
}
public MeshData getMeshData() {
return meshData;
}
}

View File

@ -0,0 +1,52 @@
/*
recast4j Copyright (c) 2015-2019 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 DotRecast.Recast;
namespace DotRecast.Detour.Test;
public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x3;
public const int SAMPLE_POLYAREA_TYPE_DOOR = 0x4;
public const int SAMPLE_POLYAREA_TYPE_GRASS = 0x5;
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP);
public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road)
public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water).
public const int SAMPLE_POLYFLAGS_DOOR = 0x04; // Ability to move through doors.
public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
}

View File

@ -0,0 +1,91 @@
/*
recast4j Copyright (c) 2015-2019 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 DotRecast.Recast;
using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Test;
public class TestDetourBuilder : DetourBuilder {
public MeshData build(InputGeomProvider geom, RecastBuilderConfig rcConfig, float agentHeight, float agentRadius,
float agentMaxClimb, int x, int y, bool applyRecastDemoFlags) {
RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(geom, rcConfig);
PolyMesh pmesh = rcResult.getMesh();
if (applyRecastDemoFlags) {
// Update poly flags from areas.
for (int i = 0; i < pmesh.npolys; ++i) {
if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND
|| pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS
|| pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD) {
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
} else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER) {
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
} else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR) {
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK
| SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
}
if (pmesh.areas[i] > 0) {
pmesh.areas[i]--;
}
}
}
PolyMeshDetail dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = getNavMeshCreateParams(rcConfig.cfg, pmesh, dmesh, agentHeight, agentRadius,
agentMaxClimb);
return build(option, x, y);
}
public NavMeshDataCreateParams getNavMeshCreateParams(RecastConfig rcConfig, PolyMesh pmesh, PolyMeshDetail dmesh,
float agentHeight, float agentRadius, float agentMaxClimb) {
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = pmesh.verts;
option.vertCount = pmesh.nverts;
option.polys = pmesh.polys;
option.polyAreas = pmesh.areas;
option.polyFlags = pmesh.flags;
option.polyCount = pmesh.npolys;
option.nvp = pmesh.nvp;
if (dmesh != null) {
option.detailMeshes = dmesh.meshes;
option.detailVerts = dmesh.verts;
option.detailVertsCount = dmesh.nverts;
option.detailTris = dmesh.tris;
option.detailTriCount = dmesh.ntris;
}
option.walkableHeight = agentHeight;
option.walkableRadius = agentRadius;
option.walkableClimb = agentMaxClimb;
option.bmin = pmesh.bmin;
option.bmax = pmesh.bmax;
option.cs = rcConfig.cs;
option.ch = rcConfig.ch;
option.buildBvTree = true;
/*
* option.offMeshConVerts = m_geom->getOffMeshConnectionVerts(); option.offMeshConRad =
* m_geom->getOffMeshConnectionRads(); option.offMeshConDir = m_geom->getOffMeshConnectionDirs();
* option.offMeshConAreas = m_geom->getOffMeshConnectionAreas(); option.offMeshConFlags =
* m_geom->getOffMeshConnectionFlags(); option.offMeshConUserID = m_geom->getOffMeshConnectionId();
* option.offMeshConCount = m_geom->getOffMeshConnectionCount();
*/
return option;
}
}

View File

@ -0,0 +1,120 @@
/*
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using static DotRecast.Recast.RecastVectors;
namespace DotRecast.Detour.Test;
public class TestTiledNavMeshBuilder {
private readonly NavMesh navMesh;
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f;
private const float m_agentRadius = 0.6f;
private const float m_agentMaxClimb = 0.9f;
private const float m_agentMaxSlope = 45.0f;
private const int m_regionMinSize = 8;
private const int m_regionMergeSize = 20;
private const float m_regionMinArea = m_regionMinSize * m_regionMinSize * m_cellSize * m_cellSize;
private const float m_regionMergeArea = m_regionMergeSize * m_regionMergeSize * m_cellSize * m_cellSize;
private const float m_edgeMaxLen = 12.0f;
private const float m_edgeMaxError = 1.3f;
private const int m_vertsPerPoly = 6;
private const float m_detailSampleDist = 6.0f;
private const float m_detailSampleMaxError = 1.0f;
private const int m_tileSize = 32;
public TestTiledNavMeshBuilder() :
this(ObjImporter.load(Loader.ToBytes("dungeon.obj")),
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope,
m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist,
m_detailSampleMaxError, m_tileSize)
{
}
public TestTiledNavMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize, float m_cellHeight,
float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope, int m_regionMinSize,
int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly, float m_detailSampleDist,
float m_detailSampleMaxError, int m_tileSize) {
// Create empty nav mesh
NavMeshParams navMeshParams = new NavMeshParams();
copy(navMeshParams.orig, m_geom.getMeshBoundsMin());
navMeshParams.tileWidth = m_tileSize * m_cellSize;
navMeshParams.tileHeight = m_tileSize * m_cellSize;
navMeshParams.maxTiles = 128;
navMeshParams.maxPolys = 32768;
navMesh = new NavMesh(navMeshParams, 6);
// Build all tiles
RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_regionMinArea, m_regionMergeArea, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, true,
m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilder rcBuilder = new RecastBuilder();
List<RecastBuilderResult> rcResult = rcBuilder.buildTiles(m_geom, cfg, null);
// Add tiles to nav mesh
foreach (RecastBuilderResult result in rcResult) {
PolyMesh pmesh = result.getMesh();
if (pmesh.npolys == 0) {
continue;
}
for (int i = 0; i < pmesh.npolys; ++i) {
pmesh.flags[i] = 1;
}
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = pmesh.verts;
option.vertCount = pmesh.nverts;
option.polys = pmesh.polys;
option.polyAreas = pmesh.areas;
option.polyFlags = pmesh.flags;
option.polyCount = pmesh.npolys;
option.nvp = pmesh.nvp;
PolyMeshDetail dmesh = result.getMeshDetail();
option.detailMeshes = dmesh.meshes;
option.detailVerts = dmesh.verts;
option.detailVertsCount = dmesh.nverts;
option.detailTris = dmesh.tris;
option.detailTriCount = dmesh.ntris;
option.walkableHeight = m_agentHeight;
option.walkableRadius = m_agentRadius;
option.walkableClimb = m_agentMaxClimb;
option.bmin = pmesh.bmin;
option.bmax = pmesh.bmax;
option.cs = m_cellSize;
option.ch = m_cellHeight;
option.tileX = result.tileX;
option.tileZ = result.tileZ;
option.buildBvTree = true;
navMesh.addTile(NavMeshBuilder.createNavMeshData(option), 0, 0);
}
}
public NavMesh getNavMesh() {
return navMesh;
}
}

View File

@ -0,0 +1,69 @@
/*
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class TiledFindPathTest {
private static readonly Status[] STATUSES = { Status.SUCCSESS };
private static readonly long[][] RESULTS = {
new[] { 281475015507969L, 281475014459393L, 281475014459392L, 281475006070784L,
281475005022208L, 281475003973636L, 281475012362240L, 281475012362241L, 281475012362242L, 281475003973634L,
281475003973635L, 281475003973633L, 281475002925059L, 281475002925057L, 281475002925056L, 281474998730753L,
281474998730754L, 281474994536450L, 281474994536451L, 281474994536452L, 281474994536448L, 281474990342146L,
281474990342145L, 281474991390723L, 281474991390724L, 281474991390725L, 281474987196418L, 281474987196417L,
281474988244996L, 281474988244995L, 281474988244997L, 281474985099266L } };
protected static readonly long[] START_REFS = { 281475015507969L };
protected static readonly long[] END_REFS = { 281474985099266L };
protected static readonly float[][] START_POS = { new[] { 39.447338f, 9.998177f, -0.784811f } };
protected static readonly float[][] END_POS = { new[] { 19.292645f, 11.611748f, -57.750366f } };
protected NavMeshQuery query;
protected NavMesh navmesh;
[SetUp]
public void setUp() {
navmesh = createNavMesh();
query = new NavMeshQuery(navmesh);
}
protected NavMesh createNavMesh() {
return new TestTiledNavMeshBuilder().getNavMesh();
}
[Test]
public void testFindPath() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < START_REFS.Length; i++) {
long startRef = START_REFS[i];
long endRef = END_REFS[i];
float[] startPos = START_POS[i];
float[] endPos = END_POS[i];
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
for (int j = 0; j < RESULTS[i].Length; j++) {
Assert.That(RESULTS[i][j], Is.EqualTo(path.result[j]));
}
}
}
}

View File

@ -0,0 +1,74 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 DotRecast.Core;
using DotRecast.Detour.TileCache.Io.Compress;
using DotRecast.Recast.Geom;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.RecastVectors;
namespace DotRecast.Detour.TileCache.Test;
public class AbstractTileCacheTest {
private const int EXPECTED_LAYERS_PER_TILE = 4;
private readonly float m_cellSize = 0.3f;
private readonly float m_cellHeight = 0.2f;
private readonly float m_agentHeight = 2.0f;
private readonly float m_agentRadius = 0.6f;
private readonly float m_agentMaxClimb = 0.9f;
private readonly float m_edgeMaxError = 1.3f;
private readonly int m_tileSize = 48;
protected class TestTileCacheMeshProcess : TileCacheMeshProcess {
public void process(NavMeshDataCreateParams option) {
for (int i = 0; i < option.polyCount; ++i) {
option.polyFlags[i] = 1;
}
}
}
public TileCache getTileCache(InputGeomProvider geom, ByteOrder order, bool cCompatibility) {
TileCacheParams option = new TileCacheParams();
int[] twh = Recast.Recast.calcTileCount(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), m_cellSize, m_tileSize, m_tileSize);
option.ch = m_cellHeight;
option.cs = m_cellSize;
vCopy(option.orig, geom.getMeshBoundsMin());
option.height = m_tileSize;
option.width = m_tileSize;
option.walkableHeight = m_agentHeight;
option.walkableRadius = m_agentRadius;
option.walkableClimb = m_agentMaxClimb;
option.maxSimplificationError = m_edgeMaxError;
option.maxTiles = twh[0] * twh[1] * EXPECTED_LAYERS_PER_TILE;
option.maxObstacles = 128;
NavMeshParams navMeshParams = new NavMeshParams();
copy(navMeshParams.orig, geom.getMeshBoundsMin());
navMeshParams.tileWidth = m_tileSize * m_cellSize;
navMeshParams.tileHeight = m_tileSize * m_cellSize;
navMeshParams.maxTiles = 256;
navMeshParams.maxPolys = 16384;
NavMesh navMesh = new NavMesh(navMeshParams, 6);
TileCache tc = new TileCache(option, new TileCacheStorageParams(order, cCompatibility), navMesh,
TileCacheCompressorFactory.get(cCompatibility), new TestTileCacheMeshProcess());
return tc;
}
}

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,223 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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;
using DotRecast.Detour.TileCache.Io;
using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io;
public class TileCacheReaderTest {
private readonly TileCacheReader reader = new TileCacheReader();
[Test]
public void testNavmesh() {
using var ms = new MemoryStream(Loader.ToBytes("all_tiles_tilecache.bin"));
using var @is = new BinaryReader(ms);
TileCache tc = reader.read(@is, 6, null);
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384));
Assert.That(tc.getNavMesh().getParams().tileWidth, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getParams().tileHeight, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getMaxVertsPerPoly(), Is.EqualTo(6));
Assert.That(tc.getParams().cs, Is.EqualTo(0.3f).Within(0.0f));
Assert.That(tc.getParams().ch, Is.EqualTo(0.2f).Within(0.0f));
Assert.That(tc.getParams().walkableClimb, Is.EqualTo(0.9f).Within(0.0f));
Assert.That(tc.getParams().walkableHeight, Is.EqualTo(2f).Within(0.0f));
Assert.That(tc.getParams().walkableRadius, Is.EqualTo(0.6f).Within(0.0f));
Assert.That(tc.getParams().width, Is.EqualTo(48));
Assert.That(tc.getParams().maxTiles, Is.EqualTo(6 * 7 * 4));
Assert.That(tc.getParams().maxObstacles, Is.EqualTo(128));
Assert.That(tc.getTileCount(), Is.EqualTo(168));
// Tile0: Tris: 1, Verts: 4 Detail Meshed: 1 Detail Verts: 0 Detail Tris: 2
// Verts: -2.269517, 28.710686, 28.710686
MeshTile tile = tc.getNavMesh().getTile(0);
MeshData data = tile.data;
MeshHeader header = data.header;
Assert.That(header.vertCount, Is.EqualTo(4));
Assert.That(header.polyCount, Is.EqualTo(1));
Assert.That(header.detailMeshCount, Is.EqualTo(1));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(2));
Assert.That(data.polys.Length, Is.EqualTo(1));
Assert.That(data.verts.Length, Is.EqualTo(3 * 4));
Assert.That(data.detailMeshes.Length, Is.EqualTo(1));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 2));
Assert.That(data.verts[1], Is.EqualTo(-2.269517f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(28.710686f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(28.710686f).Within(0.0001f));
// Tile8: Tris: 7, Verts: 10 Detail Meshed: 7 Detail Verts: 0 Detail Tris: 10
// Verts: 0.330483, 43.110687, 43.110687
tile = tc.getNavMesh().getTile(8);
data = tile.data;
header = data.header;
Console.WriteLine(data.header.x + " " + data.header.y + " " + data.header.layer);
Assert.That(header.x, Is.EqualTo(4));
Assert.That(header.y, Is.EqualTo(1));
Assert.That(header.layer, Is.EqualTo(0));
Assert.That(header.vertCount, Is.EqualTo(10));
Assert.That(header.polyCount, Is.EqualTo(7));
Assert.That(header.detailMeshCount, Is.EqualTo(7));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(10));
Assert.That(data.polys.Length, Is.EqualTo(7));
Assert.That(data.verts.Length, Is.EqualTo(3 * 10));
Assert.That(data.detailMeshes.Length, Is.EqualTo(7));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 10));
Assert.That(data.verts[1], Is.EqualTo(0.330483f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(43.110687f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(43.110687f).Within(0.0001f));
// Tile16: Tris: 13, Verts: 33 Detail Meshed: 13 Detail Verts: 0 Detail Tris: 25
// Verts: 1.130483, 5.610685, 6.510685
tile = tc.getNavMesh().getTile(16);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(33));
Assert.That(header.polyCount, Is.EqualTo(13));
Assert.That(header.detailMeshCount, Is.EqualTo(13));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(25));
Assert.That(data.polys.Length, Is.EqualTo(13));
Assert.That(data.verts.Length, Is.EqualTo(3 * 33));
Assert.That(data.detailMeshes.Length, Is.EqualTo(13));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 25));
Assert.That(data.verts[1], Is.EqualTo(1.130483f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(5.610685f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(6.510685f).Within(0.0001f));
// Tile29: Tris: 5, Verts: 15 Detail Meshed: 5 Detail Verts: 0 Detail Tris: 11
// Verts: 10.330483, 10.110685, 10.110685
tile = tc.getNavMesh().getTile(29);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(15));
Assert.That(header.polyCount, Is.EqualTo(5));
Assert.That(header.detailMeshCount, Is.EqualTo(5));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(11));
Assert.That(data.polys.Length, Is.EqualTo(5));
Assert.That(data.verts.Length, Is.EqualTo(3 * 15));
Assert.That(data.detailMeshes.Length, Is.EqualTo(5));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 11));
Assert.That(data.verts[1], Is.EqualTo(10.330483f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(10.110685f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(10.110685f).Within(0.0001f));
}
[Test]
public void testDungeon() {
using var ms = new MemoryStream(Loader.ToBytes("dungeon_all_tiles_tilecache.bin"));
using var @is = new BinaryReader(ms);
TileCache tc = reader.read(@is, 6, null);
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384));
Assert.That(tc.getNavMesh().getParams().tileWidth, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getParams().tileHeight, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getMaxVertsPerPoly(), Is.EqualTo(6));
Assert.That(tc.getParams().cs, Is.EqualTo(0.3f).Within(0.0f));
Assert.That(tc.getParams().ch, Is.EqualTo(0.2f).Within(0.0f));
Assert.That(tc.getParams().walkableClimb, Is.EqualTo(0.9f).Within(0.0f));
Assert.That(tc.getParams().walkableHeight, Is.EqualTo(2f).Within(0.0f));
Assert.That(tc.getParams().walkableRadius, Is.EqualTo(0.6f).Within(0.0f));
Assert.That(tc.getParams().width, Is.EqualTo(48));
Assert.That(tc.getParams().maxTiles, Is.EqualTo(6 * 7 * 4));
Assert.That(tc.getParams().maxObstacles, Is.EqualTo(128));
Assert.That(tc.getTileCount(), Is.EqualTo(168));
// Tile0: Tris: 8, Verts: 18 Detail Meshed: 8 Detail Verts: 0 Detail Tris: 14
// Verts: 14.997294, 15.484785, 15.484785
MeshTile tile = tc.getNavMesh().getTile(0);
MeshData data = tile.data;
MeshHeader header = data.header;
Assert.That(header.vertCount, Is.EqualTo(18));
Assert.That(header.polyCount, Is.EqualTo(8));
Assert.That(header.detailMeshCount, Is.EqualTo(8));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(14));
Assert.That(data.polys.Length, Is.EqualTo(8));
Assert.That(data.verts.Length, Is.EqualTo(3 * 18));
Assert.That(data.detailMeshes.Length, Is.EqualTo(8));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 14));
Assert.That(data.verts[1], Is.EqualTo(14.997294f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(15.484785f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(15.484785f).Within(0.0001f));
// Tile8: Tris: 3, Verts: 8 Detail Meshed: 3 Detail Verts: 0 Detail Tris: 6
// Verts: 13.597294, 17.584785, 17.584785
tile = tc.getNavMesh().getTile(8);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(8));
Assert.That(header.polyCount, Is.EqualTo(3));
Assert.That(header.detailMeshCount, Is.EqualTo(3));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(6));
Assert.That(data.polys.Length, Is.EqualTo(3));
Assert.That(data.verts.Length, Is.EqualTo(3 * 8));
Assert.That(data.detailMeshes.Length, Is.EqualTo(3));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 6));
Assert.That(data.verts[1], Is.EqualTo(13.597294f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(17.584785f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(17.584785f).Within(0.0001f));
// Tile16: Tris: 10, Verts: 20 Detail Meshed: 10 Detail Verts: 0 Detail Tris: 18
// Verts: 6.197294, -22.315216, -22.315216
tile = tc.getNavMesh().getTile(16);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(20));
Assert.That(header.polyCount, Is.EqualTo(10));
Assert.That(header.detailMeshCount, Is.EqualTo(10));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(18));
Assert.That(data.polys.Length, Is.EqualTo(10));
Assert.That(data.verts.Length, Is.EqualTo(3 * 20));
Assert.That(data.detailMeshes.Length, Is.EqualTo(10));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 18));
Assert.That(data.verts[1], Is.EqualTo(6.197294f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(-22.315216f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(-22.315216f).Within(0.0001f));
// Tile29: Tris: 1, Verts: 5 Detail Meshed: 1 Detail Verts: 0 Detail Tris: 3
// Verts: 10.197294, 48.484783, 48.484783
tile = tc.getNavMesh().getTile(29);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(5));
Assert.That(header.polyCount, Is.EqualTo(1));
Assert.That(header.detailMeshCount, Is.EqualTo(1));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(3));
Assert.That(data.polys.Length, Is.EqualTo(1));
Assert.That(data.verts.Length, Is.EqualTo(3 * 5));
Assert.That(data.detailMeshes.Length, Is.EqualTo(1));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
Assert.That(data.verts[1], Is.EqualTo(10.197294f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(48.484783f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(48.484783f).Within(0.0001f));
}
}

View File

@ -0,0 +1,137 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.IO;
using DotRecast.Core;
using DotRecast.Detour.TileCache.Io;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io;
public class TileCacheReaderWriterTest : AbstractTileCacheTest {
private readonly TileCacheReader reader = new TileCacheReader();
private readonly TileCacheWriter writer = new TileCacheWriter();
[Test]
public void testFastLz() {
testDungeon(false);
testDungeon(true);
}
[Test]
public void testLZ4() {
testDungeon(true);
testDungeon(false);
}
private void testDungeon(bool cCompatibility) {
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] layer in layers) {
long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs);
}
using var msout = new MemoryStream();
using var baos = new BinaryWriter(msout);
writer.write(baos, tc, ByteOrder.LITTLE_ENDIAN, cCompatibility);
using var msis = new MemoryStream(msout.ToArray());
using var bais = new BinaryReader(msis);
tc = reader.read(bais, 6, null);
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384));
Assert.That(tc.getNavMesh().getParams().tileWidth, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getParams().tileHeight, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getMaxVertsPerPoly(), Is.EqualTo(6));
Assert.That(tc.getParams().cs, Is.EqualTo(0.3f).Within(0.0f));
Assert.That(tc.getParams().ch, Is.EqualTo(0.2f).Within(0.0f));
Assert.That(tc.getParams().walkableClimb, Is.EqualTo(0.9f).Within(0.0f));
Assert.That(tc.getParams().walkableHeight, Is.EqualTo(2f).Within(0.0f));
Assert.That(tc.getParams().walkableRadius, Is.EqualTo(0.6f).Within(0.0f));
Assert.That(tc.getParams().width, Is.EqualTo(48));
Assert.That(tc.getParams().maxTiles, Is.EqualTo(6 * 7 * 4));
Assert.That(tc.getParams().maxObstacles, Is.EqualTo(128));
Assert.That(tc.getTileCount(), Is.EqualTo(168));
// Tile0: Tris: 8, Verts: 18 Detail Meshed: 8 Detail Verts: 0 Detail Tris: 14
MeshTile tile = tc.getNavMesh().getTile(0);
MeshData data = tile.data;
MeshHeader header = data.header;
Assert.That(header.vertCount, Is.EqualTo(18));
Assert.That(header.polyCount, Is.EqualTo(8));
Assert.That(header.detailMeshCount, Is.EqualTo(8));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(14));
Assert.That(data.polys.Length, Is.EqualTo(8));
Assert.That(data.verts.Length, Is.EqualTo(3 * 18));
Assert.That(data.detailMeshes.Length, Is.EqualTo(8));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 14));
// Tile8: Tris: 3, Verts: 8 Detail Meshed: 3 Detail Verts: 0 Detail Tris: 6
tile = tc.getNavMesh().getTile(8);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(8));
Assert.That(header.polyCount, Is.EqualTo(3));
Assert.That(header.detailMeshCount, Is.EqualTo(3));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(6));
Assert.That(data.polys.Length, Is.EqualTo(3));
Assert.That(data.verts.Length, Is.EqualTo(3 * 8));
Assert.That(data.detailMeshes.Length, Is.EqualTo(3));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 6));
// Tile16: Tris: 10, Verts: 20 Detail Meshed: 10 Detail Verts: 0 Detail Tris: 18
tile = tc.getNavMesh().getTile(16);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(20));
Assert.That(header.polyCount, Is.EqualTo(10));
Assert.That(header.detailMeshCount, Is.EqualTo(10));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(18));
Assert.That(data.polys.Length, Is.EqualTo(10));
Assert.That(data.verts.Length, Is.EqualTo(3 * 20));
Assert.That(data.detailMeshes.Length, Is.EqualTo(10));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 18));
// Tile29: Tris: 1, Verts: 5 Detail Meshed: 1 Detail Verts: 0 Detail Tris: 3
tile = tc.getNavMesh().getTile(29);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(5));
Assert.That(header.polyCount, Is.EqualTo(1));
Assert.That(header.detailMeshCount, Is.EqualTo(1));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(3));
Assert.That(data.polys.Length, Is.EqualTo(1));
Assert.That(data.verts.Length, Is.EqualTo(3 * 5));
Assert.That(data.detailMeshes.Length, Is.EqualTo(1));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
}
}

View File

@ -0,0 +1,55 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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 DotRecast.Recast;
namespace DotRecast.Detour.TileCache.Test;
public class SampleAreaModifications {
public static int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
/// Value for the kind of ceil "ground"
public static int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
/// Value for the kind of ceil "water"
public static int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
/// Value for the kind of ceil "road"
public static int SAMPLE_POLYAREA_TYPE_ROAD = 0x3;
/// Value for the kind of ceil "grass"
public static int SAMPLE_POLYAREA_TYPE_GRASS = 0x4;
/// Flag for door area. Can be combined with area types and jump flag.
public static int SAMPLE_POLYAREA_FLAG_DOOR = 0x08;
/// Flag for jump area. Can be combined with area types and door flag.
public static int SAMPLE_POLYAREA_FLAG_JUMP = 0x10;
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_FLAG_DOOR,
SAMPLE_POLYAREA_FLAG_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_FLAG_JUMP,
SAMPLE_POLYAREA_FLAG_JUMP);
}

View File

@ -0,0 +1,92 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
public class TempObstaclesTest : AbstractTileCacheTest {
[Test]
public void testDungeon() {
bool cCompatibility = true;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] data in layers) {
long refs = tc.addTile(data, 0);
tc.buildNavMeshTile(refs);
}
List<MeshTile> tiles = tc.getNavMesh().getTilesAt(1, 4);
MeshTile tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
Assert.That(tile.data.header.polyCount, Is.EqualTo(6));
long o = tc.addObstacle(new float[] { -1.815208f, 9.998184f, -20.307983f }, 1f, 2f);
bool upToDate = tc.update();
Assert.That(upToDate, Is.True);
tiles = tc.getNavMesh().getTilesAt(1, 4);
tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(22));
Assert.That(tile.data.header.polyCount, Is.EqualTo(11));
tc.removeObstacle(o);
upToDate = tc.update();
Assert.That(upToDate, Is.True);
tiles = tc.getNavMesh().getTilesAt(1, 4);
tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
Assert.That(tile.data.header.polyCount, Is.EqualTo(6));
}
[Test]
public void testDungeonBox() {
bool cCompatibility = true;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] data in layers) {
long refs = tc.addTile(data, 0);
tc.buildNavMeshTile(refs);
}
List<MeshTile> tiles = tc.getNavMesh().getTilesAt(1, 4);
MeshTile tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
Assert.That(tile.data.header.polyCount, Is.EqualTo(6));
long o = tc.addBoxObstacle(new float[] { -2.315208f, 9.998184f, -20.807983f },
new float[] { -1.315208f, 11.998184f, -19.807983f });
bool upToDate = tc.update();
Assert.That(upToDate, Is.True);
tiles = tc.getNavMesh().getTilesAt(1, 4);
tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(22));
Assert.That(tile.data.header.polyCount, Is.EqualTo(11));
tc.removeObstacle(o);
upToDate = tc.update();
Assert.That(upToDate, Is.True);
tiles = tc.getNavMesh().getTilesAt(1, 4);
tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
Assert.That(tile.data.header.polyCount, Is.EqualTo(6));
}
}

View File

@ -0,0 +1,120 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.TileCache.Test;
public class TestTileLayerBuilder : AbstractTileLayersBuilder {
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f;
private const float m_agentRadius = 0.6f;
private const float m_agentMaxClimb = 0.9f;
private const float m_agentMaxSlope = 45.0f;
private const int m_regionMinSize = 8;
private const int m_regionMergeSize = 20;
private const float m_regionMinArea = m_regionMinSize * m_regionMinSize * m_cellSize * m_cellSize;
private const float m_regionMergeArea = m_regionMergeSize * m_regionMergeSize * m_cellSize * m_cellSize;
private const float m_edgeMaxLen = 12.0f;
private const float m_edgeMaxError = 1.3f;
private const int m_vertsPerPoly = 6;
private const float m_detailSampleDist = 6.0f;
private const float m_detailSampleMaxError = 1.0f;
private readonly RecastConfig rcConfig;
private const int m_tileSize = 48;
protected readonly InputGeomProvider geom;
private readonly int tw;
private readonly int th;
public TestTileLayerBuilder(InputGeomProvider geom) {
this.geom = geom;
rcConfig = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight,
m_agentRadius, m_agentMaxClimb, m_regionMinArea, m_regionMergeArea, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly,
true, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
int[] twh = Recast.Recast.calcTileCount(bmin, bmax, m_cellSize, m_tileSize, m_tileSize);
tw = twh[0];
th = twh[1];
}
public List<byte[]> build(ByteOrder order, bool cCompatibility, int threads) {
return build(order, cCompatibility, threads, tw, th);
}
public int getTw() {
return tw;
}
public int getTh() {
return th;
}
protected override List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility) {
HeightfieldLayerSet lset = getHeightfieldSet(tx, ty);
List<byte[]> result = new();
if (lset != null) {
TileCacheBuilder builder = new TileCacheBuilder();
for (int i = 0; i < lset.layers.Length; ++i) {
HeightfieldLayerSet.HeightfieldLayer layer = lset.layers[i];
// Store header
TileCacheLayerHeader header = new TileCacheLayerHeader();
header.magic = TileCacheLayerHeader.DT_TILECACHE_MAGIC;
header.version = TileCacheLayerHeader.DT_TILECACHE_VERSION;
// Tile layer location in the navmesh.
header.tx = tx;
header.ty = ty;
header.tlayer = i;
vCopy(header.bmin, layer.bmin);
vCopy(header.bmax, layer.bmax);
// Tile info.
header.width = layer.width;
header.height = layer.height;
header.minx = layer.minx;
header.maxx = layer.maxx;
header.miny = layer.miny;
header.maxy = layer.maxy;
header.hmin = layer.hmin;
header.hmax = layer.hmax;
result.Add(builder.compressTileCacheLayer(header, layer.heights, layer.areas, layer.cons, order, cCompatibility));
}
}
return result;
}
protected HeightfieldLayerSet getHeightfieldSet(int tx, int ty) {
RecastBuilder rcBuilder = new RecastBuilder();
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
RecastBuilderConfig cfg = new RecastBuilderConfig(rcConfig, bmin, bmax, tx, ty);
HeightfieldLayerSet lset = rcBuilder.buildLayers(geom, cfg);
return lset;
}
}

View File

@ -0,0 +1,61 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.IO;
using DotRecast.Core;
using DotRecast.Detour.TileCache.Io;
using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
public class TileCacheFindPathTest : AbstractTileCacheTest {
private readonly float[] start = { 39.44734f, 9.998177f, -0.784811f };
private readonly float[] end = { 19.292645f, 11.611748f, -57.750366f };
private readonly NavMesh navmesh;
private readonly NavMeshQuery query;
public TileCacheFindPathTest() {
using var msis = new MemoryStream(Loader.ToBytes("dungeon_all_tiles_tilecache.bin"));
using var @is = new BinaryReader(msis);
TileCache tcC = new TileCacheReader().read(@is, 6, new TestTileCacheMeshProcess());
navmesh = tcC.getNavMesh();
query = new NavMeshQuery(navmesh);
}
[Test]
public void testFindPath() {
QueryFilter filter = new DefaultQueryFilter();
float[] extents = new float[] { 2f, 4f, 2f };
Result<FindNearestPolyResult> findPolyStart = query.findNearestPoly(start, extents, filter);
Result<FindNearestPolyResult> findPolyEnd = query.findNearestPoly(end, extents, filter);
long startRef = findPolyStart.result.getNearestRef();
long endRef = findPolyEnd.result.getNearestRef();
float[] startPos = findPolyStart.result.getNearestPos();
float[] endPos = findPolyEnd.result.getNearestPos();
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
int maxStraightPath = 256;
int options = 0;
Result<List<StraightPathItem>> pathStr = query.findStraightPath(startPos, endPos, path.result, maxStraightPath,
options);
Assert.That(pathStr.result.Count, Is.EqualTo(8));
}
}

View File

@ -0,0 +1,103 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
public class TileCacheNavigationTest : AbstractTileCacheTest {
protected readonly long[] startRefs = { 281475006070787L };
protected readonly long[] endRefs = { 281474986147841L };
protected readonly float[][] startPoss = { new[] { 39.447338f, 9.998177f, -0.784811f } };
protected readonly float[][] endPoss = { new[] { 19.292645f, 11.611748f, -57.750366f } };
private readonly Status[] statuses = { Status.SUCCSESS };
private readonly long[][] results = { new[] { 281475006070787L, 281475006070785L, 281475005022208L, 281475005022209L, 281475003973633L,
281475003973634L, 281475003973632L, 281474996633604L, 281474996633605L, 281474996633603L, 281474995585027L,
281474995585029L, 281474995585026L, 281474995585028L, 281474995585024L, 281474991390721L, 281474991390722L,
281474991390725L, 281474991390720L, 281474987196418L, 281474987196417L, 281474988244995L, 281474988245001L,
281474988244997L, 281474988244998L, 281474988245002L, 281474988245000L, 281474988244999L, 281474988244994L,
281474985099264L, 281474985099266L, 281474986147841L } };
protected NavMesh navmesh;
protected NavMeshQuery query;
[SetUp]
public void setUp() {
bool cCompatibility = true;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] data in layers) {
tc.addTile(data, 0);
}
for (int y = 0; y < layerBuilder.getTh(); ++y) {
for (int x = 0; x < layerBuilder.getTw(); ++x) {
foreach (long refs in tc.getTilesAt(x, y)) {
tc.buildNavMeshTile(refs);
}
}
}
navmesh = tc.getNavMesh();
query = new NavMeshQuery(navmesh);
}
[Test]
public void testFindPathWithDefaultHeuristic() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(statuses[i]));
Assert.That(path.result.Count, Is.EqualTo(results[i].Length));
for (int j = 0; j < results[i].Length; j++) {
Assert.That(path.result[j], Is.EqualTo(results[i][j])); // TODO : 확인 필요
}
}
}
[Test]
public void testFindPathWithNoHeuristic() {
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter, new DefaultQueryHeuristic(0.0f),
0, 0);
Assert.That(path.status, Is.EqualTo(statuses[i]));
Assert.That(path.result.Count, Is.EqualTo(results[i].Length));
for (int j = 0; j < results[i].Length; j++) {
Assert.That(path.result[j], Is.EqualTo(results[i][j])); // TODO : 확인 필요
}
}
}
}

View File

@ -0,0 +1,272 @@
/*
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
recast4j copyright (c) 2015-2019 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.Collections.Generic;
using System.Diagnostics;
using DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
public class TileCacheTest : AbstractTileCacheTest {
[Test]
public void testFastLz() {
testDungeon(ByteOrder.LITTLE_ENDIAN, false);
testDungeon(ByteOrder.LITTLE_ENDIAN, true);
testDungeon(ByteOrder.BIG_ENDIAN, false);
testDungeon(ByteOrder.BIG_ENDIAN, true);
test(ByteOrder.LITTLE_ENDIAN, false);
test(ByteOrder.LITTLE_ENDIAN, true);
test(ByteOrder.BIG_ENDIAN, false);
test(ByteOrder.BIG_ENDIAN, true);
}
[Test]
public void testLZ4() {
testDungeon(ByteOrder.LITTLE_ENDIAN, false);
testDungeon(ByteOrder.LITTLE_ENDIAN, true);
testDungeon(ByteOrder.BIG_ENDIAN, false);
testDungeon(ByteOrder.BIG_ENDIAN, true);
test(ByteOrder.LITTLE_ENDIAN, false);
test(ByteOrder.LITTLE_ENDIAN, true);
test(ByteOrder.BIG_ENDIAN, false);
test(ByteOrder.BIG_ENDIAN, true);
}
private void testDungeon(ByteOrder order, bool cCompatibility) {
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TileCache tc = getTileCache(geom, order, cCompatibility);
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(order, cCompatibility, 1);
int cacheLayerCount = 0;
int cacheCompressedSize = 0;
int cacheRawSize = 0;
foreach (byte[] layer in layers) {
long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs);
cacheLayerCount++;
cacheCompressedSize += layer.Length;
cacheRawSize += 4 * 48 * 48 + 56; // FIXME
}
Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility
+ " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize);
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384));
Assert.That(tc.getNavMesh().getParams().tileWidth, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getParams().tileHeight, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getMaxVertsPerPoly(), Is.EqualTo(6));
Assert.That(tc.getParams().cs, Is.EqualTo(0.3f));
Assert.That(tc.getParams().ch, Is.EqualTo(0.2f));
Assert.That(tc.getParams().walkableClimb, Is.EqualTo(0.9f));
Assert.That(tc.getParams().walkableHeight, Is.EqualTo(2f));
Assert.That(tc.getParams().walkableRadius, Is.EqualTo(0.6f));
Assert.That(tc.getParams().width, Is.EqualTo(48));
Assert.That(tc.getParams().maxTiles, Is.EqualTo(6 * 7 * 4));
Assert.That(tc.getParams().maxObstacles, Is.EqualTo(128));
Assert.That(tc.getTileCount(), Is.EqualTo(168));
// Tile0: Tris: 8, Verts: 18 Detail Meshed: 8 Detail Verts: 0 Detail Tris: 14
MeshTile tile = tc.getNavMesh().getTile(0);
MeshData data = tile.data;
MeshHeader header = data.header;
Assert.That(header.vertCount, Is.EqualTo(18));
Assert.That(header.polyCount, Is.EqualTo(8));
Assert.That(header.detailMeshCount, Is.EqualTo(8));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(14));
Assert.That(data.polys.Length, Is.EqualTo(8));
Assert.That(data.verts.Length, Is.EqualTo(3 * 18));
Assert.That(data.detailMeshes.Length, Is.EqualTo(8));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 14));
Assert.That(data.verts[1], Is.EqualTo(14.997294f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(15.484785f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(15.484785f).Within(0.0001f));
// Tile8: Tris: 3, Verts: 8 Detail Meshed: 3 Detail Verts: 0 Detail Tris: 6
tile = tc.getNavMesh().getTile(8);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(8));
Assert.That(header.polyCount, Is.EqualTo(3));
Assert.That(header.detailMeshCount, Is.EqualTo(3));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(6));
Assert.That(data.polys.Length, Is.EqualTo(3));
Assert.That(data.verts.Length, Is.EqualTo(3 * 8));
Assert.That(data.detailMeshes.Length, Is.EqualTo(3));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 6));
// Tile16: Tris: 10, Verts: 20 Detail Meshed: 10 Detail Verts: 0 Detail Tris: 18
tile = tc.getNavMesh().getTile(16);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(20));
Assert.That(header.polyCount, Is.EqualTo(10));
Assert.That(header.detailMeshCount, Is.EqualTo(10));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(18));
Assert.That(data.polys.Length, Is.EqualTo(10));
Assert.That(data.verts.Length, Is.EqualTo(3 * 20));
Assert.That(data.detailMeshes.Length, Is.EqualTo(10));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 18));
// Tile29: Tris: 1, Verts: 5 Detail Meshed: 1 Detail Verts: 0 Detail Tris: 3
tile = tc.getNavMesh().getTile(29);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(5));
Assert.That(header.polyCount, Is.EqualTo(1));
Assert.That(header.detailMeshCount, Is.EqualTo(1));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(3));
Assert.That(data.polys.Length, Is.EqualTo(1));
Assert.That(data.verts.Length, Is.EqualTo(3 * 5));
Assert.That(data.detailMeshes.Length, Is.EqualTo(1));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
}
private void test(ByteOrder order, bool cCompatibility) {
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("nav_test.obj"));
TileCache tc = getTileCache(geom, order, cCompatibility);
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(order, cCompatibility, 1);
int cacheLayerCount = 0;
int cacheCompressedSize = 0;
int cacheRawSize = 0;
foreach (byte[] layer in layers) {
long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs);
cacheLayerCount++;
cacheCompressedSize += layer.Length;
cacheRawSize += 4 * 48 * 48 + 56;
}
Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility
+ " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize);
}
[Test]
public void testPerformance() {
int threads = 4;
ByteOrder order = ByteOrder.LITTLE_ENDIAN;
bool cCompatibility = false;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
for (int i = 0; i < 4; i++) {
layerBuilder.build(order, cCompatibility, 1);
layerBuilder.build(order, cCompatibility, threads);
}
long t1 = Stopwatch.GetTimestamp();
List<byte[]> layers = null;
for (int i = 0; i < 8; i++) {
layers = layerBuilder.build(order, cCompatibility, 1);
}
long t2 = Stopwatch.GetTimestamp();
for (int i = 0; i < 8; i++) {
layers = layerBuilder.build(order, cCompatibility, threads);
}
long t3 = Stopwatch.GetTimestamp();
Console.WriteLine(" Time ST : " + (t2 - t1) / 1000000);
Console.WriteLine(" Time MT : " + (t3 - t2) / 1000000);
TileCache tc = getTileCache(geom, order, cCompatibility);
foreach (byte[] layer in layers) {
long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs);
}
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384));
Assert.That(tc.getNavMesh().getParams().tileWidth, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getParams().tileHeight, Is.EqualTo(14.4f).Within(0.001f));
Assert.That(tc.getNavMesh().getMaxVertsPerPoly(), Is.EqualTo(6));
Assert.That(tc.getParams().cs, Is.EqualTo(0.3f));
Assert.That(tc.getParams().ch, Is.EqualTo(0.2f));
Assert.That(tc.getParams().walkableClimb, Is.EqualTo(0.9f));
Assert.That(tc.getParams().walkableHeight, Is.EqualTo(2f));
Assert.That(tc.getParams().walkableRadius, Is.EqualTo(0.6f));
Assert.That(tc.getParams().width, Is.EqualTo(48));
Assert.That(tc.getParams().maxTiles, Is.EqualTo(6 * 7 * 4));
Assert.That(tc.getParams().maxObstacles, Is.EqualTo(128));
Assert.That(tc.getTileCount(), Is.EqualTo(168));
// Tile0: Tris: 8, Verts: 18 Detail Meshed: 8 Detail Verts: 0 Detail Tris: 14
MeshTile tile = tc.getNavMesh().getTile(0);
MeshData data = tile.data;
MeshHeader header = data.header;
Assert.That(header.vertCount, Is.EqualTo(18));
Assert.That(header.polyCount, Is.EqualTo(8));
Assert.That(header.detailMeshCount, Is.EqualTo(8));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(14));
Assert.That(data.polys.Length, Is.EqualTo(8));
Assert.That(data.verts.Length, Is.EqualTo(3 * 18));
Assert.That(data.detailMeshes.Length, Is.EqualTo(8));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 14));
Assert.That(data.verts[1], Is.EqualTo(14.997294f).Within(0.0001f));
Assert.That(data.verts[6], Is.EqualTo(15.484785f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(15.484785f).Within(0.0001f));
// Tile8: Tris: 3, Verts: 8 Detail Meshed: 3 Detail Verts: 0 Detail Tris: 6
tile = tc.getNavMesh().getTile(8);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(8));
Assert.That(header.polyCount, Is.EqualTo(3));
Assert.That(header.detailMeshCount, Is.EqualTo(3));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(6));
Assert.That(data.polys.Length, Is.EqualTo(3));
Assert.That(data.verts.Length, Is.EqualTo(3 * 8));
Assert.That(data.detailMeshes.Length, Is.EqualTo(3));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 6));
// Tile16: Tris: 10, Verts: 20 Detail Meshed: 10 Detail Verts: 0 Detail Tris: 18
tile = tc.getNavMesh().getTile(16);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(20));
Assert.That(header.polyCount, Is.EqualTo(10));
Assert.That(header.detailMeshCount, Is.EqualTo(10));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(18));
Assert.That(data.polys.Length, Is.EqualTo(10));
Assert.That(data.verts.Length, Is.EqualTo(3 * 20));
Assert.That(data.detailMeshes.Length, Is.EqualTo(10));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 18));
// Tile29: Tris: 1, Verts: 5 Detail Meshed: 1 Detail Verts: 0 Detail Tris: 3
tile = tc.getNavMesh().getTile(29);
data = tile.data;
header = data.header;
Assert.That(header.vertCount, Is.EqualTo(5));
Assert.That(header.polyCount, Is.EqualTo(1));
Assert.That(header.detailMeshCount, Is.EqualTo(1));
Assert.That(header.detailVertCount, Is.EqualTo(0));
Assert.That(header.detailTriCount, Is.EqualTo(3));
Assert.That(data.polys.Length, Is.EqualTo(1));
Assert.That(data.verts.Length, Is.EqualTo(3 * 5));
Assert.That(data.detailMeshes.Length, Is.EqualTo(1));
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
}
}

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,157 @@
/*
recast4j Copyright (c) 2015-2019 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;
using DotRecast.Recast.Geom;
using NUnit.Framework;
namespace DotRecast.Recast.Test;
using static RecastConstants;
public class RecastLayersTest
{
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f;
private const float m_agentRadius = 0.6f;
private const float m_agentMaxClimb = 0.9f;
private const float m_agentMaxSlope = 45.0f;
private const int m_regionMinSize = 8;
private const int m_regionMergeSize = 20;
private const float m_regionMinArea = m_regionMinSize * m_regionMinSize * m_cellSize * m_cellSize;
private const float m_regionMergeArea = m_regionMergeSize * m_regionMergeSize * m_cellSize * m_cellSize;
private const float m_edgeMaxLen = 12.0f;
private const float m_edgeMaxError = 1.3f;
private const int m_vertsPerPoly = 6;
private const float m_detailSampleDist = 6.0f;
private const float m_detailSampleMaxError = 1.0f;
private const PartitionType m_partitionType = PartitionType.WATERSHED;
private const int m_tileSize = 48;
[Test]
public void testDungeon2()
{
}
[Test]
public void testDungeon()
{
HeightfieldLayerSet lset = build("dungeon.obj", 3, 2);
Assert.That(lset.layers.Length, Is.EqualTo(1));
Assert.That(lset.layers[0].width, Is.EqualTo(48));
Assert.That(lset.layers[0].hmin, Is.EqualTo(51));
Assert.That(lset.layers[0].hmax, Is.EqualTo(67));
Assert.That(lset.layers[0].heights[7], Is.EqualTo(17));
Assert.That(lset.layers[0].heights[107], Is.EqualTo(15));
Assert.That(lset.layers[0].heights[257], Is.EqualTo(13));
Assert.That(lset.layers[0].heights[1814], Is.EqualTo(255));
Assert.That(lset.layers[0].cons[12], Is.EqualTo(135));
Assert.That(lset.layers[0].cons[109], Is.EqualTo(15));
Assert.That(lset.layers[0].cons[530], Is.EqualTo(15));
Assert.That(lset.layers[0].cons[1600], Is.EqualTo(0));
}
[Test]
public void test()
{
HeightfieldLayerSet lset = build("nav_test.obj", 3, 2);
Assert.That(lset.layers.Length, Is.EqualTo(3));
Assert.That(lset.layers[0].width, Is.EqualTo(48));
Assert.That(lset.layers[0].hmin, Is.EqualTo(13));
Assert.That(lset.layers[0].hmax, Is.EqualTo(30));
Assert.That(lset.layers[0].heights[7], Is.EqualTo(0));
Assert.That(lset.layers[0].heights[107], Is.EqualTo(255));
Assert.That(lset.layers[0].heights[257], Is.EqualTo(0));
Assert.That(lset.layers[0].heights[1814], Is.EqualTo(255));
Assert.That(lset.layers[0].cons[12], Is.EqualTo(133));
Assert.That(lset.layers[0].cons[109], Is.EqualTo(0));
Assert.That(lset.layers[0].cons[530], Is.EqualTo(0));
Assert.That(lset.layers[0].cons[1600], Is.EqualTo(15));
Assert.That(lset.layers[1].width, Is.EqualTo(48));
Assert.That(lset.layers[1].hmin, Is.EqualTo(13));
Assert.That(lset.layers[1].hmax, Is.EqualTo(13));
Assert.That(lset.layers[1].heights[7], Is.EqualTo(255));
Assert.That(lset.layers[1].heights[107], Is.EqualTo(255));
Assert.That(lset.layers[1].heights[257], Is.EqualTo(255));
Assert.That(lset.layers[1].heights[1814], Is.EqualTo(255));
Assert.That(lset.layers[1].cons[12], Is.EqualTo(0));
Assert.That(lset.layers[1].cons[109], Is.EqualTo(0));
Assert.That(lset.layers[1].cons[530], Is.EqualTo(0));
Assert.That(lset.layers[1].cons[1600], Is.EqualTo(0));
Assert.That(lset.layers[2].width, Is.EqualTo(48));
Assert.That(lset.layers[2].hmin, Is.EqualTo(76));
Assert.That(lset.layers[2].hmax, Is.EqualTo(76));
Assert.That(lset.layers[2].heights[7], Is.EqualTo(255));
Assert.That(lset.layers[2].heights[107], Is.EqualTo(255));
Assert.That(lset.layers[2].heights[257], Is.EqualTo(255));
Assert.That(lset.layers[2].heights[1814], Is.EqualTo(255));
Assert.That(lset.layers[2].cons[12], Is.EqualTo(0));
Assert.That(lset.layers[2].cons[109], Is.EqualTo(0));
Assert.That(lset.layers[2].cons[530], Is.EqualTo(0));
Assert.That(lset.layers[2].cons[1600], Is.EqualTo(0));
}
[Test]
public void test2()
{
HeightfieldLayerSet lset = build("nav_test.obj", 2, 4);
Assert.That(lset.layers.Length, Is.EqualTo(2));
Assert.That(lset.layers[0].width, Is.EqualTo(48));
Assert.That(lset.layers[0].hmin, Is.EqualTo(13));
Assert.That(lset.layers[0].hmax, Is.EqualTo(13));
Assert.That(lset.layers[0].heights[7], Is.EqualTo(0));
Assert.That(lset.layers[0].heights[107], Is.EqualTo(0));
Assert.That(lset.layers[0].heights[257], Is.EqualTo(0));
Assert.That(lset.layers[0].heights[1814], Is.EqualTo(0));
Assert.That(lset.layers[0].cons[12], Is.EqualTo(135));
Assert.That(lset.layers[0].cons[109], Is.EqualTo(15));
Assert.That(lset.layers[0].cons[530], Is.EqualTo(0));
Assert.That(lset.layers[0].cons[1600], Is.EqualTo(15));
Assert.That(lset.layers[1].width, Is.EqualTo(48));
Assert.That(lset.layers[1].hmin, Is.EqualTo(68));
Assert.That(lset.layers[1].hmax, Is.EqualTo(101));
Assert.That(lset.layers[1].heights[7], Is.EqualTo(33));
Assert.That(lset.layers[1].heights[107], Is.EqualTo(255));
Assert.That(lset.layers[1].heights[257], Is.EqualTo(255));
Assert.That(lset.layers[1].heights[1814], Is.EqualTo(3));
Assert.That(lset.layers[1].cons[12], Is.EqualTo(0));
Assert.That(lset.layers[1].cons[109], Is.EqualTo(0));
Assert.That(lset.layers[1].cons[530], Is.EqualTo(15));
Assert.That(lset.layers[1].cons[1600], Is.EqualTo(0));
}
private HeightfieldLayerSet build(string filename, int x, int y)
{
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes(filename));
RecastBuilder builder = new RecastBuilder();
RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_regionMinArea, m_regionMergeArea, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, true,
m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), x, y);
HeightfieldLayerSet lset = builder.buildLayers(geom, bcfg);
return lset;
}
}

View File

@ -0,0 +1,337 @@
/*
recast4j Copyright (c) 2015-2019 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.Diagnostics;
using System.IO;
using DotRecast.Core;
using DotRecast.Recast.Geom;
using NUnit.Framework;
namespace DotRecast.Recast.Test;
using static RecastConstants;
public class RecastSoloMeshTest
{
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f;
private const float m_agentRadius = 0.6f;
private const float m_agentMaxClimb = 0.9f;
private const float m_agentMaxSlope = 45.0f;
private const int m_regionMinSize = 8;
private const int m_regionMergeSize = 20;
private const float m_edgeMaxLen = 12.0f;
private const float m_edgeMaxError = 1.3f;
private const int m_vertsPerPoly = 6;
private const float m_detailSampleDist = 6.0f;
private const float m_detailSampleMaxError = 1.0f;
private PartitionType m_partitionType = PartitionType.WATERSHED;
[Test]
public void testPerformance()
{
for (int i = 0; i < 10; i++)
{
testBuild("dungeon.obj", PartitionType.WATERSHED, 52, 16, 15, 223, 118, 118, 513, 291);
testBuild("dungeon.obj", PartitionType.MONOTONE, 0, 17, 16, 210, 100, 100, 453, 264);
testBuild("dungeon.obj", PartitionType.LAYERS, 0, 5, 5, 203, 97, 97, 446, 266);
}
}
[Test]
public void testDungeonWatershed()
{
testBuild("dungeon.obj", PartitionType.WATERSHED, 52, 16, 15, 223, 118, 118, 513, 291);
}
[Test]
public void testDungeonMonotone()
{
testBuild("dungeon.obj", PartitionType.MONOTONE, 0, 17, 16, 210, 100, 100, 453, 264);
}
[Test]
public void testDungeonLayers()
{
testBuild("dungeon.obj", PartitionType.LAYERS, 0, 5, 5, 203, 97, 97, 446, 266);
}
[Test]
public void testWatershed()
{
testBuild("nav_test.obj", PartitionType.WATERSHED, 60, 48, 47, 349, 153, 153, 802, 558);
}
[Test]
public void testMonotone()
{
testBuild("nav_test.obj", PartitionType.MONOTONE, 0, 50, 49, 340, 185, 185, 871, 557);
}
[Test]
public void testLayers()
{
testBuild("nav_test.obj", PartitionType.LAYERS, 0, 19, 32, 312, 150, 150, 764, 521);
}
public void testBuild(string filename, PartitionType partitionType, int expDistance, int expRegions,
int expContours, int expVerts, int expPolys, int expDetMeshes, int expDetVerts, int expDetTris)
{
m_partitionType = partitionType;
InputGeomProvider geomProvider = ObjImporter.load(Loader.ToBytes(filename));
long time = Stopwatch.GetTimestamp();
float[] bmin = geomProvider.getMeshBoundsMin();
float[] bmax = geomProvider.getMeshBoundsMax();
Telemetry m_ctx = new Telemetry();
//
// Step 1. Initialize build config.
//
// Init build configuration from GUI
RecastConfig cfg = new RecastConfig(partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, bmin, bmax);
//
// Step 2. Rasterize input polygon soup.
//
// Allocate voxel heightfield where we rasterize our input data to.
Heightfield m_solid = new Heightfield(bcfg.width, bcfg.height, bcfg.bmin, bcfg.bmax, cfg.cs, cfg.ch, cfg.borderSize);
foreach (TriMesh geom in geomProvider.meshes())
{
float[] verts = geom.getVerts();
int[] tris = geom.getTris();
int ntris = tris.Length / 3;
// Allocate array that can hold triangle area types.
// If you have multiple meshes you need to process, allocate
// and array which can hold the max number of triangles you need to
// process.
// Find triangles which are walkable based on their slope and rasterize
// them.
// If your input data is multiple meshes, you can transform them here,
// calculate
// the are type for each of the meshes and rasterize them.
int[] m_triareas = Recast.markWalkableTriangles(m_ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
cfg.walkableAreaMod);
RecastRasterization.rasterizeTriangles(m_solid, verts, tris, m_triareas, ntris, cfg.walkableClimb, m_ctx);
//
// Step 3. Filter walkables surfaces.
//
}
// Once all geometry is rasterized, we do initial pass of filtering to
// remove unwanted overhangs caused by the conservative rasterization
// as well as filter spans where the character cannot possibly stand.
RecastFilter.filterLowHangingWalkableObstacles(m_ctx, cfg.walkableClimb, m_solid);
RecastFilter.filterLedgeSpans(m_ctx, cfg.walkableHeight, cfg.walkableClimb, m_solid);
RecastFilter.filterWalkableLowHeightSpans(m_ctx, cfg.walkableHeight, m_solid);
//
// Step 4. Partition walkable surface to simple regions.
//
// 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
// between walkable cells will be calculated.
CompactHeightfield m_chf = RecastCompact.buildCompactHeightfield(m_ctx, cfg.walkableHeight, cfg.walkableClimb,
m_solid);
// Erode the walkable area by agent radius.
RecastArea.erodeWalkableArea(m_ctx, cfg.walkableRadius, m_chf);
// (Optional) Mark areas.
/*
* ConvexVolume vols = m_geom->getConvexVolumes(); for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i)
* rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned
* char)vols[i].area, *m_chf);
*/
// Partition the heightfield so that we can use simple algorithm later
// to triangulate the walkable areas.
// There are 3 martitioning methods, each with some pros and cons:
// 1) Watershed partitioning
// - the classic Recast partitioning
// - creates the nicest tessellation
// - usually slowest
// - partitions the heightfield into nice regions without holes or
// overlaps
// - the are some corner cases where this method creates produces holes
// and overlaps
// - holes may appear when a small obstacles is close to large open area
// (triangulation can handle this)
// - overlaps may occur if you have narrow spiral corridors (i.e
// stairs), this make triangulation to fail
// * generally the best choice if you precompute the nacmesh, use this
// if you have large open areas
// 2) Monotone partioning
// - fastest
// - partitions the heightfield into regions without holes and overlaps
// (guaranteed)
// - creates long thin polygons, which sometimes causes paths with
// detours
// * use this if you want fast navmesh generation
// 3) Layer partitoining
// - quite fast
// - partitions the heighfield into non-overlapping regions
// - relies on the triangulation code to cope with holes (thus slower
// than monotone partitioning)
// - produces better triangles than monotone partitioning
// - does not have the corner cases of watershed partitioning
// - can be slow and create a bit ugly tessellation (still better than
// monotone)
// if you have large open areas with small obstacles (not a problem if
// you use tiles)
// * good choice to use for tiled navmesh with medium and small sized
// tiles
long time3 = Stopwatch.GetTimestamp();
if (m_partitionType == PartitionType.WATERSHED)
{
// Prepare for region partitioning, by calculating distance field
// along the walkable surface.
RecastRegion.buildDistanceField(m_ctx, m_chf);
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildRegions(m_ctx, m_chf, cfg.minRegionArea, cfg.mergeRegionArea);
}
else if (m_partitionType == PartitionType.MONOTONE)
{
// Partition the walkable surface into simple regions without holes.
// Monotone partitioning does not need distancefield.
RecastRegion.buildRegionsMonotone(m_ctx, m_chf, cfg.minRegionArea, cfg.mergeRegionArea);
}
else
{
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildLayerRegions(m_ctx, m_chf, cfg.minRegionArea);
}
Assert.That(m_chf.maxDistance, Is.EqualTo(expDistance), "maxDistance");
Assert.That(m_chf.maxRegions, Is.EqualTo(expRegions), "Regions");
//
// Step 5. Trace and simplify region contours.
//
// Create contours.
ContourSet m_cset = RecastContour.buildContours(m_ctx, m_chf, cfg.maxSimplificationError, cfg.maxEdgeLen,
RecastConstants.RC_CONTOUR_TESS_WALL_EDGES);
Assert.That(m_cset.conts.Count, Is.EqualTo(expContours), "Contours");
//
// Step 6. Build polygons mesh from contours.
//
// Build polygon navmesh from the contours.
PolyMesh m_pmesh = RecastMesh.buildPolyMesh(m_ctx, m_cset, cfg.maxVertsPerPoly);
Assert.That(m_pmesh.nverts, Is.EqualTo(expVerts), "Mesh Verts");
Assert.That(m_pmesh.npolys, Is.EqualTo(expPolys), "Mesh Polys");
//
// Step 7. Create detail mesh which allows to access approximate height
// on each polygon.
//
PolyMeshDetail m_dmesh = RecastMeshDetail.buildPolyMeshDetail(m_ctx, m_pmesh, m_chf, cfg.detailSampleDist,
cfg.detailSampleMaxError);
Assert.That(m_dmesh.nmeshes, Is.EqualTo(expDetMeshes), "Mesh Detail Meshes");
Assert.That(m_dmesh.nverts, Is.EqualTo(expDetVerts), "Mesh Detail Verts");
Assert.That(m_dmesh.ntris, Is.EqualTo(expDetTris), "Mesh Detail Tris");
long time2 = Stopwatch.GetTimestamp();
Console.WriteLine(filename + " : " + partitionType + " " + (time2 - time) / 1000000 + " ms");
Console.WriteLine(" " + (time3 - time) / 1000000 + " ms");
saveObj(filename.Substring(0, filename.LastIndexOf('.')) + "_" + partitionType + "_detail.obj", m_dmesh);
saveObj(filename.Substring(0, filename.LastIndexOf('.')) + "_" + partitionType + ".obj", m_pmesh);
m_ctx.print();
}
private void saveObj(string filename, PolyMesh mesh)
{
try
{
string path = Path.Combine("test-output", filename);
Directory.CreateDirectory(Path.GetDirectoryName(path));
using StreamWriter fw = new StreamWriter(path);
for (int v = 0; v < mesh.nverts; v++)
{
fw.Write("v " + (mesh.bmin[0] + mesh.verts[v * 3] * mesh.cs) + " "
+ (mesh.bmin[1] + mesh.verts[v * 3 + 1] * mesh.ch) + " "
+ (mesh.bmin[2] + mesh.verts[v * 3 + 2] * mesh.cs) + "\n");
}
for (int i = 0; i < mesh.npolys; i++)
{
int p = i * mesh.nvp * 2;
fw.Write("f ");
for (int j = 0; j < mesh.nvp; ++j)
{
int v = mesh.polys[p + j];
if (v == RC_MESH_NULL_IDX)
{
break;
}
fw.Write((v + 1) + " ");
}
fw.Write("\n");
}
fw.Close();
}
catch (Exception e)
{
}
}
private void saveObj(string filename, PolyMeshDetail dmesh)
{
try
{
string filePath = Path.Combine("test-output", filename);
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
using StreamWriter fw = new StreamWriter(filePath);
for (int v = 0; v < dmesh.nverts; v++)
{
fw.Write(
"v " + dmesh.verts[v * 3] + " " + dmesh.verts[v * 3 + 1] + " " + dmesh.verts[v * 3 + 2] + "\n");
}
for (int m = 0; m < dmesh.nmeshes; m++)
{
int vfirst = dmesh.meshes[m * 4];
int tfirst = dmesh.meshes[m * 4 + 2];
for (int f = 0; f < dmesh.meshes[m * 4 + 3]; f++)
{
fw.Write("f " + (vfirst + dmesh.tris[(tfirst + f) * 4] + 1) + " "
+ (vfirst + dmesh.tris[(tfirst + f) * 4 + 1] + 1) + " "
+ (vfirst + dmesh.tris[(tfirst + f) * 4 + 2] + 1) + "\n");
}
}
fw.Close();
}
catch (Exception e)
{
}
}
}

View File

@ -0,0 +1,55 @@
/*
recast4j Copyright (c) 2015-2019 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 NUnit.Framework;
namespace DotRecast.Recast.Test;
using static RecastConstants;
public class RecastTest
{
[Test]
public void testClearUnwalkableTriangles()
{
float walkableSlopeAngle = 45;
float[] verts = { 0, 0, 0, 1, 0, 0, 0, 0, -1 };
int nv = 3;
int[] walkable_tri = { 0, 1, 2 };
int[] unwalkable_tri = { 0, 2, 1 };
int nt = 1;
Telemetry ctx = new Telemetry();
{
int[] areas = { 42 };
Recast.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");
}
{
int[] areas = { 42 };
Recast.clearUnwalkableTriangles(ctx, walkableSlopeAngle, verts, nv, walkable_tri, nt, areas);
Assert.That(areas[0], Is.EqualTo(42), "Does not modify walkable triangle aread ID's");
}
{
int[] areas = { 42 };
walkableSlopeAngle = 0;
Recast.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.");
}
}
}

View File

@ -0,0 +1,167 @@
/*
recast4j Copyright (c) 2015-2019 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.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DotRecast.Core;
using DotRecast.Recast.Geom;
using NUnit.Framework;
namespace DotRecast.Recast.Test;
using static RecastConstants;
public class RecastTileMeshTest
{
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f;
private const float m_agentRadius = 0.6f;
private const float m_agentMaxClimb = 0.9f;
private const float m_agentMaxSlope = 45.0f;
private const int m_regionMinSize = 8;
private const int m_regionMergeSize = 20;
private const float m_regionMinArea = m_regionMinSize * m_regionMinSize * m_cellSize * m_cellSize;
private const float m_regionMergeArea = m_regionMergeSize * m_regionMergeSize * m_cellSize * m_cellSize;
private const float m_edgeMaxLen = 12.0f;
private const float m_edgeMaxError = 1.3f;
private const int m_vertsPerPoly = 6;
private const float m_detailSampleDist = 6.0f;
private const float m_detailSampleMaxError = 1.0f;
private const PartitionType m_partitionType = PartitionType.WATERSHED;
private const int m_tileSize = 32;
[Test]
public void testDungeon()
{
testBuild("dungeon.obj");
}
public void testBuild(string filename)
{
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes(filename));
RecastBuilder builder = new RecastBuilder();
RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_regionMinArea, m_regionMergeArea, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, true,
m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), 7, 8);
RecastBuilderResult rcResult = builder.build(geom, bcfg);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(1));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(5));
bcfg = new RecastBuilderConfig(cfg, geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), 6, 9);
rcResult = builder.build(geom, bcfg);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(2));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(7));
bcfg = new RecastBuilderConfig(cfg, geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), 2, 9);
rcResult = builder.build(geom, bcfg);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(2));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(9));
bcfg = new RecastBuilderConfig(cfg, geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), 4, 3);
rcResult = builder.build(geom, bcfg);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(3));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(6));
bcfg = new RecastBuilderConfig(cfg, geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), 2, 8);
rcResult = builder.build(geom, bcfg);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(5));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(17));
bcfg = new RecastBuilderConfig(cfg, geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), 0, 8);
rcResult = builder.build(geom, bcfg);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(6));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(15));
}
[Test]
public void testPerformance()
{
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
RecastBuilder builder = new RecastBuilder();
RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_regionMinArea, m_regionMergeArea, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, true,
m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
for (int i = 0; i < 4; i++)
{
build(geom, builder, cfg, 1, true);
build(geom, builder, cfg, 4, true);
}
long t1 = Stopwatch.GetTimestamp();
for (int i = 0; i < 4; i++)
{
build(geom, builder, cfg, 1, false);
}
long t2 = Stopwatch.GetTimestamp();
for (int i = 0; i < 4; i++)
{
build(geom, builder, cfg, 4, false);
}
long t3 = Stopwatch.GetTimestamp();
Console.WriteLine(" Time ST : " + (t2 - t1) / 1000000);
Console.WriteLine(" Time MT : " + (t3 - t2) / 1000000);
}
private void build(InputGeomProvider geom, RecastBuilder builder, RecastConfig cfg, int threads, bool validate)
{
CancellationTokenSource cts = new CancellationTokenSource();
List<RecastBuilderResult> tiles = new();
var task = builder.buildTilesAsync(geom, cfg, threads, tiles, Task.Factory, cts.Token);
if (validate)
{
RecastBuilderResult rcResult = getTile(tiles, 7, 8);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(1));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(5));
rcResult = getTile(tiles, 6, 9);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(2));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(7));
rcResult = getTile(tiles, 2, 9);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(2));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(9));
rcResult = getTile(tiles, 4, 3);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(3));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(6));
rcResult = getTile(tiles, 2, 8);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(5));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(17));
rcResult = getTile(tiles, 0, 8);
Assert.That(rcResult.getMesh().npolys, Is.EqualTo(6));
Assert.That(rcResult.getMesh().nverts, Is.EqualTo(15));
}
try
{
cts.Cancel();
//executor.awaitTermination(1000, TimeUnit.HOURS);
}
catch (Exception e)
{
}
}
private RecastBuilderResult getTile(List<RecastBuilderResult> tiles, int x, int z)
{
return tiles.FirstOrDefault(tile => tile.tileX == x && tile.tileZ == z);
}
}

View File

@ -0,0 +1,60 @@
/*
recast4j Copyright (c) 2015-2019 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.
*/
namespace DotRecast.Recast.Test;
public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
/// Value for the kind of ceil "ground"
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
/// Value for the kind of ceil "water"
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
/// Value for the kind of ceil "road"
public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x3;
/// Value for the kind of ceil "grass"
public const int SAMPLE_POLYAREA_TYPE_GRASS = 0x4;
/// Flag for door area. Can be combined with area types and jump flag.
public const int SAMPLE_POLYAREA_FLAG_DOOR = 0x08;
/// Flag for jump area. Can be combined with area types and door flag.
public const int SAMPLE_POLYAREA_FLAG_JUMP = 0x10;
public static readonly AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static readonly AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static readonly AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static readonly AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static readonly AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_FLAG_DOOR,
SAMPLE_POLYAREA_FLAG_DOOR);
public static readonly AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_FLAG_JUMP,
SAMPLE_POLYAREA_FLAG_JUMP);
}