diff --git a/.changeset/config.json b/.changeset/config.json index 1e9bf73f..ae1a1ff6 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -12,7 +12,8 @@ "node-cjs-example", "node-esm-example", "parcel-example", - "vite-example" + "vite-wasm-example", + "vite-worker-example" ], "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { "onlyUpdatePeerDependentsWhenOutOfRange": true diff --git a/.changeset/eighty-hounds-applaud.md b/.changeset/eighty-hounds-applaud.md new file mode 100644 index 00000000..f97d634c --- /dev/null +++ b/.changeset/eighty-hounds-applaud.md @@ -0,0 +1,6 @@ +--- +"@recast-navigation/core": minor +"recast-navigation": minor +--- + +feat: change Crowd and NavMeshQuery constructors to take NavMesh as first arg, and options as second diff --git a/.changeset/few-weeks-jam.md b/.changeset/few-weeks-jam.md new file mode 100644 index 00000000..19f3ff5f --- /dev/null +++ b/.changeset/few-weeks-jam.md @@ -0,0 +1,7 @@ +--- +"@recast-navigation/core": minor +"@recast-navigation/wasm": minor +"recast-navigation": minor +--- + +fix: change findPolysAroundCircle resultCost to be a FloatArray, not a FloatRef diff --git a/.changeset/moody-boats-own.md b/.changeset/moody-boats-own.md new file mode 100644 index 00000000..f2eeb0ef --- /dev/null +++ b/.changeset/moody-boats-own.md @@ -0,0 +1,6 @@ +--- +"@recast-navigation/core": minor +"recast-navigation": minor +--- + +feat(NavMeshQuery): rename closestPointOnPoly posOverPoly return to isPointOverPoly diff --git a/.changeset/real-boxes-crash.md b/.changeset/real-boxes-crash.md new file mode 100644 index 00000000..ce6e9e87 --- /dev/null +++ b/.changeset/real-boxes-crash.md @@ -0,0 +1,32 @@ +--- +"@recast-navigation/core": minor +"recast-navigation": minor +--- + +feat: support separate wasm file as well as inlined wasm file + +Progresses https://github.com/isaac-mason/recast-navigation-js/issues/164 + +Now `init` can be optionally passed a default import of one of the `@recast-navigation/wasm` packages. + +The `@recast-navigation/wasm` package is no longer included in the `@recast-navigation/core` package. If nothing is passed to `init`, the inlined wasm-compat flavor is dynamically imported. + +Note that the other `wasm` flavor currently does not support node.js environments. + +```ts +import { init } from 'recast-navigation'; + +// import the 'wasm' flavor - has a separate wasm file, not inlined +import RecastWasm from '@recast-navigation/wasm/wasm'; + +await init(RecastWasm); +``` + +It's still possible to use the inlined wasm flavor by not passing anything to `init` as before. + +```ts +import { init } from 'recast-navigation'; + +// internally dynamically imports `@recast-navigation/wasm` +await init(); +``` diff --git a/.changeset/sharp-maps-relate.md b/.changeset/sharp-maps-relate.md new file mode 100644 index 00000000..3af621e8 --- /dev/null +++ b/.changeset/sharp-maps-relate.md @@ -0,0 +1,6 @@ +--- +"@recast-navigation/core": minor +"recast-navigation": minor +--- + +feat(Arrays): rename 'free' to 'destroy' for consistency with other methods diff --git a/.changeset/short-geese-give.md b/.changeset/short-geese-give.md new file mode 100644 index 00000000..fa46e419 --- /dev/null +++ b/.changeset/short-geese-give.md @@ -0,0 +1,6 @@ +--- +"@recast-navigation/core": minor +"recast-navigation": minor +--- + +fix: missing cleanup for Raw Vec3 and \*Ref classes diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 15413065..54c87a76 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -18,10 +18,10 @@ jobs: - name: Checkout Repo uses: actions/checkout@v2 - - name: Setup Node.js 18.x + - name: Setup Node.js 22.x uses: actions/setup-node@v2 with: - node-version: 18.x + node-version: 22.x - name: Install Dependencies run: yarn diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7072db4..18d5ac0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,10 +18,10 @@ jobs: - name: Checkout Repo uses: actions/checkout@v2 - - name: Setup Node.js 18.x + - name: Setup Node.js 22.x uses: actions/setup-node@v2 with: - node-version: 18.x + node-version: 22.x - name: Install Dependencies run: yarn diff --git a/.nvmrc b/.nvmrc index e44a38e0..33206cba 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.12.1 +v22.0.0 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 179fd80c..73b14298 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -6,7 +6,7 @@ The `recast-navigation-js` repository is structured as a yarn monorepo. You will ### Node Installation -**This project uses node 18.** +**This project uses node 22.** If you don't already use a node version manager. Give nvm a try if you don't already have one. diff --git a/apps/navmesh-website/src/features/recast/crowd/recast-agent.tsx b/apps/navmesh-website/src/features/recast/crowd/recast-agent.tsx index 8392124e..fe7d68be 100644 --- a/apps/navmesh-website/src/features/recast/crowd/recast-agent.tsx +++ b/apps/navmesh-website/src/features/recast/crowd/recast-agent.tsx @@ -62,10 +62,9 @@ export const RecastAgent = forwardRef( return; } - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); - const crowd = new Crowd({ - navMesh, + const crowd = new Crowd(navMesh, { maxAgents: 1, maxAgentRadius: agentRadius, }); diff --git a/examples/no-bundler/index.html b/examples/no-bundler/index.html new file mode 100644 index 00000000..6cb799d7 --- /dev/null +++ b/examples/no-bundler/index.html @@ -0,0 +1,84 @@ + + + + + diff --git a/examples/node-cjs-example/index.cjs b/examples/node-cjs-example/index.cjs index 1434c46d..4c2c2398 100644 --- a/examples/node-cjs-example/index.cjs +++ b/examples/node-cjs-example/index.cjs @@ -33,7 +33,7 @@ Recast.init().then(() => { const { navMesh } = RecastGenerators.generateSoloNavMesh(positions, indices, config); - const navMeshQuery = new Recast.NavMeshQuery({ navMesh }); + const navMeshQuery = new Recast.NavMeshQuery(navMesh); const { point: closestPoint } = navMeshQuery.findClosestPoint({ x: 2, y: 1, z: 2 }); diff --git a/examples/node-cjs-example/package.json b/examples/node-cjs-example/package.json index 8d503dd6..801e5435 100644 --- a/examples/node-cjs-example/package.json +++ b/examples/node-cjs-example/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "description": "example of using recast-navigation in node", "scripts": { - "start": "node index.cjs" + "start": "node --experimental-require-module index.cjs" }, "author": "", "license": "ISC", diff --git a/examples/node-esm-example/index.mjs b/examples/node-esm-example/index.mjs index 4657af88..e76cb31d 100644 --- a/examples/node-esm-example/index.mjs +++ b/examples/node-esm-example/index.mjs @@ -34,7 +34,7 @@ const indices = groundMesh.geometry.index.array; const { navMesh } = generateSoloNavMesh(positions, indices, config); -const navMeshQuery = new NavMeshQuery({ navMesh }); +const navMeshQuery = new NavMeshQuery(navMesh); const { point: closestPoint } = navMeshQuery.findClosestPoint({ x: 2, y: 1, z: 2 }); diff --git a/examples/vite-example/.gitignore b/examples/vite-wasm-example/.gitignore similarity index 100% rename from examples/vite-example/.gitignore rename to examples/vite-wasm-example/.gitignore diff --git a/examples/vite-example/.prettierrc.json b/examples/vite-wasm-example/.prettierrc.json similarity index 100% rename from examples/vite-example/.prettierrc.json rename to examples/vite-wasm-example/.prettierrc.json diff --git a/examples/vite-example/index.html b/examples/vite-wasm-example/index.html similarity index 100% rename from examples/vite-example/index.html rename to examples/vite-wasm-example/index.html diff --git a/examples/vite-example/package.json b/examples/vite-wasm-example/package.json similarity index 95% rename from examples/vite-example/package.json rename to examples/vite-wasm-example/package.json index 9c91a02a..fc46bfaf 100644 --- a/examples/vite-example/package.json +++ b/examples/vite-wasm-example/package.json @@ -1,5 +1,5 @@ { - "name": "vite-example", + "name": "vite-wasm-example", "private": true, "type": "module", "scripts": { diff --git a/examples/vite-example/src/app.tsx b/examples/vite-wasm-example/src/app.tsx similarity index 90% rename from examples/vite-example/src/app.tsx rename to examples/vite-wasm-example/src/app.tsx index d7f73904..c90390c3 100644 --- a/examples/vite-example/src/app.tsx +++ b/examples/vite-wasm-example/src/app.tsx @@ -1,5 +1,6 @@ -import { Environment, OrbitControls } from '@react-three/drei'; +import { OrbitControls } from '@react-three/drei'; import { Canvas, useThree } from '@react-three/fiber'; +import RecastWasm from '@recast-navigation/wasm/wasm'; import { useEffect, useRef } from 'react'; import { NavMeshQuery, init } from 'recast-navigation'; import { NavMeshHelper, threeToSoloNavMesh } from 'recast-navigation/three'; @@ -39,7 +40,7 @@ const App = () => { if (!success) return; - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); const navMeshHelper = new NavMeshHelper({ navMesh, @@ -109,7 +110,7 @@ const App = () => { }; const RecastInit = (props: { children: JSX.Element }) => { - suspend(() => init(), []); + suspend(() => init(RecastWasm), []); return props.children; }; @@ -119,7 +120,8 @@ export default () => ( - + + diff --git a/examples/vite-example/src/index.css b/examples/vite-wasm-example/src/index.css similarity index 100% rename from examples/vite-example/src/index.css rename to examples/vite-wasm-example/src/index.css diff --git a/examples/vite-example/src/main.tsx b/examples/vite-wasm-example/src/main.tsx similarity index 100% rename from examples/vite-example/src/main.tsx rename to examples/vite-wasm-example/src/main.tsx diff --git a/examples/vite-example/src/vite-env.d.ts b/examples/vite-wasm-example/src/vite-env.d.ts similarity index 100% rename from examples/vite-example/src/vite-env.d.ts rename to examples/vite-wasm-example/src/vite-env.d.ts diff --git a/examples/vite-example/tsconfig.json b/examples/vite-wasm-example/tsconfig.json similarity index 100% rename from examples/vite-example/tsconfig.json rename to examples/vite-wasm-example/tsconfig.json diff --git a/examples/vite-example/tsconfig.node.json b/examples/vite-wasm-example/tsconfig.node.json similarity index 100% rename from examples/vite-example/tsconfig.node.json rename to examples/vite-wasm-example/tsconfig.node.json diff --git a/examples/vite-example/vite.config.ts b/examples/vite-wasm-example/vite.config.ts similarity index 100% rename from examples/vite-example/vite.config.ts rename to examples/vite-wasm-example/vite.config.ts diff --git a/examples/vite-worker-example/.gitignore b/examples/vite-worker-example/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/examples/vite-worker-example/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/vite-worker-example/.prettierrc.json b/examples/vite-worker-example/.prettierrc.json new file mode 100644 index 00000000..0a725205 --- /dev/null +++ b/examples/vite-worker-example/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/examples/vite-worker-example/index.html b/examples/vite-worker-example/index.html new file mode 100644 index 00000000..e0d1c840 --- /dev/null +++ b/examples/vite-worker-example/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/examples/vite-worker-example/package.json b/examples/vite-worker-example/package.json new file mode 100644 index 00000000..ab583a17 --- /dev/null +++ b/examples/vite-worker-example/package.json @@ -0,0 +1,27 @@ +{ + "name": "vite-worker-example", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@react-three/drei": "^9.105.4", + "@react-three/fiber": "^8.15.12", + "react": "^18.3.1", + "react-dom": "^18.2.0", + "recast-navigation": "0.27.0", + "suspend-react": "^0.1.3", + "three": "^0.163.0" + }, + "devDependencies": { + "@types/react": "^18.3.1", + "@types/react-dom": "^18.2.8", + "@types/three": "^0.163.0", + "@vitejs/plugin-react": "^4.2.0", + "typescript": "^5.4.3", + "vite": "^5.2.8" + } +} diff --git a/examples/vite-worker-example/src/app.tsx b/examples/vite-worker-example/src/app.tsx new file mode 100644 index 00000000..65efc8fb --- /dev/null +++ b/examples/vite-worker-example/src/app.tsx @@ -0,0 +1,110 @@ +import { Environment, OrbitControls } from '@react-three/drei'; +import { Canvas, useThree } from '@react-three/fiber'; +import { useEffect, useState } from 'react'; +import { NavMesh, importNavMesh, init } from 'recast-navigation'; +import { DebugDrawer, getPositionsAndIndices } from 'recast-navigation/three'; +import { suspend } from 'suspend-react'; +import { Mesh } from 'three'; +import NavMeshWorker from './navmesh-worker?worker'; + +const App = () => { + const scene = useThree((state) => state.scene); + + useEffect(() => { + const debugDrawer = new DebugDrawer(); + + const meshes: Mesh[] = []; + + scene.traverse((child) => { + if (child instanceof Mesh) { + meshes.push(child); + } + }); + + const [positions, indices] = getPositionsAndIndices(meshes); + + const config = { + cs: 0.05, + ch: 0.2, + }; + + const worker = new NavMeshWorker(); + + let navMesh: NavMesh | undefined; + + worker.onmessage = (event) => { + const navMeshExport = event.data; + + const result = importNavMesh(navMeshExport); + + navMesh = result.navMesh; + + debugDrawer.clear(); + debugDrawer.drawNavMesh(navMesh); + + console.log('here'); + }; + + worker.postMessage({ positions, indices, config }, [ + positions.buffer, + indices.buffer, + ]); + + const onResize = () => { + debugDrawer.resize(window.innerWidth, window.innerHeight); + }; + + onResize(); + + window.addEventListener('resize', onResize); + + scene.add(debugDrawer); + + return () => { + worker.terminate(); + + if (navMesh) { + navMesh.destroy(); + } + + scene.remove(debugDrawer); + window.removeEventListener('resize', onResize); + debugDrawer.dispose(); + }; + }, []); + + return ( + <> + {/* ground */} + + + + + + {/* obstacle */} + + + + + + ); +}; + +const RecastInit = (props: { children: JSX.Element }) => { + suspend(() => init(), []); + + return props.children; +}; + +export default () => ( + + + + + + + + + + +); diff --git a/examples/vite-worker-example/src/index.css b/examples/vite-worker-example/src/index.css new file mode 100644 index 00000000..bee437f7 --- /dev/null +++ b/examples/vite-worker-example/src/index.css @@ -0,0 +1,11 @@ +body { + margin: 0; + padding: 0; +} + +body, +#root, +canvas { + width: 100%; + height: 100vh; +} diff --git a/examples/vite-worker-example/src/main.tsx b/examples/vite-worker-example/src/main.tsx new file mode 100644 index 00000000..52a767ff --- /dev/null +++ b/examples/vite-worker-example/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './app'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + +); diff --git a/examples/vite-worker-example/src/navmesh-worker.ts b/examples/vite-worker-example/src/navmesh-worker.ts new file mode 100644 index 00000000..091defd5 --- /dev/null +++ b/examples/vite-worker-example/src/navmesh-worker.ts @@ -0,0 +1,24 @@ +import { RecastConfig, exportNavMesh, init } from '@recast-navigation/core'; +import { generateSoloNavMesh } from '@recast-navigation/generators'; + +self.onmessage = async (event: { + data: { + positions: Float32Array; + indices: Uint32Array; + config: Partial; + }; +}) => { + await init(); + + const { positions, indices, config } = event.data; + + const { success, navMesh } = generateSoloNavMesh(positions, indices, config); + + if (!success) return; + + const navMeshExport = exportNavMesh(navMesh); + + self.postMessage(navMeshExport, { transfer: [navMeshExport.buffer] }); + + navMesh.destroy(); +}; diff --git a/examples/vite-worker-example/src/vite-env.d.ts b/examples/vite-worker-example/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/vite-worker-example/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/vite-worker-example/tsconfig.json b/examples/vite-worker-example/tsconfig.json new file mode 100644 index 00000000..232da89b --- /dev/null +++ b/examples/vite-worker-example/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ES2022", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/vite-worker-example/tsconfig.node.json b/examples/vite-worker-example/tsconfig.node.json new file mode 100644 index 00000000..fd1518a4 --- /dev/null +++ b/examples/vite-worker-example/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ES2022", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/vite-worker-example/vite.config.ts b/examples/vite-worker-example/vite.config.ts new file mode 100644 index 00000000..5a33944a --- /dev/null +++ b/examples/vite-worker-example/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/package.json b/package.json index 20743774..019dea83 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test": "concurrently --kill-others-on-fail \"yarn test:packages\" \"yarn test:node-smoke-test\" \"yarn test:bundlers-smoke-test\" \"yarn lint\"", "test:packages": "yarn workspaces foreach -A -t --include recast-navigation --include @recast-navigation/core --include @recast-navigation/three --include @recast-navigation/generators run test", "test:node-smoke-test": "(cd ./examples/node-cjs-example && yarn start) && (cd ./examples/node-esm-example && yarn start)", - "test:bundlers-smoke-test": "concurrently --kill-others-on-fail \"(cd ./examples/parcel-example && yarn build)\" \"(cd ./examples/vite-example && yarn build)\"", + "test:bundlers-smoke-test": "concurrently --kill-others-on-fail \"(cd ./examples/parcel-example && yarn build)\" \"(cd ./examples/vite-wasm-example && yarn build)\"", "lint": "yarn workspaces foreach -A -t run lint", "change": "yarn changeset", "release": "yarn build && yarn test", diff --git a/packages/recast-navigation-core/package.json b/packages/recast-navigation-core/package.json index e8b73361..801cb80c 100644 --- a/packages/recast-navigation-core/package.json +++ b/packages/recast-navigation-core/package.json @@ -12,7 +12,8 @@ "bugs": { "url": "https://github.com/isaac-mason/recast-navigation-js/issues" }, - "main": "./dist/index.cjs", + "type": "module", + "main": "./dist/index.mjs", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "files": [ @@ -29,8 +30,11 @@ "@recast-navigation/wasm": "0.27.0" }, "devDependencies": { - "@babel/core": "^7.24.4", + "@babel/core": "^7.24.5", + "@babel/preset-env": "^7.24.5", + "@babel/preset-typescript": "^7.24.1", "@isaac-mason/eslint-config-typescript": "^0.0.5", + "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-terser": "^0.4.3", diff --git a/packages/recast-navigation-core/rollup.config.js b/packages/recast-navigation-core/rollup.config.js index 5aab5f97..dc5bbf99 100644 --- a/packages/recast-navigation-core/rollup.config.js +++ b/packages/recast-navigation-core/rollup.config.js @@ -1,13 +1,33 @@ +import babel from '@rollup/plugin-babel'; import commonjs from '@rollup/plugin-commonjs'; -import { nodeResolve } from '@rollup/plugin-node-resolve'; +import resolve from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; import typescript from '@rollup/plugin-typescript'; import path from 'path'; import filesize from 'rollup-plugin-filesize'; +const babelOptions = { + babelrc: false, + extensions: ['.ts'], + exclude: '**/node_modules/**', + babelHelpers: 'bundled', + presets: [ + [ + '@babel/preset-env', + { + loose: true, + modules: false, + targets: '>1%, not dead, not ie 11, not op_mini all', + }, + ], + '@babel/preset-typescript', + ], +}; + export default [ { input: `./src/index.ts`, + external: ['@recast-navigation/wasm'], output: [ { file: `dist/index.mjs`, @@ -15,22 +35,16 @@ export default [ sourcemap: true, exports: 'named', }, - { - file: `dist/index.cjs`, - format: 'cjs', - sourcemap: true, - exports: 'named', - }, ], plugins: [ terser(), - nodeResolve(), + resolve(), commonjs(), typescript({ tsconfig: path.resolve(__dirname, `tsconfig.json`), - sourceMap: true, - inlineSources: true, + emitDeclarationOnly: true, }), + babel(babelOptions), filesize(), ], }, diff --git a/packages/recast-navigation-core/src/arrays.ts b/packages/recast-navigation-core/src/arrays.ts index 1498598d..322c82c5 100644 --- a/packages/recast-navigation-core/src/arrays.ts +++ b/packages/recast-navigation-core/src/arrays.ts @@ -1,5 +1,4 @@ -import { Raw } from './raw'; -import RawModule from './raw-module'; +import { Raw, RawModule } from './raw'; type TypedArray = | typeof Int32Array @@ -49,8 +48,8 @@ abstract class BaseArray< view.set(data); } - free() { - this.raw.free(); + destroy() { + Raw.destroy(this.raw); } getHeapView(): InstanceType { @@ -80,6 +79,16 @@ abstract class BaseArray< export class IntArray extends BaseArray { typedArrayClass = Int32Array; + /** + * Creates a new IntArray. + */ + constructor(); + + /** + * Creates a wrapper around an existing raw IntArray object. + */ + constructor(raw: RawModule.IntArray); + constructor(raw?: RawModule.IntArray) { super(raw ?? new Raw.Module.IntArray()); } @@ -88,7 +97,7 @@ export class IntArray extends BaseArray { return Raw.Module.HEAP32; } - static fromRaw(raw?: RawModule.IntArray) { + static fromRaw(raw: RawModule.IntArray) { return new this(raw); } } @@ -99,6 +108,16 @@ export class UnsignedIntArray extends BaseArray< > { typedArrayClass = Uint32Array; + /** + * Creates a new UnsignedIntArray. + */ + constructor(); + + /** + * Creates a wrapper around an existing raw UnsignedIntArray object. + */ + constructor(raw: RawModule.UnsignedIntArray); + constructor(raw?: RawModule.UnsignedIntArray) { super(raw ?? new Raw.Module.UnsignedIntArray()); } @@ -107,7 +126,7 @@ export class UnsignedIntArray extends BaseArray< return Raw.Module.HEAPU32; } - static fromRaw(raw?: RawModule.UnsignedIntArray) { + static fromRaw(raw: RawModule.UnsignedIntArray) { return new this(raw); } } @@ -118,6 +137,16 @@ export class UnsignedCharArray extends BaseArray< > { typedArrayClass = Uint8Array; + /** + * Creates a new UnsignedCharArray. + */ + constructor(); + + /** + * Creates a wrapper around an existing raw UnsignedCharArray object. + */ + constructor(raw: RawModule.UnsignedCharArray); + constructor(raw?: RawModule.UnsignedCharArray) { super(raw ?? new Raw.Module.UnsignedCharArray()); } @@ -126,7 +155,7 @@ export class UnsignedCharArray extends BaseArray< return Raw.Module.HEAPU8; } - static fromRaw(raw?: RawModule.UnsignedCharArray) { + static fromRaw(raw: RawModule.UnsignedCharArray) { return new this(raw); } } @@ -137,6 +166,16 @@ export class UnsignedShortArray extends BaseArray< > { typedArrayClass = Uint16Array; + /** + * Creates a new UnsignedShortArray. + */ + constructor(); + + /** + * Creates a wrapper around an existing raw UnsignedShortArray object. + */ + constructor(raw: RawModule.UnsignedShortArray); + constructor(raw?: RawModule.UnsignedShortArray) { super(raw ?? new Raw.Module.UnsignedShortArray()); } @@ -145,7 +184,7 @@ export class UnsignedShortArray extends BaseArray< return Raw.Module.HEAPU16; } - static fromRaw(raw?: RawModule.UnsignedShortArray) { + static fromRaw(raw: RawModule.UnsignedShortArray) { return new this(raw); } } @@ -156,6 +195,16 @@ export class FloatArray extends BaseArray< > { typedArrayClass = Float32Array; + /** + * Creates a new FloatArray. + */ + constructor(); + + /** + * Creates a wrapper around an existing raw FloatArray object. + */ + constructor(raw: RawModule.FloatArray); + constructor(raw?: RawModule.FloatArray) { super(raw ?? new Raw.Module.FloatArray()); } @@ -164,7 +213,7 @@ export class FloatArray extends BaseArray< return Raw.Module.HEAPF32; } - static fromRaw(raw?: RawModule.FloatArray) { + static fromRaw(raw: RawModule.FloatArray) { return new this(raw); } } diff --git a/packages/recast-navigation-core/src/crowd.ts b/packages/recast-navigation-core/src/crowd.ts index b3c5797c..e8e8090e 100644 --- a/packages/recast-navigation-core/src/crowd.ts +++ b/packages/recast-navigation-core/src/crowd.ts @@ -1,7 +1,6 @@ import type { NavMesh } from './nav-mesh'; import { NavMeshQuery, QueryFilter } from './nav-mesh-query'; -import { Raw } from './raw'; -import type R from './raw-module'; +import { Raw, RawModule } from './raw'; import { Vector3, vec3 } from './utils'; const Epsilon = 0.001; @@ -93,7 +92,7 @@ export const crowdAgentParamsDefaults: CrowdAgentParams = { }; export class CrowdAgent implements CrowdAgentParams { - raw: R.dtCrowdAgent; + raw: RawModule.dtCrowdAgent; get radius(): number { return this.raw.params.radius; @@ -384,15 +383,10 @@ export type CrowdParams = { * [Limit: > 0] */ maxAgentRadius: number; - - /** - * The navigation mesh to use for planning. - */ - navMesh: NavMesh; }; export class Crowd { - raw: R.dtCrowd; + raw: RawModule.dtCrowd; /** * The agents in the crowd. @@ -426,7 +420,20 @@ export class Crowd { */ navMeshQuery: NavMeshQuery; - constructor({ maxAgents, maxAgentRadius, navMesh }: CrowdParams) { + /** + * + * @param navMesh the navmesh the crowd will use for planning + * @param param1 the crowd parameters + * + * @example + * ```ts + * const crowd = new Crowd(navMesh, { + * maxAgents: 100, + * maxAgentRadius: 1, + * }); + * ``` + */ + constructor(navMesh: NavMesh, { maxAgents, maxAgentRadius }: CrowdParams) { this.navMesh = navMesh; this.raw = Raw.Detour.allocCrowd(); this.raw.init(maxAgents, maxAgentRadius, navMesh.raw.getNavMesh()); diff --git a/packages/recast-navigation-core/src/detour.ts b/packages/recast-navigation-core/src/detour.ts index fabedc29..aa96edeb 100644 --- a/packages/recast-navigation-core/src/detour.ts +++ b/packages/recast-navigation-core/src/detour.ts @@ -1,6 +1,5 @@ import { UnsignedCharArray } from './arrays'; -import { Raw } from './raw'; -import type R from './raw-module'; +import { Raw, type RawModule } from './raw'; import { RecastPolyMesh, RecastPolyMeshDetail } from './recast'; import { Vector3, Vector3Tuple, array, vec3 } from './utils'; @@ -61,9 +60,9 @@ export const statusToReadableString = (status: number): string => { }; export class DetourPolyDetail { - raw: R.dtPolyDetail; + raw: RawModule.dtPolyDetail; - constructor(raw: R.dtPolyDetail) { + constructor(raw: RawModule.dtPolyDetail) { this.raw = raw; } @@ -85,9 +84,9 @@ export class DetourPolyDetail { } export class DetourLink { - raw: R.dtLink; + raw: RawModule.dtLink; - constructor(raw: R.dtLink) { + constructor(raw: RawModule.dtLink) { this.raw = raw; } @@ -117,9 +116,9 @@ export class DetourLink { } export class DetourBVNode { - raw: R.dtBVNode; + raw: RawModule.dtBVNode; - constructor(raw: R.dtBVNode) { + constructor(raw: RawModule.dtBVNode) { this.raw = raw; } @@ -137,9 +136,9 @@ export class DetourBVNode { } export class DetourOffMeshConnection { - raw: R.dtOffMeshConnection; + raw: RawModule.dtOffMeshConnection; - constructor(raw: R.dtOffMeshConnection) { + constructor(raw: RawModule.dtOffMeshConnection) { this.raw = raw; } @@ -169,9 +168,9 @@ export class DetourOffMeshConnection { } export class DetourMeshHeader { - raw: R.dtMeshHeader; + raw: RawModule.dtMeshHeader; - constructor(raw: R.dtMeshHeader) { + constructor(raw: RawModule.dtMeshHeader) { this.raw = raw; } @@ -261,9 +260,9 @@ export class DetourMeshHeader { } export class DetourPoly { - raw: R.dtPoly; + raw: RawModule.dtPoly; - constructor(raw: R.dtPoly) { + constructor(raw: RawModule.dtPoly) { this.raw = raw; } @@ -297,9 +296,9 @@ export class DetourPoly { } export class DetourMeshTile { - raw: R.dtMeshTile; + raw: RawModule.dtMeshTile; - constructor(raw: R.dtMeshTile) { + constructor(raw: RawModule.dtMeshTile) { this.raw = raw; } @@ -394,9 +393,19 @@ export type OffMeshConnectionParams = { }; export class NavMeshCreateParams { - raw: R.dtNavMeshCreateParams; + raw: RawModule.dtNavMeshCreateParams; - constructor(raw?: R.dtNavMeshCreateParams) { + /** + * Creates a new NavMeshCreateParams object. + */ + constructor() + + /** + * Creates a wrapper around an existing raw NavMeshCreateParams object. + */ + constructor(raw: RawModule.dtNavMeshCreateParams) + + constructor(raw?: RawModule.dtNavMeshCreateParams) { this.raw = raw ?? new Raw.Module.dtNavMeshCreateParams(); } diff --git a/packages/recast-navigation-core/src/index.ts b/packages/recast-navigation-core/src/index.ts index 2f335fb0..47d22124 100644 --- a/packages/recast-navigation-core/src/index.ts +++ b/packages/recast-navigation-core/src/index.ts @@ -1,5 +1,3 @@ -import Wasm from './raw-module'; - export * from './arrays'; export * from './crowd'; export * from './detour'; @@ -10,4 +8,3 @@ export * from './recast'; export * from './serdes'; export * from './tile-cache'; export * from './utils'; -export { Wasm }; diff --git a/packages/recast-navigation-core/src/nav-mesh-query.ts b/packages/recast-navigation-core/src/nav-mesh-query.ts index fcff4e0c..68d05cde 100644 --- a/packages/recast-navigation-core/src/nav-mesh-query.ts +++ b/packages/recast-navigation-core/src/nav-mesh-query.ts @@ -1,11 +1,10 @@ import { FloatArray, UnsignedCharArray, UnsignedIntArray } from './arrays'; import { NavMesh } from './nav-mesh'; -import { Raw } from './raw'; -import type R from './raw-module'; +import { Raw, type RawModule } from './raw'; import { Vector3, array, vec3 } from './utils'; export class QueryFilter { - raw: R.dtQueryFilter; + raw: RawModule.dtQueryFilter; get includeFlags(): number { return this.raw.getIncludeFlags(); @@ -23,7 +22,18 @@ export class QueryFilter { this.raw.setExcludeFlags(flags); } - constructor(raw?: R.dtQueryFilter) { + /** + * Constructs a new query filter object. + */ + constructor(); + + /** + * Constructs a query filter wrapper for a raw dtQueryFilter object. + * @param raw + */ + constructor(raw: RawModule.dtQueryFilter); + + constructor(raw?: RawModule.dtQueryFilter) { this.raw = raw ?? new Raw.Module.dtQueryFilter(); } @@ -37,16 +47,27 @@ export class QueryFilter { } export type NavMeshQueryParams = { - navMesh: NavMesh; - /** * @default 2048 */ maxNodes?: number; + + /** + * Default query filter. + * + * If omitted, the default filter will include all flags and exclude none. + * + * ```ts + * this.defaultFilter = new QueryFilter(); + * this.defaultFilter.includeFlags = 0xffff; + * this.defaultFilter.excludeFlags = 0; + * ``` + */ + defaultQueryFilter?: QueryFilter; }; export class NavMeshQuery { - raw: R.NavMeshQuery; + raw: RawModule.NavMeshQuery; /** * Default query filter. @@ -58,17 +79,44 @@ export class NavMeshQuery { */ defaultQueryHalfExtents = { x: 1, y: 1, z: 1 }; - constructor(value: R.NavMeshQuery | NavMeshQueryParams) { + /** + * Constructs a new navigation mesh query object. + * @param navMesh the navigation mesh to use for the query + * @param params optional additional parameters + * + * @example + * ```ts + * const query = new NavMeshQuery(navMesh); + * ``` + * + * @example + * ```ts + * const query = new NavMeshQuery(navMesh, { maxNodes: 2048 }); + * ``` + */ + constructor(navMesh: NavMesh, params?: NavMeshQueryParams); + + /** + * Constructs a navigation mesh query object from a raw object. + * @param raw + */ + constructor(raw: RawModule.NavMeshQuery); + + constructor(value: RawModule.NavMeshQuery | NavMesh, params?: NavMeshQueryParams) { if (value instanceof Raw.Module.NavMeshQuery) { this.raw = value; } else { this.raw = new Raw.Module.NavMeshQuery(); - this.raw.init(value.navMesh.raw, value.maxNodes ?? 2048); + this.raw.init(value.raw, params?.maxNodes ?? 2048); } - this.defaultFilter = new QueryFilter(); - this.defaultFilter.includeFlags = 0xffff; - this.defaultFilter.excludeFlags = 0; + if (params?.defaultQueryFilter) { + this.defaultFilter = params.defaultQueryFilter; + } else { + this.defaultFilter = new QueryFilter(); + this.defaultFilter.includeFlags = 0xffff; + this.defaultFilter.excludeFlags = 0; + } } /** @@ -90,25 +138,34 @@ export class NavMeshQuery { halfExtents?: Vector3; } ) { - const nearestRef = new Raw.UnsignedIntRef(); - const nearestPoint = new Raw.Vec3(); - const isOverPoly = new Raw.BoolRef(); + const nearestRefRaw = new Raw.UnsignedIntRef(); + const nearestPointRaw = new Raw.Vec3(); + const isOverPolyRaw = new Raw.BoolRef(); const status = this.raw.findNearestPoly( vec3.toArray(position), vec3.toArray(options?.halfExtents ?? this.defaultQueryHalfExtents), options?.filter?.raw ?? this.defaultFilter.raw, - nearestRef, - nearestPoint, - isOverPoly + nearestRefRaw, + nearestPointRaw, + isOverPolyRaw ); + const nearestPoint = vec3.fromRaw(nearestPointRaw); + Raw.destroy(nearestPointRaw); + + const nearestRef = nearestRefRaw.value; + Raw.destroy(nearestRefRaw); + + const isOverPoly = isOverPolyRaw.value; + Raw.destroy(isOverPolyRaw); + return { success: Raw.Detour.statusSucceed(status), status, - nearestRef: nearestRef.value, - nearestPoint: vec3.fromRaw(nearestPoint), - isOverPoly: isOverPoly.value, + nearestRef, + nearestPoint, + isOverPoly, }; } @@ -142,9 +199,11 @@ export class NavMeshQuery { const resultRefArray = new UnsignedIntArray(); const resultParentArray = new UnsignedIntArray(); + const resultCostArray = new FloatArray(); resultRefArray.resize(maxPolys); resultParentArray.resize(maxPolys); - const resultCostRef = new Raw.FloatRef(); + resultCostArray.resize(maxPolys); + const resultCountRef = new Raw.IntRef(); const status = this.raw.findPolysAroundCircle( @@ -154,19 +213,27 @@ export class NavMeshQuery { filter.raw, resultRefArray.raw, resultParentArray.raw, - resultCostRef, + resultCostArray.raw, resultCountRef, maxPolys ); - const resultCost = resultCostRef.value; - const resultCount = resultCountRef.value; - const resultRefs = [...resultRefArray.getHeapView()]; + resultRefArray.destroy(); + const resultParents = [...resultParentArray.getHeapView()]; + resultParentArray.destroy(); - resultRefArray.free(); - resultParentArray.free(); + const resultCost = [...resultCostArray.getHeapView()]; + resultCostArray.destroy(); + + const resultCount = resultCountRef.value; + Raw.destroy(resultCountRef); + + // todo: freeing resultCostRef and resultCountRef intermittently throws + // memory related errors. + // const resultCost = resultCostRef.value; + // const resultCount = resultCountRef.value; return { success: Raw.Detour.statusSucceed(status), @@ -219,9 +286,10 @@ export class NavMeshQuery { ); const polyCount = polyCountRef.value; + Raw.destroy(polyCountRef); const polyRefs = [...polysRefsArray.getHeapView()]; - polysRefsArray.free(); + polysRefsArray.destroy(); return { success: Raw.Detour.statusSucceed(status), @@ -238,21 +306,27 @@ export class NavMeshQuery { * @param position The position to find the closest point to */ closestPointOnPoly(polyRef: number, position: Vector3) { - const closestPoint = new Raw.Vec3(); - const positionOverPoly = new Raw.BoolRef(); + const closestPointRaw = new Raw.Vec3(); + const positionOverPolyRaw = new Raw.BoolRef(); const status = this.raw.closestPointOnPoly( polyRef, vec3.toArray(position), - closestPoint, - positionOverPoly + closestPointRaw, + positionOverPolyRaw ); + const closestPoint = vec3.fromRaw(closestPointRaw); + Raw.destroy(closestPointRaw); + + const isPointOverPoly = positionOverPolyRaw.value; + Raw.destroy(positionOverPolyRaw); + return { success: Raw.Detour.statusSucceed(status), status, - closestPoint: vec3.fromRaw(closestPoint), - posOverPoly: positionOverPoly.value, + closestPoint, + isPointOverPoly, }; } @@ -294,12 +368,21 @@ export class NavMeshQuery { resultPointOverPoly ); + const polyRef = resultPolyRef.value; + Raw.destroy(resultPolyRef); + + const point = vec3.fromRaw(resultPoint); + Raw.destroy(resultPoint); + + const isPointOverPoly = resultPointOverPoly.value; + Raw.destroy(resultPointOverPoly); + return { success: Raw.Detour.statusSucceed(status), status, - polyRef: resultPolyRef.value, - point: vec3.fromRaw(resultPoint), - isPointOverPoly: resultPointOverPoly.value, + polyRef, + point, + isPointOverPoly, }; } @@ -338,9 +421,6 @@ export class NavMeshQuery { randomPolyRef: number; randomPoint: Vector3; } { - const randomPolyRef = new Raw.UnsignedIntRef(); - const randomPoint = new Raw.Vec3(); - const filter = options?.filter ?? this.defaultFilter; const halfExtents = options?.halfExtents ?? this.defaultQueryHalfExtents; @@ -366,20 +446,29 @@ export class NavMeshQuery { startRef = nearestPoly.nearestRef; } + const randomPolyRefRaw = new Raw.UnsignedIntRef(); + const randomPointRaw = new Raw.Vec3(); + const status = this.raw.findRandomPointAroundCircle( startRef, vec3.toArray(position), radius, filter.raw, - randomPolyRef, - randomPoint + randomPolyRefRaw, + randomPointRaw ); + const randomPolyRef = randomPolyRefRaw.value; + Raw.destroy(randomPolyRefRaw); + + const randomPoint = vec3.fromRaw(randomPointRaw); + Raw.destroy(randomPointRaw); + return { success: Raw.Detour.statusSucceed(status), status, - randomPolyRef: randomPolyRef.value, - randomPoint: vec3.fromRaw(randomPoint), + randomPolyRef, + randomPoint, }; } @@ -412,7 +501,7 @@ export class NavMeshQuery { ) { const maxVisitedSize = options?.maxVisitedSize ?? 256; - const resultPosition = new Raw.Vec3(); + const resultPositionRaw = new Raw.Vec3(); const visitedArray = new UnsignedIntArray(); const filter = options?.filter?.raw ?? this.defaultFilter.raw; @@ -422,18 +511,21 @@ export class NavMeshQuery { vec3.toArray(startPosition), vec3.toArray(endPosition), filter, - resultPosition, + resultPositionRaw, visitedArray.raw, maxVisitedSize ); + const resultPosition = vec3.fromRaw(resultPositionRaw); + Raw.destroy(resultPositionRaw); + const visited = [...visitedArray.getHeapView()]; - visitedArray.free(); + visitedArray.destroy(); return { success: Raw.Detour.statusSucceed(status), status, - resultPosition: vec3.fromRaw(resultPosition), + resultPosition, visited, }; } @@ -450,20 +542,26 @@ export class NavMeshQuery { */ filter?: QueryFilter; }) { - const randomPolyRef = new Raw.UnsignedIntRef(); - const randomPoint = new Raw.Vec3(); + const randomPolyRefRaw = new Raw.UnsignedIntRef(); + const randomPointRaw = new Raw.Vec3(); const status = this.raw.findRandomPoint( options?.filter?.raw ?? this.defaultFilter.raw, - randomPolyRef, - randomPoint + randomPolyRefRaw, + randomPointRaw ); + const randomPolyRef = randomPolyRefRaw.value; + Raw.destroy(randomPolyRefRaw); + + const randomPoint = vec3.fromRaw(randomPointRaw); + Raw.destroy(randomPointRaw); + return { success: Raw.Detour.statusSucceed(status), status, - randomPolyRef: randomPolyRef.value, - randomPoint: vec3.fromRaw(randomPoint), + randomPolyRef, + randomPoint, }; } @@ -475,16 +573,20 @@ export class NavMeshQuery { */ getPolyHeight(polyRef: number, position: Vector3) { const floatRef = new Raw.FloatRef(); + const status = this.raw.getPolyHeight( polyRef, vec3.toArray(position), floatRef ); + const height = floatRef.value; + Raw.destroy(floatRef); + return { success: Raw.Detour.statusSucceed(status), status, - height: floatRef.value, + height, }; } @@ -661,10 +763,10 @@ export class NavMeshQuery { }); } - findPathResult.polys.free(); - findStraightPathResult.straightPath.free(); - findStraightPathResult.straightPathFlags.free(); - findStraightPathResult.straightPathRefs.free(); + findPathResult.polys.destroy(); + findStraightPathResult.straightPath.destroy(); + findStraightPathResult.straightPathFlags.destroy(); + findStraightPathResult.straightPathRefs.destroy(); return { success: true, @@ -680,6 +782,12 @@ export class NavMeshQuery { * @param endPosition position within the end polygon. * @param options additional options * @returns + * + * The `polys` array returned must be freed after use. + * + * ```ts + * findPathResult.polys.destroy(); + * ``` */ findPath( startRef: number, @@ -703,8 +811,8 @@ export class NavMeshQuery { const filter = options?.filter ?? this.defaultFilter; const maxPathPolys = options?.maxPathPolys ?? 256; - const polys = new UnsignedIntArray(); - polys.resize(maxPathPolys); + const polysArray = new UnsignedIntArray(); + polysArray.resize(maxPathPolys); const status = this.raw.findPath( startRef, @@ -712,14 +820,14 @@ export class NavMeshQuery { vec3.toArray(startPosition), vec3.toArray(endPosition), filter.raw, - polys.raw, + polysArray.raw, maxPathPolys ); return { success: Raw.Detour.statusSucceed(status), status, - polys, + polys: polysArray, }; } @@ -746,6 +854,14 @@ export class NavMeshQuery { * @param path an array of polygon references that represent the path corridor * @param options additional options * @returns the straight path result + * + * The straightPath, straightPathFlags, and straightPathRefs arrays returned must be freed after use. + * + * ```ts + * findStraightPathResult.straightPath.destroy(); + * findStraightPathResult.straightPathFlags.destroy(); + * findStraightPathResult.straightPathRefs.destroy(); + * ``` */ findStraightPath( start: Vector3, @@ -811,7 +927,7 @@ export class NavMeshQuery { const straightPathRefs = new UnsignedIntArray(); straightPathRefs.resize(maxStraightPathPoints); - const straightPathCount = new Raw.IntRef(); + const straightPathCountRaw = new Raw.IntRef(); const status = this.raw.findStraightPath( vec3.toArray(start), @@ -820,18 +936,21 @@ export class NavMeshQuery { straightPath.raw, straightPathFlags.raw, straightPathRefs.raw, - straightPathCount, + straightPathCountRaw, maxStraightPathPoints, straightPathOptions ); + const straightPathCount = straightPathCountRaw.value; + Raw.destroy(straightPathCountRaw); + return { success: Raw.Detour.statusSucceed(status), status, straightPath, straightPathFlags, straightPathRefs, - straightPathCount: straightPathCount.value, + straightPathCount, }; } @@ -959,7 +1078,7 @@ export class NavMeshQuery { prevRef ); - return { + const result = { success: Raw.Detour.statusSucceed(status), status, t: raycastHit.t, @@ -969,6 +1088,10 @@ export class NavMeshQuery { maxPath: raycastHit.maxPath, pathCost: raycastHit.pathCost, }; + + Raw.destroy(raycastHit); + + return result; } /** diff --git a/packages/recast-navigation-core/src/nav-mesh.ts b/packages/recast-navigation-core/src/nav-mesh.ts index f4d4076d..c6573c8f 100644 --- a/packages/recast-navigation-core/src/nav-mesh.ts +++ b/packages/recast-navigation-core/src/nav-mesh.ts @@ -5,14 +5,13 @@ import { DetourPoly, statusSucceed, } from './detour'; -import { Raw } from './raw'; -import type R from './raw-module'; +import { Raw, type RawModule } from './raw'; import { Vector3, array, vec3 } from './utils'; export class NavMeshGetTilesAtResult { - raw: R.NavMeshGetTilesAtResult; + raw: RawModule.NavMeshGetTilesAtResult; - constructor(raw: R.NavMeshGetTilesAtResult) { + constructor(raw: RawModule.NavMeshGetTilesAtResult) { this.raw = raw; } @@ -26,9 +25,9 @@ export class NavMeshGetTilesAtResult { } export class NavMeshRemoveTileResult { - raw: R.NavMeshRemoveTileResult; + raw: RawModule.NavMeshRemoveTileResult; - constructor(raw: R.NavMeshRemoveTileResult) { + constructor(raw: RawModule.NavMeshRemoveTileResult) { this.raw = raw; } @@ -42,9 +41,9 @@ export class NavMeshRemoveTileResult { } export class NavMeshCalcTileLocResult { - raw: R.NavMeshCalcTileLocResult; + raw: RawModule.NavMeshCalcTileLocResult; - constructor(raw: R.NavMeshCalcTileLocResult) { + constructor(raw: RawModule.NavMeshCalcTileLocResult) { this.raw = raw; } @@ -58,9 +57,9 @@ export class NavMeshCalcTileLocResult { } export class NavMeshStoreTileStateResult { - raw: R.NavMeshStoreTileStateResult; + raw: RawModule.NavMeshStoreTileStateResult; - constructor(raw: R.NavMeshStoreTileStateResult) { + constructor(raw: RawModule.NavMeshStoreTileStateResult) { this.raw = raw; } @@ -99,7 +98,7 @@ export type NavMeshParamsType = { }; export class NavMeshParams { - constructor(public raw: R.dtNavMeshParams) {} + constructor(public raw: RawModule.dtNavMeshParams) {} static create(params: NavMeshParamsType): NavMeshParams { const raw = new Raw.Module.dtNavMeshParams(); @@ -132,9 +131,20 @@ export class NavMeshParams { } export class NavMesh { - raw: R.NavMesh; + raw: RawModule.NavMesh; - constructor(raw?: R.NavMesh) { + /** + * Constructs a new NavMesh + */ + constructor(); + + /** + * Creates a wrapper around a raw NavMesh object + * @param raw raw object + */ + constructor(raw: RawModule.NavMesh); + + constructor(raw?: RawModule.NavMesh) { this.raw = raw ?? new Raw.Module.NavMesh(); } @@ -164,13 +174,21 @@ export class NavMesh { * @returns the status of the operation and the reference of the added tile */ addTile(navMeshData: UnsignedCharArray, flags: number, lastRef: number) { - const tileRef = new Raw.UnsignedIntRef(); + const tileRefRaw = new Raw.UnsignedIntRef(); + + const status = this.raw.addTile( + navMeshData.raw, + flags, + lastRef, + tileRefRaw + ); - const status = this.raw.addTile(navMeshData.raw, flags, lastRef, tileRef); + const tileRef = tileRefRaw.value; + Raw.destroy(tileRefRaw); return { status, - tileRef: tileRef.value, + tileRef, }; } @@ -186,10 +204,19 @@ export class NavMesh { this.raw.decodePolyId(polyRef, saltRef, itRef, ipRef); + const tileSalt = saltRef.value; + Raw.destroy(saltRef); + + const tileIndex = itRef.value; + Raw.destroy(itRef); + + const tilePolygonIndex = ipRef.value; + Raw.destroy(ipRef); + return { - tileSalt: saltRef.value, - tileIndex: itRef.value, - tilePolygonIndex: ipRef.value, + tileSalt, + tileIndex, + tilePolygonIndex, }; } @@ -359,20 +386,26 @@ export class NavMesh { * @returns */ getOffMeshConnectionPolyEndPoints(prevRef: number, polyRef: number) { - const start = new Raw.Vec3(); - const end = new Raw.Vec3(); + const startRaw = new Raw.Vec3(); + const endRaw = new Raw.Vec3(); const status = this.raw.getOffMeshConnectionPolyEndPoints( prevRef, polyRef, - start, - end + startRaw, + endRaw ); + const start = vec3.fromRaw(startRaw); + Raw.destroy(startRaw); + + const end = vec3.fromRaw(endRaw); + Raw.destroy(endRaw); + return { status, - start: vec3.fromRaw(start), - end: vec3.fromRaw(end), + start, + end, }; } @@ -400,13 +433,16 @@ export class NavMesh { * @returns */ getPolyFlags(ref: number) { - const flags = new Raw.UnsignedShortRef(); + const flagsRaw = new Raw.UnsignedShortRef(); - const status = this.raw.getPolyFlags(ref, flags); + const status = this.raw.getPolyFlags(ref, flagsRaw); + + const flags = flagsRaw.value; + Raw.destroy(flagsRaw); return { status, - flags: flags.value, + flags, }; } @@ -425,13 +461,16 @@ export class NavMesh { * @returns */ getPolyArea(ref: number) { - const area = new Raw.UnsignedCharRef(); + const areaRaw = new Raw.UnsignedCharRef(); + + const status = this.raw.getPolyArea(ref, areaRaw); - const status = this.raw.getPolyArea(ref, area); + const area = areaRaw.value; + Raw.destroy(areaRaw); return { status, - area: area.value, + area, }; } diff --git a/packages/recast-navigation-core/src/raw-module.ts b/packages/recast-navigation-core/src/raw-module.ts index 62797e7a..e69de29b 100644 --- a/packages/recast-navigation-core/src/raw-module.ts +++ b/packages/recast-navigation-core/src/raw-module.ts @@ -1,3 +0,0 @@ -import RawModule from '@recast-navigation/wasm'; - -export default RawModule; diff --git a/packages/recast-navigation-core/src/raw.ts b/packages/recast-navigation-core/src/raw.ts index 24857fe5..366742c8 100644 --- a/packages/recast-navigation-core/src/raw.ts +++ b/packages/recast-navigation-core/src/raw.ts @@ -1,6 +1,11 @@ -import RawModule from './raw-module'; +import type { + default as Module, + default as RawModule, +} from '@recast-navigation/wasm'; import type { Pretty } from './types'; +export type { RawModule }; + type ModuleKey = (keyof typeof RawModule)[][number]; const instances = [ @@ -27,7 +32,6 @@ const classes = [ 'dtTileCacheParams', 'dtTileCacheLayerHeader', 'Vec3', - 'Vec2', 'BoolRef', 'IntRef', 'UnsignedIntRef', @@ -45,6 +49,7 @@ type RawApi = Pretty< { Module: typeof RawModule; isNull: (obj: unknown) => boolean; + destroy: (obj: unknown) => void; } & { [K in (typeof instances)[number]]: InstanceType<(typeof RawModule)[K]>; } & { @@ -61,14 +66,22 @@ export const Raw = { isNull: (obj: unknown) => { return Raw.Module.getPointer(obj) === 0; }, + destroy: (obj: unknown) => { + Raw.Module.destroy(obj); + }, } satisfies Partial as RawApi; -export const init = async () => { +export const init = async (impl?: typeof Module) => { if (Raw.Module !== undefined) { return; } - Raw.Module = await RawModule(); + if (impl) { + Raw.Module = await impl(); + } else { + const defaultExport = (await import('@recast-navigation/wasm')).default; + Raw.Module = await defaultExport(); + } for (const instance of instances) { (Raw as any)[instance] = new Raw.Module[instance](); diff --git a/packages/recast-navigation-core/src/recast.ts b/packages/recast-navigation-core/src/recast.ts index 94c9244f..c9e497cd 100644 --- a/packages/recast-navigation-core/src/recast.ts +++ b/packages/recast-navigation-core/src/recast.ts @@ -1,7 +1,6 @@ import { Vector3Tuple } from 'three'; import { FloatArray, IntArray, UnsignedCharArray } from './arrays'; -import { Raw } from './raw'; -import type R from './raw-module'; +import { Raw, type RawModule } from './raw'; import { Vector2Tuple, Vector3, array, vec3 } from './utils'; export type RecastConfig = { @@ -131,7 +130,7 @@ export const recastConfigDefaults: RecastConfig = { export const createRcConfig = ( partialConfig: Partial -): R.rcConfig => { +): RawModule.rcConfig => { const config = { ...recastConfigDefaults, ...partialConfig, @@ -157,7 +156,9 @@ export const createRcConfig = ( return rcConfig; }; -export const cloneRcConfig = (rcConfig: R.rcConfig): R.rcConfig => { +export const cloneRcConfig = ( + rcConfig: RawModule.rcConfig +): RawModule.rcConfig => { const clone = new Raw.Module.rcConfig(); clone.set_bmin(0, rcConfig.get_bmin(0)); @@ -189,7 +190,7 @@ export const cloneRcConfig = (rcConfig: R.rcConfig): R.rcConfig => { }; export class RecastBuildContext { - raw: R.RecastBuildContext; + raw: RawModule.RecastBuildContext; logs: Array<{ category: number; msg: string }> = []; startTimes: { [label: string]: number } = {}; @@ -284,9 +285,19 @@ export class RecastBuildContext { } export class RecastChunkyTriMesh { - raw: R.rcChunkyTriMesh; + raw: RawModule.rcChunkyTriMesh; - constructor(raw?: R.rcChunkyTriMesh) { + /** + * Creates a new RecastChunkyTriMesh. + */ + constructor(); + + /** + * Creates a wrapper around an existing raw RecastChunkyTriMesh object. + */ + constructor(raw: RawModule.rcChunkyTriMesh); + + constructor(raw?: RawModule.rcChunkyTriMesh) { this.raw = raw ?? new Raw.rcChunkyTriMesh(); } @@ -321,7 +332,7 @@ export class RecastChunkyTriMesh { ); } - nodes(index: number): R.rcChunkyTriMeshNode { + nodes(index: number): RawModule.rcChunkyTriMeshNode { return this.raw.get_nodes(index); } @@ -331,9 +342,9 @@ export class RecastChunkyTriMesh { } export class RecastSpan { - raw: R.rcSpan; + raw: RawModule.rcSpan; - constructor(raw: R.rcSpan) { + constructor(raw: RawModule.rcSpan) { this.raw = raw; } @@ -355,9 +366,9 @@ export class RecastSpan { } export class RecastSpanPool { - raw: R.rcSpanPool; + raw: RawModule.rcSpanPool; - constructor(raw: R.rcSpanPool) { + constructor(raw: RawModule.rcSpanPool) { this.raw = raw; } @@ -373,9 +384,9 @@ export class RecastSpanPool { } export class RecastHeightfield { - raw: R.rcHeightfield; + raw: RawModule.rcHeightfield; - constructor(raw: R.rcHeightfield) { + constructor(raw: RawModule.rcHeightfield) { this.raw = raw; } @@ -417,9 +428,9 @@ export class RecastHeightfield { } export class RecastCompactCell { - raw: R.rcCompactCell; + raw: RawModule.rcCompactCell; - constructor(raw: R.rcCompactCell) { + constructor(raw: RawModule.rcCompactCell) { this.raw = raw; } @@ -433,9 +444,9 @@ export class RecastCompactCell { } export class RecastCompactSpan { - raw: R.rcCompactSpan; + raw: RawModule.rcCompactSpan; - constructor(raw: R.rcCompactSpan) { + constructor(raw: RawModule.rcCompactSpan) { this.raw = raw; } @@ -457,9 +468,9 @@ export class RecastCompactSpan { } export class RecastCompactHeightfield { - raw: R.rcCompactHeightfield; + raw: RawModule.rcCompactHeightfield; - constructor(raw: R.rcCompactHeightfield) { + constructor(raw: RawModule.rcCompactHeightfield) { this.raw = raw; } @@ -529,9 +540,9 @@ export class RecastCompactHeightfield { } export class RecastContour { - raw: R.rcContour; + raw: RawModule.rcContour; - constructor(raw: R.rcContour) { + constructor(raw: RawModule.rcContour) { this.raw = raw; } @@ -561,9 +572,9 @@ export class RecastContour { } export class RecastContourSet { - raw: R.rcContourSet; + raw: RawModule.rcContourSet; - constructor(raw: R.rcContourSet) { + constructor(raw: RawModule.rcContourSet) { this.raw = raw; } @@ -609,9 +620,9 @@ export class RecastContourSet { } export class RecastHeightfieldLayer { - raw: R.rcHeightfieldLayer; + raw: RawModule.rcHeightfieldLayer; - constructor(raw: R.rcHeightfieldLayer) { + constructor(raw: RawModule.rcHeightfieldLayer) { this.raw = raw; } @@ -677,9 +688,9 @@ export class RecastHeightfieldLayer { } export class RecastHeightfieldLayerSet { - raw: R.rcHeightfieldLayerSet; + raw: RawModule.rcHeightfieldLayerSet; - constructor(raw: R.rcHeightfieldLayerSet) { + constructor(raw: RawModule.rcHeightfieldLayerSet) { this.raw = raw; } @@ -693,9 +704,9 @@ export class RecastHeightfieldLayerSet { } export class RecastPolyMesh { - raw: R.rcPolyMesh; + raw: RawModule.rcPolyMesh; - constructor(raw: R.rcPolyMesh) { + constructor(raw: RawModule.rcPolyMesh) { this.raw = raw; } @@ -769,9 +780,9 @@ export class RecastPolyMesh { } export class RecastPolyMeshDetail { - raw: R.rcPolyMeshDetail; + raw: RawModule.rcPolyMeshDetail; - constructor(raw: R.rcPolyMeshDetail) { + constructor(raw: RawModule.rcPolyMeshDetail) { this.raw = raw; } diff --git a/packages/recast-navigation-core/src/serdes/import-nav-mesh.ts b/packages/recast-navigation-core/src/serdes/import-nav-mesh.ts index a9f87d65..803a28cc 100644 --- a/packages/recast-navigation-core/src/serdes/import-nav-mesh.ts +++ b/packages/recast-navigation-core/src/serdes/import-nav-mesh.ts @@ -1,6 +1,5 @@ import { NavMesh } from '../nav-mesh'; -import { Raw } from '../raw'; -import type R from '../raw-module'; +import { Raw, type RawModule } from '../raw'; import { TileCache, TileCacheMeshProcess } from '../tile-cache'; export type NavMeshImporterResult = @@ -10,8 +9,8 @@ export type NavMeshImporterResult = | { navMesh: NavMesh; tileCache: TileCache; - allocator: R.RecastLinearAllocator; - compressor: R.RecastFastLZCompressor; + allocator: RawModule.RecastLinearAllocator; + compressor: RawModule.RecastFastLZCompressor; }; export const importNavMesh = ( diff --git a/packages/recast-navigation-core/src/tile-cache.ts b/packages/recast-navigation-core/src/tile-cache.ts index af72dcc1..b1dfd376 100644 --- a/packages/recast-navigation-core/src/tile-cache.ts +++ b/packages/recast-navigation-core/src/tile-cache.ts @@ -1,11 +1,10 @@ import { UnsignedCharArray, UnsignedShortArray } from './arrays'; import { NavMeshCreateParams } from './detour'; import { NavMesh } from './nav-mesh'; -import { Raw } from './raw'; -import type R from './raw-module'; +import { Raw, type RawModule } from './raw'; import { Vector3, vec3 } from './utils'; -export type ObstacleRef = R.dtObstacleRef; +export type ObstacleRef = RawModule.dtObstacleRef; export type BoxObstacle = { type: 'box'; @@ -57,7 +56,7 @@ export type TileCacheParamsType = { }; export class DetourTileCacheParams { - constructor(public raw: R.dtTileCacheParams) {} + constructor(public raw: RawModule.dtTileCacheParams) {} static create(config: TileCacheParamsType): DetourTileCacheParams { const tileCacheParams = new Raw.Module.dtTileCacheParams(); @@ -88,11 +87,22 @@ export type TileCacheUpdateResult = { }; export class TileCache { - raw: R.TileCache; + raw: RawModule.TileCache; obstacles: Map = new Map(); - constructor(raw?: R.TileCache) { + /** + * Constructs a new TileCache + */ + constructor(); + + /** + * Creates a wrapper around a raw TileCache object + * @param raw raw object + */ + constructor(raw: RawModule.TileCache); + + constructor(raw?: RawModule.TileCache) { this.raw = raw ?? new Raw.Module.TileCache(); } @@ -102,8 +112,8 @@ export class TileCache { */ init( params: DetourTileCacheParams, - alloc: R.RecastLinearAllocator, - compressor: R.RecastFastLZCompressor, + alloc: RawModule.RecastLinearAllocator, + compressor: RawModule.RecastFastLZCompressor, meshProcess: TileCacheMeshProcess ) { return this.raw.init(params.raw, alloc, compressor, meshProcess.raw); @@ -244,11 +254,11 @@ export class TileCache { addTile( data: UnsignedCharArray, flags: number = Raw.Module.DT_COMPRESSEDTILE_FREE_DATA - ): R.TileCacheAddTileResult { + ): RawModule.TileCacheAddTileResult { return this.raw.addTile(data.raw, flags); } - buildNavMeshTile(ref: R.dtCompressedTileRef, navMesh: NavMesh) { + buildNavMeshTile(ref: RawModule.dtCompressedTileRef, navMesh: NavMesh) { return this.raw.buildNavMeshTile(ref, navMesh.raw); } @@ -262,7 +272,7 @@ export class TileCache { } export class TileCacheMeshProcess { - raw: R.TileCacheMeshProcess; + raw: RawModule.TileCacheMeshProcess; constructor( process: ( @@ -302,8 +312,8 @@ export class TileCacheMeshProcess { } export const buildTileCacheLayer = ( - comp: R.RecastFastLZCompressor, - header: R.dtTileCacheLayerHeader, + comp: RawModule.RecastFastLZCompressor, + header: RawModule.dtTileCacheLayerHeader, heights: UnsignedCharArray, areas: UnsignedCharArray, cons: UnsignedCharArray, diff --git a/packages/recast-navigation-core/src/utils.ts b/packages/recast-navigation-core/src/utils.ts index f6b602f7..80a6c2ab 100644 --- a/packages/recast-navigation-core/src/utils.ts +++ b/packages/recast-navigation-core/src/utils.ts @@ -1,5 +1,4 @@ -import { Raw } from './raw'; -import type R from './raw-module'; +import { Raw, type RawModule } from './raw'; export type Vector3 = { x: number; y: number; z: number }; @@ -10,7 +9,7 @@ export type Vector2 = { x: number; y: number }; export type Vector2Tuple = [x: number, y: number]; export const vec3 = { - toRaw: ({ x, y, z }: Vector3, existing?: R.Vec3) => { + toRaw: ({ x, y, z }: Vector3, existing?: RawModule.Vec3) => { if (existing) { existing.x = x; existing.y = y; @@ -20,13 +19,9 @@ export const vec3 = { return new Raw.Module.Vec3(x, y, z); }, - fromRaw: (vec3: R.Vec3, freeRaw?: boolean) => { + fromRaw: (vec3: RawModule.Vec3) => { const { x, y, z } = vec3; - if (freeRaw) { - Raw.Module.destroy(vec3); - } - return { x, y, z }; }, fromArray: ([x, y, z]: number[]) => { @@ -53,4 +48,4 @@ export const array = (getter: (index: number) => T, count: number) => { */ export const range = (n: number) => { return [...Array(n)].map((_, i) => i); -} +}; diff --git a/packages/recast-navigation-generators/package.json b/packages/recast-navigation-generators/package.json index 2bbc7910..3564908a 100644 --- a/packages/recast-navigation-generators/package.json +++ b/packages/recast-navigation-generators/package.json @@ -12,7 +12,8 @@ "bugs": { "url": "https://github.com/isaac-mason/recast-navigation-js/issues" }, - "main": "./dist/index.cjs", + "type": "module", + "main": "./dist/index.mjs", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "files": [ @@ -30,7 +31,11 @@ "@recast-navigation/wasm": "0.27.0" }, "devDependencies": { + "@babel/core": "^7.24.5", + "@babel/preset-env": "^7.24.5", + "@babel/preset-typescript": "^7.24.1", "@isaac-mason/eslint-config-typescript": "^0.0.5", + "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-terser": "^0.4.3", diff --git a/packages/recast-navigation-generators/rollup.config.js b/packages/recast-navigation-generators/rollup.config.js index dfa96ca7..7194245e 100644 --- a/packages/recast-navigation-generators/rollup.config.js +++ b/packages/recast-navigation-generators/rollup.config.js @@ -1,10 +1,29 @@ +import babel from '@rollup/plugin-babel'; import commonjs from '@rollup/plugin-commonjs'; -import { nodeResolve } from '@rollup/plugin-node-resolve'; +import resolve from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; import typescript from '@rollup/plugin-typescript'; import path from 'path'; import filesize from 'rollup-plugin-filesize'; +const babelOptions = { + babelrc: false, + extensions: ['.ts'], + exclude: '**/node_modules/**', + babelHelpers: 'bundled', + presets: [ + [ + '@babel/preset-env', + { + loose: true, + modules: false, + targets: '>1%, not dead, not ie 11, not op_mini all', + }, + ], + '@babel/preset-typescript', + ], +}; + export default [ { input: `./src/index.ts`, @@ -16,22 +35,16 @@ export default [ sourcemap: true, exports: 'named', }, - { - file: `dist/index.cjs`, - format: 'cjs', - sourcemap: true, - exports: 'named', - } ], plugins: [ terser(), - nodeResolve(), + resolve(), commonjs(), typescript({ tsconfig: path.resolve(__dirname, `tsconfig.json`), - sourceMap: true, - inlineSources: true, + emitDeclarationOnly: true, }), + babel(babelOptions), filesize(), ], }, diff --git a/packages/recast-navigation-generators/src/generators/generate-solo-nav-mesh.ts b/packages/recast-navigation-generators/src/generators/generate-solo-nav-mesh.ts index 0bfd91a6..c68f758c 100644 --- a/packages/recast-navigation-generators/src/generators/generate-solo-nav-mesh.ts +++ b/packages/recast-navigation-generators/src/generators/generate-solo-nav-mesh.ts @@ -226,9 +226,9 @@ export const generateSoloNavMesh = ( return fail('Could not rasterize triangles'); } - triangleAreasArray.free(); - verticesArray.free(); - trianglesArray.free(); + triangleAreasArray.destroy(); + verticesArray.destroy(); + trianglesArray.destroy(); // // Step 3. Filter walkables surfaces. diff --git a/packages/recast-navigation-generators/src/generators/generate-tile-cache.ts b/packages/recast-navigation-generators/src/generators/generate-tile-cache.ts index 49fbc7f3..b5f7118a 100644 --- a/packages/recast-navigation-generators/src/generators/generate-tile-cache.ts +++ b/packages/recast-navigation-generators/src/generators/generate-tile-cache.ts @@ -164,8 +164,8 @@ export const generateTileCache = ( }; const cleanup = () => { - verticesArray.free(); - trianglesArray.free(); + verticesArray.destroy(); + trianglesArray.destroy(); if (!keepIntermediates) { for (let i = 0; i < intermediates.tileIntermediates.length; i++) { @@ -407,7 +407,7 @@ export const generateTileCache = ( tileConfig.walkableClimb ); - triangleAreasArray.free(); + triangleAreasArray.destroy(); if (!success) { return { n: 0 }; diff --git a/packages/recast-navigation-generators/src/generators/generate-tiled-nav-mesh.ts b/packages/recast-navigation-generators/src/generators/generate-tiled-nav-mesh.ts index 2e489a47..c8e5270c 100644 --- a/packages/recast-navigation-generators/src/generators/generate-tiled-nav-mesh.ts +++ b/packages/recast-navigation-generators/src/generators/generate-tiled-nav-mesh.ts @@ -136,8 +136,8 @@ export const generateTiledNavMesh = ( trianglesArray.copy(triangles); const cleanup = () => { - verticesArray.free(); - trianglesArray.free(); + verticesArray.destroy(); + trianglesArray.destroy(); if (keepIntermediates) return; @@ -413,7 +413,7 @@ export const generateTiledNavMesh = ( tileConfig.walkableClimb ); - triangleAreasArray.free(); + triangleAreasArray.destroy(); if (!success) { return failTileMesh('Could not rasterize triangles'); @@ -640,7 +640,7 @@ export const generateTiledNavMesh = ( })` ); - result.data.free(); + result.data.destroy(); } } } diff --git a/packages/recast-navigation-three/README.md b/packages/recast-navigation-three/README.md index ccfb45f5..18ca4ea0 100644 --- a/packages/recast-navigation-three/README.md +++ b/packages/recast-navigation-three/README.md @@ -1,6 +1,6 @@ # @recast-navigation/three -Three.js helpers for [`recast-navigation`](https://github.com/isaac-mason/recast-navigation-js/tree/main/packages/recast-navigation). +Three.js nav mesh generation and visualisation helpers for [`recast-navigation`](https://github.com/isaac-mason/recast-navigation-js/tree/main/packages/recast-navigation). ## Installation @@ -88,7 +88,30 @@ https://github.com/isaac-mason/recast-navigation-js This library provides helpers that are used in conjunction with the core library. +### Debug Drawer +This package provides a `DebugDrawer` utility that can visualise a navmesh and other generation intermediates. + +```ts +import { DebugDrawer } from 'recast-navigation/three'; + +const debugDrawer = new DebugDrawer({ scene }); + +// resize the debug drawer when the window resizes +// required for rendering lines correctly +debugDrawer.resize(window.innerWidth, window.innerHeight); + +// draw a navmesh +debugDrawer.drawNavMesh(navMesh); + +// clear the debug drawer +debugDrawer.reset(); + +// dispose of threejs and wasm resources +debugDrawer.dispose(); +``` + +See the Debug Drawer storybooks for more examples: https://recast-navigation-js.isaacmason.com/?path=/story/debug-three-debug-drawer--nav-mesh ### Helpers diff --git a/packages/recast-navigation-three/package.json b/packages/recast-navigation-three/package.json index b97be19f..4ae81d7a 100644 --- a/packages/recast-navigation-three/package.json +++ b/packages/recast-navigation-three/package.json @@ -12,7 +12,8 @@ "bugs": { "url": "https://github.com/isaac-mason/recast-navigation-js/issues" }, - "main": "./dist/index.cjs", + "type": "module", + "main": "./dist/index.mjs", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "files": [ @@ -34,7 +35,11 @@ "three": "0.x.x" }, "devDependencies": { + "@babel/core": "^7.24.5", + "@babel/preset-env": "^7.24.5", + "@babel/preset-typescript": "^7.24.1", "@isaac-mason/eslint-config-typescript": "^0.0.5", + "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-terser": "^0.4.3", diff --git a/packages/recast-navigation-three/rollup.config.js b/packages/recast-navigation-three/rollup.config.js index bd9d6fa6..53408615 100644 --- a/packages/recast-navigation-three/rollup.config.js +++ b/packages/recast-navigation-three/rollup.config.js @@ -1,14 +1,37 @@ +import babel from '@rollup/plugin-babel'; import commonjs from '@rollup/plugin-commonjs'; -import { nodeResolve } from '@rollup/plugin-node-resolve'; +import resolve from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; import typescript from '@rollup/plugin-typescript'; import path from 'path'; import filesize from 'rollup-plugin-filesize'; +const babelOptions = { + babelrc: false, + extensions: ['.ts'], + exclude: '**/node_modules/**', + babelHelpers: 'bundled', + presets: [ + [ + '@babel/preset-env', + { + loose: true, + modules: false, + targets: '>1%, not dead, not ie 11, not op_mini all', + }, + ], + '@babel/preset-typescript', + ], +}; + export default [ { input: `./src/index.ts`, - external: ['@recast-navigation/core', '@recast-navigation/generators', 'three'], + external: [ + '@recast-navigation/core', + '@recast-navigation/generators', + 'three', + ], output: [ { file: `dist/index.mjs`, @@ -16,22 +39,16 @@ export default [ sourcemap: true, exports: 'named', }, - { - file: `dist/index.cjs`, - format: 'cjs', - sourcemap: true, - exports: 'named', - } ], plugins: [ terser(), - nodeResolve(), + resolve(), commonjs(), typescript({ tsconfig: path.resolve(__dirname, `tsconfig.json`), - sourceMap: true, - inlineSources: true, + emitDeclarationOnly: true, }), + babel(babelOptions), filesize(), ], }, diff --git a/packages/recast-navigation-three/src/debug/debug-drawer.ts b/packages/recast-navigation-three/src/debug/debug-drawer.ts index ecff46ea..2412453a 100644 --- a/packages/recast-navigation-three/src/debug/debug-drawer.ts +++ b/packages/recast-navigation-three/src/debug/debug-drawer.ts @@ -75,6 +75,7 @@ export class DebugDrawer extends THREE.Group { transparent: true, opacity: 0.4, depthWrite: false, + resolution: new THREE.Vector2(800, 800), }); this.debugDrawImpl = new Raw.Module.DebugDrawImpl(); diff --git a/packages/recast-navigation-wasm/CMakeLists.txt b/packages/recast-navigation-wasm/CMakeLists.txt index ff1af784..bdd5b994 100644 --- a/packages/recast-navigation-wasm/CMakeLists.txt +++ b/packages/recast-navigation-wasm/CMakeLists.txt @@ -70,7 +70,8 @@ if ("${CMAKE_BUILD_TYPE}" STREQUAL "debug") set(EMCC_ARGS ${EMCC_ARGS} -g - -s ASSERTIONS) + -s SAFE_HEAP=1 + -s ASSERTIONS=1) else() set(EMCC_ARGS ${EMCC_ARGS} diff --git a/packages/recast-navigation-wasm/package.json b/packages/recast-navigation-wasm/package.json index 8ebf0a09..6d26f127 100644 --- a/packages/recast-navigation-wasm/package.json +++ b/packages/recast-navigation-wasm/package.json @@ -18,15 +18,18 @@ "exports": { ".": { "types": "./dist/recast-navigation.d.ts", - "import": "./dist/recast-navigation.wasm-compat.js" + "import": "./dist/recast-navigation.wasm-compat.js", + "default": "./dist/recast-navigation.wasm-compat.js" }, "./wasm": { "types": "./dist/recast-navigation.d.ts", - "import": "./dist/recast-navigation.wasm.js" + "import": "./dist/recast-navigation.wasm.js", + "default": "./dist/recast-navigation.wasm.js" }, "./wasm-compat": { "types": "./dist/recast-navigation.d.ts", - "import": "./dist/recast-navigation.wasm-compat.js" + "import": "./dist/recast-navigation.wasm-compat.js", + "default": "./dist/recast-navigation.wasm-compat.js" } }, "files": [ diff --git a/packages/recast-navigation-wasm/recast-navigation.idl b/packages/recast-navigation-wasm/recast-navigation.idl index 52d6adba..c3781794 100644 --- a/packages/recast-navigation-wasm/recast-navigation.idl +++ b/packages/recast-navigation-wasm/recast-navigation.idl @@ -346,14 +346,6 @@ interface Vec3 { void Vec3(float x, float y, float z); }; -interface Vec2 { - attribute float x; - attribute float y; - - void Vec2(); - void Vec2(float x, float y); -}; - interface NavMeshRemoveTileResult { attribute unsigned long status; attribute octet[] data; @@ -469,7 +461,7 @@ interface NavMeshQuery { unsigned long findNearestPoly([Const] float[] center, [Const] float[] halfExtents, [Const] dtQueryFilter filter, UnsignedIntRef nearestRef, Vec3 nearestPt, BoolRef isOverPoly); - unsigned long findPolysAroundCircle(unsigned long startRef, [Const] float[] centerPos, [Const] float radius, [Const] dtQueryFilter filter, UnsignedIntArray resultRef, UnsignedIntArray resultParent, FloatRef resultCost, IntRef resultCount, [Const] long maxResult); + unsigned long findPolysAroundCircle(unsigned long startRef, [Const] float[] centerPos, [Const] float radius, [Const] dtQueryFilter filter, UnsignedIntArray resultRef, UnsignedIntArray resultParent, FloatArray resultCost, IntRef resultCount, [Const] long maxResult); unsigned long queryPolygons([Const] float[] center, [Const] float[] halfExtents, [Const] dtQueryFilter filter, UnsignedIntArray polys, IntRef polyCount, [Const] long maxPolys); diff --git a/packages/recast-navigation-wasm/src/Arrays.h b/packages/recast-navigation-wasm/src/Arrays.h index ebd96934..9395cd5e 100644 --- a/packages/recast-navigation-wasm/src/Arrays.h +++ b/packages/recast-navigation-wasm/src/Arrays.h @@ -4,12 +4,25 @@ #include template -struct ArrayWrapperTemplate +class ArrayWrapperTemplate { +public: T *data; int size; bool isView; + ArrayWrapperTemplate() + { + data = nullptr; + size = 0; + isView = false; + } + + ~ArrayWrapperTemplate() + { + free(); + } + void free() { if (!isView) @@ -62,22 +75,22 @@ struct ArrayWrapperTemplate } }; -struct IntArray : public ArrayWrapperTemplate +class IntArray : public ArrayWrapperTemplate { }; -struct UnsignedIntArray : public ArrayWrapperTemplate +class UnsignedIntArray : public ArrayWrapperTemplate { }; -struct UnsignedCharArray : public ArrayWrapperTemplate +class UnsignedCharArray : public ArrayWrapperTemplate { }; -struct UnsignedShortArray : public ArrayWrapperTemplate +class UnsignedShortArray : public ArrayWrapperTemplate { }; -struct FloatArray : public ArrayWrapperTemplate +class FloatArray : public ArrayWrapperTemplate { }; diff --git a/packages/recast-navigation-wasm/src/NavMeshQuery.cpp b/packages/recast-navigation-wasm/src/NavMeshQuery.cpp index cdc86280..d50b92fb 100644 --- a/packages/recast-navigation-wasm/src/NavMeshQuery.cpp +++ b/packages/recast-navigation-wasm/src/NavMeshQuery.cpp @@ -63,9 +63,9 @@ dtStatus NavMeshQuery::findNearestPoly(const float *center, const float *halfExt return m_navQuery->findNearestPoly(center, halfExtents, filter, &nearestRef->value, &nearestPt->x, &isOverPoly->value); } -dtStatus NavMeshQuery::findPolysAroundCircle(dtPolyRef startRef, const float *centerPos, const float radius, const dtQueryFilter *filter, UnsignedIntArray *resultRef, UnsignedIntArray *resultParent, FloatRef *resultCost, IntRef *resultCount, const int maxResult) +dtStatus NavMeshQuery::findPolysAroundCircle(dtPolyRef startRef, const float *centerPos, const float radius, const dtQueryFilter *filter, UnsignedIntArray *resultRef, UnsignedIntArray *resultParent, FloatArray *resultCost, IntRef *resultCount, const int maxResult) { - return m_navQuery->findPolysAroundCircle(startRef, centerPos, radius, filter, resultRef->data, resultParent->data, &resultCost->value, &resultCount->value, maxResult); + return m_navQuery->findPolysAroundCircle(startRef, centerPos, radius, filter, resultRef->data, resultParent->data, resultCost->data, &resultCount->value, maxResult); } dtStatus NavMeshQuery::queryPolygons(const float *center, const float *halfExtents, const dtQueryFilter *filter, UnsignedIntArray *polys, IntRef *polyCount, const int maxPolys) diff --git a/packages/recast-navigation-wasm/src/NavMeshQuery.h b/packages/recast-navigation-wasm/src/NavMeshQuery.h index 088c5dcd..22ed1a68 100644 --- a/packages/recast-navigation-wasm/src/NavMeshQuery.h +++ b/packages/recast-navigation-wasm/src/NavMeshQuery.h @@ -39,7 +39,7 @@ class NavMeshQuery dtStatus findNearestPoly(const float *center, const float *halfExtents, const dtQueryFilter *filter, UnsignedIntRef *nearestRef, Vec3 *nearestPt, BoolRef *isOverPoly); - dtStatus findPolysAroundCircle(dtPolyRef startRef, const float *centerPos, const float radius, const dtQueryFilter *filter, UnsignedIntArray *resultRef, UnsignedIntArray *resultParent, FloatRef *resultCost, IntRef *resultCount, const int maxResult); + dtStatus findPolysAroundCircle(dtPolyRef startRef, const float *centerPos, const float radius, const dtQueryFilter *filter, UnsignedIntArray *resultRef, UnsignedIntArray *resultParent, FloatArray *resultCost, IntRef *resultCount, const int maxResult); dtStatus queryPolygons(const float *center, const float *halfExtents, const dtQueryFilter *filter, UnsignedIntArray *polys, IntRef *polyCount, const int maxPolys); diff --git a/packages/recast-navigation-wasm/src/Refs.h b/packages/recast-navigation-wasm/src/Refs.h index a06e672b..657a2db6 100644 --- a/packages/recast-navigation-wasm/src/Refs.h +++ b/packages/recast-navigation-wasm/src/Refs.h @@ -1,31 +1,94 @@ #pragma once -template -struct PrimitiveRefTemplate +class BoolRef { - T value; -}; +public: + bool value; -struct BoolRef : public PrimitiveRefTemplate -{ + BoolRef() {} + ~BoolRef() {} }; -struct IntRef : public PrimitiveRefTemplate +class IntRef { +public: + int value; + + IntRef() {} + ~IntRef() {} }; -struct UnsignedIntRef : public PrimitiveRefTemplate +class UnsignedIntRef { +public: + unsigned int value; + + UnsignedIntRef() {} + ~UnsignedIntRef() {} }; -struct UnsignedCharRef : public PrimitiveRefTemplate +class UnsignedCharRef { +public: + unsigned char value; + + UnsignedCharRef() {} + ~UnsignedCharRef() {} }; -struct UnsignedShortRef : public PrimitiveRefTemplate +class UnsignedShortRef { +public: + unsigned short value; + + UnsignedShortRef() {} + ~UnsignedShortRef() {} }; -struct FloatRef : public PrimitiveRefTemplate +struct FloatRef { + float value; + + FloatRef() {} + ~FloatRef() {} }; + + +// template +// class PrimitiveRefTemplate +// { +// public: +// T value; +// PrimitiveRefTemplate() {} +// virtual ~PrimitiveRefTemplate() {} +// }; + +// class BoolRef : public PrimitiveRefTemplate +// { +// BoolRef() : PrimitiveRefTemplate() {} +// }; + +// class IntRef : public PrimitiveRefTemplate +// { +// IntRef() : PrimitiveRefTemplate() {} +// }; + +// class UnsignedIntRef : public PrimitiveRefTemplate +// { +// UnsignedIntRef() : PrimitiveRefTemplate() {} +// }; + +// class UnsignedCharRef : public PrimitiveRefTemplate +// { +// UnsignedCharRef() : PrimitiveRefTemplate() {} +// }; + +// class UnsignedShortRef : public PrimitiveRefTemplate +// { +// UnsignedShortRef() : PrimitiveRefTemplate() {} +// }; + +// class FloatRef : public PrimitiveRefTemplate +// { +// FloatRef() : PrimitiveRefTemplate() {} +// }; diff --git a/packages/recast-navigation-wasm/src/Vec.h b/packages/recast-navigation-wasm/src/Vec.h index 7fa921b2..adadc406 100644 --- a/packages/recast-navigation-wasm/src/Vec.h +++ b/packages/recast-navigation-wasm/src/Vec.h @@ -2,39 +2,12 @@ #include -struct Vec3 +class Vec3 { +public: float x, y, z; Vec3() {} Vec3(float v) : x(v), y(v), z(v) {} Vec3(float x, float y, float z) : x(x), y(y), z(z) {} - - void isMinOf(const Vec3 &v) - { - x = std::min(x, v.x); - y = std::min(y, v.y); - z = std::min(z, v.z); - } - - void isMaxOf(const Vec3 &v) - { - x = std::max(x, v.x); - y = std::max(y, v.y); - z = std::max(z, v.z); - } - - float operator[](int index) - { - return ((float *)&x)[index]; - } -}; - -struct Vec2 -{ - float x, y; - - Vec2() {} - Vec2(float v) : x(v), y(v) {} - Vec2(float x, float y) : x(x), y(y) {} }; diff --git a/packages/recast-navigation/.gitignore b/packages/recast-navigation/.gitignore index 397099c8..37063476 100644 --- a/packages/recast-navigation/.gitignore +++ b/packages/recast-navigation/.gitignore @@ -1,19 +1,13 @@ index.mjs index.mjs.map -index.cjs -index.cjs.map index.d.ts generators.mjs generators.mjs.map -generators.cjs -generators.cjs.map generators.d.ts three.mjs three.mjs.map -three.cjs -three.cjs.map three.d.ts storybook-static diff --git a/packages/recast-navigation/.storybook/stories/advanced/custom-areas-generator.ts b/packages/recast-navigation/.storybook/stories/advanced/custom-areas-generator.ts index c611fa1f..7df3cd67 100644 --- a/packages/recast-navigation/.storybook/stories/advanced/custom-areas-generator.ts +++ b/packages/recast-navigation/.storybook/stories/advanced/custom-areas-generator.ts @@ -217,9 +217,9 @@ export const generateNavMesh = ( return fail('Could not rasterize triangles'); } - triangleAreasArray.free(); - verticesArray.free(); - trianglesArray.free(); + triangleAreasArray.destroy(); + verticesArray.destroy(); + trianglesArray.destroy(); // // Step 3. Filter walkables surfaces. diff --git a/packages/recast-navigation/.storybook/stories/advanced/custom-areas.stories.tsx b/packages/recast-navigation/.storybook/stories/advanced/custom-areas.stories.tsx index 50a86497..b57d7c3c 100644 --- a/packages/recast-navigation/.storybook/stories/advanced/custom-areas.stories.tsx +++ b/packages/recast-navigation/.storybook/stories/advanced/custom-areas.stories.tsx @@ -72,7 +72,7 @@ const useNavMesh = (group: RefObject) => { if (!success) return; - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); setNavMesh(navMesh); setNavMeshQuery(navMeshQuery); @@ -322,10 +322,9 @@ export const SingleAgent = () => { useEffect(() => { if (!navMesh || !navMeshQuery) return; - const crowd = new Crowd({ + const crowd = new Crowd(navMesh, { maxAgents: 1, maxAgentRadius: 0.6, - navMesh, }); crowd.navMeshQuery.defaultQueryHalfExtents.x = 5; diff --git a/packages/recast-navigation/.storybook/stories/advanced/flood-fill-pruning.stories.tsx b/packages/recast-navigation/.storybook/stories/advanced/flood-fill-pruning.stories.tsx index a816a4d6..4ae6525f 100644 --- a/packages/recast-navigation/.storybook/stories/advanced/flood-fill-pruning.stories.tsx +++ b/packages/recast-navigation/.storybook/stories/advanced/flood-fill-pruning.stories.tsx @@ -46,7 +46,7 @@ export const FloodFillPruning = () => { if (!success) return; - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); const nearestPolyResult = navMeshQuery.findNearestPoly(point, { halfExtents: { x: 2, y: 2, z: 2 }, @@ -145,10 +145,12 @@ export const FloodFillPruning = () => { style={{ position: 'absolute', top: 0, - color: 'white', - padding: 24, + padding: '25px', userSelect: 'none', + fontSize: '1.5em', fontFamily: 'monospace', + fontWeight: 400, + color: 'white', }} > click to set flood fill start point diff --git a/packages/recast-navigation/.storybook/stories/crowd.stories.tsx b/packages/recast-navigation/.storybook/stories/crowd.stories.tsx index 9fd25c75..3a851daf 100644 --- a/packages/recast-navigation/.storybook/stories/crowd.stories.tsx +++ b/packages/recast-navigation/.storybook/stories/crowd.stories.tsx @@ -57,9 +57,9 @@ export const SingleAgent = () => { if (!success) return; - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); - const crowd = new Crowd({ navMesh, maxAgents: 1, maxAgentRadius: 0.2 }); + const crowd = new Crowd(navMesh, { maxAgents: 1, maxAgentRadius: 0.2 }); const { point: agentPosition } = navMeshQuery.findClosestPoint({ x: -2.9, @@ -162,10 +162,9 @@ export const MultipleAgents = () => { if (!success) return; - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); - const crowd = new Crowd({ - navMesh, + const crowd = new Crowd(navMesh, { maxAgents: 10, maxAgentRadius, }); diff --git a/packages/recast-navigation/.storybook/stories/nav-mesh-query/compute-path.stories.tsx b/packages/recast-navigation/.storybook/stories/nav-mesh-query/compute-path.stories.tsx index fa6f0b67..0f4e810b 100644 --- a/packages/recast-navigation/.storybook/stories/nav-mesh-query/compute-path.stories.tsx +++ b/packages/recast-navigation/.storybook/stories/nav-mesh-query/compute-path.stories.tsx @@ -9,7 +9,7 @@ import { decorators } from '../../decorators'; import { parameters } from '../../parameters'; export default { - title: 'NavMeshQuery / ComputePath', + title: 'NavMeshQuery / Compute Path', decorators, parameters, }; @@ -43,7 +43,7 @@ export const ComputePath = () => { if (!success) return; - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); const { point: start } = navMeshQuery.findClosestPoint({ x: -4.128927083678903, diff --git a/packages/recast-navigation/.storybook/stories/nav-mesh-query/find-random-point.stories.tsx b/packages/recast-navigation/.storybook/stories/nav-mesh-query/find-random-point.stories.tsx index b37e2764..9526fd9b 100644 --- a/packages/recast-navigation/.storybook/stories/nav-mesh-query/find-random-point.stories.tsx +++ b/packages/recast-navigation/.storybook/stories/nav-mesh-query/find-random-point.stories.tsx @@ -54,17 +54,17 @@ export const FindRandomPoint = () => { if (!success) return; - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); setNavMesh(navMesh); setNavMeshQuery(navMeshQuery); return () => { - navMesh.destroy(); - navMeshQuery.destroy(); - setNavMesh(undefined); setNavMeshQuery(undefined); + + navMeshQuery.destroy(); + navMesh.destroy(); }; }, [group]); @@ -93,12 +93,20 @@ export const FindRandomPoint = () => {
-
diff --git a/packages/recast-navigation/.storybook/stories/nav-mesh-query/move-along-surface.stories.tsx b/packages/recast-navigation/.storybook/stories/nav-mesh-query/move-along-surface.stories.tsx new file mode 100644 index 00000000..df5f3530 --- /dev/null +++ b/packages/recast-navigation/.storybook/stories/nav-mesh-query/move-along-surface.stories.tsx @@ -0,0 +1,193 @@ +import { useFrame } from '@react-three/fiber'; +import { NavMesh, NavMeshQuery } from '@recast-navigation/core'; +import { threeToSoloNavMesh } from '@recast-navigation/three'; +import React, { useEffect, useRef, useState } from 'react'; +import { Group, Mesh, Vector3 } from 'three'; +import { Debug } from '../../common/debug'; +import { NavTestEnvironment } from '../../common/nav-test-environment'; +import { decorators, htmlTunnel } from '../../decorators'; +import { parameters } from '../../parameters'; + +export default { + title: 'NavMeshQuery / Move Along Surface', + decorators, + parameters, +}; + +const _targetCameraPosition = new Vector3(); + +export const MoveAlongSurface = () => { + const [level, setLevel] = useState(null); + + const [navMesh, setNavMesh] = useState(); + const [navMeshQuery, setNavMeshQuery] = useState(); + + const capsule = useRef(null!); + + const controls = useRef({ + forward: false, + backward: false, + left: false, + right: false, + }); + + useEffect(() => { + const keyControlMap = { + w: 'forward', + s: 'backward', + a: 'left', + d: 'right', + ArrowUp: 'forward', + ArrowDown: 'backward', + ArrowLeft: 'left', + ArrowRight: 'right', + }; + + const handleKeyDown = (event: KeyboardEvent) => { + const control = keyControlMap[event.key]; + if (control) { + controls.current[control] = true; + } + }; + + const handleKeyUp = (event: KeyboardEvent) => { + const control = keyControlMap[event.key]; + if (control) { + controls.current[control] = false; + } + }; + + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + }; + }, []); + + useFrame(({ camera }, delta) => { + if (!capsule.current || !navMeshQuery) return; + + const { forward, backward, left, right } = controls.current; + + const velocity = new Vector3(); + + if (forward) velocity.z -= 1; + if (backward) velocity.z += 1; + if (left) velocity.x -= 1; + if (right) velocity.x += 1; + + velocity.normalize(); + + velocity.multiplyScalar(1 - Math.pow(0.001, delta)).multiplyScalar(0.5); + + const { point, polyRef } = navMeshQuery.findClosestPoint({ + x: capsule.current.position.x, + y: capsule.current.position.y, + z: capsule.current.position.z, + }); + + const movementTarget = new Vector3().copy(point).clone().add(velocity); + + const { resultPosition } = navMeshQuery.moveAlongSurface( + polyRef, + point, + movementTarget + ); + + const polyHeightResult = navMeshQuery.getPolyHeight( + polyRef, + resultPosition + ); + + capsule.current.position.set( + resultPosition.x, + polyHeightResult.success ? polyHeightResult.height : resultPosition.y, + resultPosition.z + ); + + const targetCameraPosition = _targetCameraPosition.copy( + capsule.current.position + ); + + targetCameraPosition.y += 10; + targetCameraPosition.z += 10; + + camera.position.copy(targetCameraPosition); + camera.lookAt(capsule.current.position); + }); + + useEffect(() => { + if (!level) return; + + const meshes: Mesh[] = []; + + level.traverse((child) => { + if (child instanceof Mesh) { + meshes.push(child); + } + }); + + const cellSize = 0.05; + + const walkableRadius = 0.3; + const walkableHeight = 0.35; + + const { success, navMesh } = threeToSoloNavMesh(meshes, { + cs: cellSize, + ch: 0.2, + walkableHeight: walkableHeight / cellSize, + walkableRadius: walkableRadius / cellSize, + }); + + if (!success) return; + + const navMeshQuery = new NavMeshQuery(navMesh); + + setNavMesh(navMesh); + setNavMeshQuery(navMeshQuery); + + return () => { + setNavMesh(undefined); + setNavMeshQuery(undefined); + + navMeshQuery.destroy(); + navMesh.destroy(); + }; + }, [level]); + + return ( + <> + + + + + + + + + + + + + + +
+ use wasd or arrow keys to move +
+
+ + ); +}; diff --git a/packages/recast-navigation/.storybook/stories/nav-mesh-query/nearby-polygons.stories.tsx b/packages/recast-navigation/.storybook/stories/nav-mesh-query/nearby-polygons.stories.tsx index 07da0c73..5ac6ced3 100644 --- a/packages/recast-navigation/.storybook/stories/nav-mesh-query/nearby-polygons.stories.tsx +++ b/packages/recast-navigation/.storybook/stories/nav-mesh-query/nearby-polygons.stories.tsx @@ -1,6 +1,11 @@ import { Html, OrbitControls } from '@react-three/drei'; import { ThreeEvent } from '@react-three/fiber'; -import { NavMesh, NavMeshQuery, range } from '@recast-navigation/core'; +import { + NavMesh, + NavMeshQuery, + range, + statusToReadableString, +} from '@recast-navigation/core'; import { threeToSoloNavMesh } from '@recast-navigation/three'; import React, { Fragment, useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; @@ -71,14 +76,14 @@ export function ClickNearbyPolygons() { if (!success) return; - const navMeshQuery = new NavMeshQuery({ navMesh }); + const navMeshQuery = new NavMeshQuery(navMesh); set({ navMesh, navMeshQuery }); return () => { set({ navMesh: undefined, navMeshQuery: undefined }); - navMeshQuery?.destroy(); + navMeshQuery.destroy(); navMesh.destroy(); }; }, [group]); @@ -146,6 +151,8 @@ export function ClickNearbyPolygons() { navMeshQuery.findNearestPoly(clickedPosition); console.info('findNearestPoly', startRef); + const maxPolys = 100; + const findPolysAroundCircleResult = navMeshQuery.findPolysAroundCircle( startRef, clickedPosition, @@ -153,6 +160,10 @@ export function ClickNearbyPolygons() { { maxPolys } ); console.info('findPolysAroundCircle', findPolysAroundCircleResult); + console.info( + 'findPolysAroundCircle status', + statusToReadableString(findPolysAroundCircleResult.status) + ); const halfExtents = { x: 0.5, y: 0.5, z: 0.5 }; const queryPolygonsResult = navMeshQuery.queryPolygons( @@ -162,10 +173,19 @@ export function ClickNearbyPolygons() { ); console.info('queryPolygons', queryPolygonsResult); - const polyRefs = - selectType === 'circle' - ? findPolysAroundCircleResult.resultRefs - : queryPolygonsResult.polyRefs; + let polyRefs: number[] = []; + + if (selectType === 'circle') { + polyRefs = findPolysAroundCircleResult.resultRefs.slice( + 0, + findPolysAroundCircleResult.resultCount + ); + } else { + polyRefs = queryPolygonsResult.polyRefs.slice( + 0, + queryPolygonsResult.polyCount + ); + } const decodedPolyRefs = polyRefs.map((polyRef) => navMesh.decodePolyId(polyRef) @@ -250,15 +270,21 @@ export function ClickNearbyPolygons() { style={{ position: 'absolute', top: 0, - color: 'white', - padding: 24, + padding: '25px', userSelect: 'none', + fontFamily: 'monospace', + fontWeight: 400, + color: 'white', }} >

Click to select triangles