@recast-navigation/generators 0.37.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
|
-
import { NavMesh, RecastBuildContext, RecastCompactHeightfield, RecastConfig, RecastContourSet, RecastHeightfield, RecastPolyMesh, RecastPolyMeshDetail } from '@recast-navigation/core';
|
|
1
|
+
import { NavMesh, RecastBuildContext, RecastCompactHeightfield, RecastConfig, RecastContourSet, RecastHeightfield, RecastPolyMesh, RecastPolyMeshDetail, UnsignedCharArray, Vector3Tuple } from '@recast-navigation/core';
|
|
2
2
|
import { Pretty } from '../types';
|
|
3
3
|
import { OffMeshConnectionGeneratorParams } from './common';
|
|
4
|
-
export type SoloNavMeshGeneratorConfig = Pretty<Omit<RecastConfig, 'tileSize'> & OffMeshConnectionGeneratorParams
|
|
5
|
-
|
|
4
|
+
export type SoloNavMeshGeneratorConfig = Pretty<Omit<RecastConfig, 'tileSize'> & OffMeshConnectionGeneratorParams & {
|
|
5
|
+
/**
|
|
6
|
+
* @default true
|
|
7
|
+
*/
|
|
8
|
+
buildBvTree?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The minimum and maximum bounds of the heightfield's AABB in world units.
|
|
11
|
+
* If not provided, the bounding box will be calculated from the input positions and indices
|
|
12
|
+
*/
|
|
13
|
+
bounds?: [bbMin: Vector3Tuple, bbMax: Vector3Tuple];
|
|
14
|
+
}>;
|
|
15
|
+
export declare const soloNavMeshGeneratorConfigDefaults: {
|
|
16
|
+
buildBvTree: true;
|
|
17
|
+
borderSize: number;
|
|
18
|
+
tileSize: number;
|
|
19
|
+
cs: number;
|
|
20
|
+
ch: number;
|
|
21
|
+
walkableSlopeAngle: number;
|
|
22
|
+
walkableHeight: number;
|
|
23
|
+
walkableClimb: number;
|
|
24
|
+
walkableRadius: number;
|
|
25
|
+
maxEdgeLen: number;
|
|
26
|
+
maxSimplificationError: number;
|
|
27
|
+
minRegionArea: number;
|
|
28
|
+
mergeRegionArea: number;
|
|
29
|
+
maxVertsPerPoly: number;
|
|
30
|
+
detailSampleDist: number;
|
|
31
|
+
detailSampleMaxError: number;
|
|
32
|
+
};
|
|
6
33
|
export type SoloNavMeshGeneratorIntermediates = {
|
|
7
34
|
type: 'solo';
|
|
8
35
|
buildContext: RecastBuildContext;
|
|
@@ -12,18 +39,38 @@ export type SoloNavMeshGeneratorIntermediates = {
|
|
|
12
39
|
polyMesh?: RecastPolyMesh;
|
|
13
40
|
polyMeshDetail?: RecastPolyMeshDetail;
|
|
14
41
|
};
|
|
15
|
-
type
|
|
42
|
+
type GenerateSoloNavMeshDataSuccessResult = {
|
|
43
|
+
navMeshData: UnsignedCharArray;
|
|
44
|
+
success: true;
|
|
45
|
+
intermediates: SoloNavMeshGeneratorIntermediates;
|
|
46
|
+
};
|
|
47
|
+
type GenerateSoloNavMeshDataFailResult = {
|
|
48
|
+
navMeshData: undefined;
|
|
49
|
+
success: false;
|
|
50
|
+
intermediates: SoloNavMeshGeneratorIntermediates;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
53
|
+
export type GenerateSoloNavMeshDataResult = GenerateSoloNavMeshDataSuccessResult | GenerateSoloNavMeshDataFailResult;
|
|
54
|
+
/**
|
|
55
|
+
* Builds Solo NavMesh data from the given positions and indices.
|
|
56
|
+
* @param positions a flat array of positions
|
|
57
|
+
* @param indices a flat array of indices
|
|
58
|
+
* @param navMeshGeneratorConfig optional configuration for the NavMesh generator
|
|
59
|
+
* @param keepIntermediates if true intermediates will be returned
|
|
60
|
+
*/
|
|
61
|
+
export declare const generateSoloNavMeshData: (positions: ArrayLike<number>, indices: ArrayLike<number>, navMeshGeneratorConfig?: Partial<SoloNavMeshGeneratorConfig>, keepIntermediates?: boolean) => GenerateSoloNavMeshDataResult;
|
|
62
|
+
type GenerateSoloNavMeshSuccessResult = {
|
|
16
63
|
navMesh: NavMesh;
|
|
17
64
|
success: true;
|
|
18
65
|
intermediates: SoloNavMeshGeneratorIntermediates;
|
|
19
66
|
};
|
|
20
|
-
type
|
|
67
|
+
type GenerateSoloNavMeshFailResult = {
|
|
21
68
|
navMesh: undefined;
|
|
22
69
|
success: false;
|
|
23
70
|
intermediates: SoloNavMeshGeneratorIntermediates;
|
|
24
71
|
error: string;
|
|
25
72
|
};
|
|
26
|
-
export type
|
|
73
|
+
export type GenerateSoloNavMeshResult = GenerateSoloNavMeshSuccessResult | GenerateSoloNavMeshFailResult;
|
|
27
74
|
/**
|
|
28
75
|
* Builds a Solo NavMesh from the given positions and indices.
|
|
29
76
|
* @param positions a flat array of positions
|
|
@@ -31,5 +78,5 @@ export type SoloNavMeshGeneratorResult = SoloNavMeshGeneratorSuccessResult | Sol
|
|
|
31
78
|
* @param navMeshGeneratorConfig optional configuration for the NavMesh generator
|
|
32
79
|
* @param keepIntermediates if true intermediates will be returned
|
|
33
80
|
*/
|
|
34
|
-
export declare const generateSoloNavMesh: (positions: ArrayLike<number>, indices: ArrayLike<number>, navMeshGeneratorConfig?: Partial<SoloNavMeshGeneratorConfig>, keepIntermediates?: boolean) =>
|
|
81
|
+
export declare const generateSoloNavMesh: (positions: ArrayLike<number>, indices: ArrayLike<number>, navMeshGeneratorConfig?: Partial<SoloNavMeshGeneratorConfig>, keepIntermediates?: boolean) => GenerateSoloNavMeshResult;
|
|
35
82
|
export {};
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { NavMesh, RecastBuildContext, RecastChunkyTriMesh, RecastCompactHeightfield, RecastConfig, RecastHeightfield, RecastHeightfieldLayerSet, TileCache, TileCacheMeshProcess } from '@recast-navigation/core';
|
|
1
|
+
import { NavMesh, RecastBuildContext, RecastChunkyTriMesh, RecastCompactHeightfield, RecastConfig, RecastHeightfield, RecastHeightfieldLayerSet, TileCache, TileCacheMeshProcess, Vector3Tuple } from '@recast-navigation/core';
|
|
2
2
|
import { Pretty } from '../types';
|
|
3
3
|
type TileCacheRecastConfig = Omit<RecastConfig, 'minRegionArea' | 'maxEdgeLen'>;
|
|
4
4
|
export type TileCacheGeneratorConfig = Pretty<TileCacheRecastConfig & {
|
|
5
|
+
/**
|
|
6
|
+
* The minimum and maximum bounds of the heightfield's AABB in world units.
|
|
7
|
+
* If not provided, the bounding box will be calculated from the input positions and indices
|
|
8
|
+
*/
|
|
9
|
+
bounds?: [bbMin: Vector3Tuple, bbMax: Vector3Tuple];
|
|
5
10
|
/**
|
|
6
11
|
* How many layers (or "floors") each navmesh tile is expected to have.
|
|
7
12
|
*/
|
|
@@ -1,35 +1,105 @@
|
|
|
1
|
-
import { NavMesh, RecastBuildContext, RecastChunkyTriMesh, RecastCompactHeightfield, RecastConfig, RecastContourSet, RecastHeightfield, RecastPolyMesh, RecastPolyMeshDetail } from '@recast-navigation/core';
|
|
1
|
+
import { FloatArray, IntArray, NavMesh, OffMeshConnectionParams, RawModule, RecastBuildContext, RecastChunkyTriMesh, RecastCompactHeightfield, RecastConfig, RecastContourSet, RecastHeightfield, RecastPolyMesh, RecastPolyMeshDetail, UnsignedCharArray, Vector3Tuple } from '@recast-navigation/core';
|
|
2
2
|
import { Pretty } from '../types';
|
|
3
3
|
import { OffMeshConnectionGeneratorParams } from './common';
|
|
4
|
-
export
|
|
5
|
-
|
|
4
|
+
export declare const buildTiledNavMeshRcConfig: ({ recastConfig, navMeshBounds: [navMeshBoundsMin, navMeshBoundsMax], }: {
|
|
5
|
+
recastConfig: RecastConfig;
|
|
6
|
+
navMeshBounds: [min: Vector3Tuple, max: Vector3Tuple];
|
|
7
|
+
}) => {
|
|
8
|
+
config: RawModule.rcConfig;
|
|
9
|
+
gridSize: RawModule.RecastCalcGridSizeResult;
|
|
10
|
+
tileSize: number;
|
|
11
|
+
tileWidth: number;
|
|
12
|
+
tileHeight: number;
|
|
13
|
+
tcs: number;
|
|
14
|
+
orig: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
z: number;
|
|
18
|
+
};
|
|
19
|
+
maxTiles: number;
|
|
20
|
+
maxPolysPerTile: number;
|
|
21
|
+
};
|
|
22
|
+
export type TiledNavMeshGeneratorConfig = Pretty<RecastConfig & OffMeshConnectionGeneratorParams & {
|
|
23
|
+
/**
|
|
24
|
+
* The minimum and maximum bounds of the heightfield's AABB in world units.
|
|
25
|
+
* If not provided, the bounding box will be calculated from the input positions and indices
|
|
26
|
+
*/
|
|
27
|
+
bounds?: [bbMin: Vector3Tuple, bbMax: Vector3Tuple];
|
|
28
|
+
/**
|
|
29
|
+
* @default 128
|
|
30
|
+
*/
|
|
31
|
+
chunkyTriMeshTrisPerChunk?: number;
|
|
32
|
+
/**
|
|
33
|
+
* @default true
|
|
34
|
+
*/
|
|
35
|
+
buildBvTree?: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const tiledNavMeshGeneratorConfigDefaults: {
|
|
38
|
+
chunkyTriMeshTrisPerChunk: number;
|
|
39
|
+
buildBvTree: true;
|
|
40
|
+
borderSize: number;
|
|
41
|
+
tileSize: number;
|
|
42
|
+
cs: number;
|
|
43
|
+
ch: number;
|
|
44
|
+
walkableSlopeAngle: number;
|
|
45
|
+
walkableHeight: number;
|
|
46
|
+
walkableClimb: number;
|
|
47
|
+
walkableRadius: number;
|
|
48
|
+
maxEdgeLen: number;
|
|
49
|
+
maxSimplificationError: number;
|
|
50
|
+
minRegionArea: number;
|
|
51
|
+
mergeRegionArea: number;
|
|
52
|
+
maxVertsPerPoly: number;
|
|
53
|
+
detailSampleDist: number;
|
|
54
|
+
detailSampleMaxError: number;
|
|
55
|
+
};
|
|
6
56
|
type TileIntermediates = {
|
|
7
|
-
|
|
8
|
-
|
|
57
|
+
x: number;
|
|
58
|
+
y: number;
|
|
9
59
|
heightfield?: RecastHeightfield;
|
|
10
60
|
compactHeightfield?: RecastCompactHeightfield;
|
|
11
61
|
contourSet?: RecastContourSet;
|
|
12
62
|
polyMesh?: RecastPolyMesh;
|
|
13
63
|
polyMeshDetail?: RecastPolyMeshDetail;
|
|
14
64
|
};
|
|
65
|
+
type GenerateTileNavMeshDataSuccessResult = {
|
|
66
|
+
success: true;
|
|
67
|
+
data?: UnsignedCharArray;
|
|
68
|
+
intermediates: TileIntermediates;
|
|
69
|
+
};
|
|
70
|
+
type GenerateTileNavMeshDataFailResult = {
|
|
71
|
+
success: false;
|
|
72
|
+
error: string;
|
|
73
|
+
intermediates: TileIntermediates;
|
|
74
|
+
};
|
|
75
|
+
export type GenerateTileNavMeshDataResult = GenerateTileNavMeshDataSuccessResult | GenerateTileNavMeshDataFailResult;
|
|
76
|
+
export declare const generateTileNavMeshData: (positions: FloatArray, indices: IntArray, rcConfig: RawModule.rcConfig, chunkyTriMesh: RecastChunkyTriMesh, tile: {
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
bmin: Vector3Tuple;
|
|
80
|
+
bmax: Vector3Tuple;
|
|
81
|
+
}, options?: {
|
|
82
|
+
offMeshConnections?: OffMeshConnectionParams[];
|
|
83
|
+
buildBvTree?: boolean;
|
|
84
|
+
}, keepIntermediates?: boolean, buildContext?: RecastBuildContext) => GenerateTileNavMeshDataResult;
|
|
15
85
|
export type TiledNavMeshGeneratorIntermediates = {
|
|
16
86
|
type: 'tiled';
|
|
17
87
|
buildContext: RecastBuildContext;
|
|
18
88
|
chunkyTriMesh?: RecastChunkyTriMesh;
|
|
19
89
|
tileIntermediates: TileIntermediates[];
|
|
20
90
|
};
|
|
21
|
-
type
|
|
91
|
+
type GenerateTiledNavMeshSuccessResult = {
|
|
22
92
|
navMesh: NavMesh;
|
|
23
93
|
success: true;
|
|
24
94
|
intermediates: TiledNavMeshGeneratorIntermediates;
|
|
25
95
|
};
|
|
26
|
-
type
|
|
96
|
+
type GenerateTiledNavMeshFailResult = {
|
|
27
97
|
navMesh: undefined;
|
|
28
98
|
success: false;
|
|
29
99
|
intermediates: TiledNavMeshGeneratorIntermediates;
|
|
30
100
|
error: string;
|
|
31
101
|
};
|
|
32
|
-
export type
|
|
102
|
+
export type GenerateTiledNavMeshResult = GenerateTiledNavMeshSuccessResult | GenerateTiledNavMeshFailResult;
|
|
33
103
|
/**
|
|
34
104
|
* Builds a Tiled NavMesh
|
|
35
105
|
* @param positions a flat array of positions
|
|
@@ -37,5 +107,5 @@ export type TiledNavMeshGeneratorResult = TiledNavMeshGeneratorSuccessResult | T
|
|
|
37
107
|
* @param navMeshGeneratorConfig optional configuration for the NavMesh generator
|
|
38
108
|
* @param keepIntermediates if true intermediates will be returned
|
|
39
109
|
*/
|
|
40
|
-
export declare const generateTiledNavMesh: (positions: ArrayLike<number>, indices: ArrayLike<number>, navMeshGeneratorConfig?: Partial<TiledNavMeshGeneratorConfig>, keepIntermediates?: boolean) =>
|
|
110
|
+
export declare const generateTiledNavMesh: (positions: ArrayLike<number>, indices: ArrayLike<number>, navMeshGeneratorConfig?: Partial<TiledNavMeshGeneratorConfig>, keepIntermediates?: boolean) => GenerateTiledNavMeshResult;
|
|
41
111
|
export {};
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { vec3, recastConfigDefaults,
|
|
1
|
+
import { vec3, recastConfigDefaults, RecastBuildContext, VerticesArray, TrianglesArray, createRcConfig, calcGridSize, allocHeightfield, createHeightfield, TriangleAreasArray, markWalkableTriangles, rasterizeTriangles, filterLowHangingWalkableObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, allocCompactHeightfield, buildCompactHeightfield, freeHeightfield, erodeWalkableArea, buildDistanceField, buildRegions, allocContourSet, buildContours, Recast, allocPolyMesh, buildPolyMesh, allocPolyMeshDetail, buildPolyMeshDetail, freeCompactHeightfield, freeContourSet, NavMeshCreateParams, createNavMeshData, Raw, NavMesh, freePolyMesh, freePolyMeshDetail, TileCacheMeshProcess, TileCache, DetourTileCacheParams, NavMeshParams, RecastChunkyTriMesh, statusFailed, freeHeightfieldLayerSet, cloneRcConfig, ChunkIdsArray, allocHeightfieldLayerSet, buildHeightfieldLayers, TileCacheData, Detour, getHeightfieldLayerHeights, getHeightfieldLayerAreas, getHeightfieldLayerCons, buildTileCacheLayer, statusToReadableString } from '@recast-navigation/core';
|
|
2
2
|
|
|
3
3
|
const getBoundingBox = (positions, indices) => {
|
|
4
4
|
const bbMin = {
|
|
@@ -57,25 +57,22 @@ const dtNextPow2 = v => {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
const soloNavMeshGeneratorConfigDefaults = {
|
|
60
|
-
...recastConfigDefaults
|
|
60
|
+
...recastConfigDefaults,
|
|
61
|
+
buildBvTree: true
|
|
61
62
|
};
|
|
62
63
|
/**
|
|
63
|
-
* Builds
|
|
64
|
+
* Builds Solo NavMesh data from the given positions and indices.
|
|
64
65
|
* @param positions a flat array of positions
|
|
65
66
|
* @param indices a flat array of indices
|
|
66
67
|
* @param navMeshGeneratorConfig optional configuration for the NavMesh generator
|
|
67
68
|
* @param keepIntermediates if true intermediates will be returned
|
|
68
69
|
*/
|
|
69
|
-
const
|
|
70
|
-
if (!Raw.Module) {
|
|
71
|
-
throw new Error('"init" must be called before using any recast-navigation-js APIs. See: https://github.com/isaac-mason/recast-navigation-js?tab=readme-ov-file#initialization');
|
|
72
|
-
}
|
|
70
|
+
const generateSoloNavMeshData = (positions, indices, navMeshGeneratorConfig = {}, keepIntermediates = false) => {
|
|
73
71
|
const buildContext = new RecastBuildContext();
|
|
74
72
|
const intermediates = {
|
|
75
73
|
type: 'solo',
|
|
76
74
|
buildContext
|
|
77
75
|
};
|
|
78
|
-
const navMesh = new NavMesh();
|
|
79
76
|
const cleanup = () => {
|
|
80
77
|
if (keepIntermediates) return;
|
|
81
78
|
if (intermediates.heightfield) {
|
|
@@ -101,9 +98,8 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
101
98
|
};
|
|
102
99
|
const fail = error => {
|
|
103
100
|
cleanup();
|
|
104
|
-
navMesh.destroy();
|
|
105
101
|
return {
|
|
106
|
-
|
|
102
|
+
navMeshData: undefined,
|
|
107
103
|
success: false,
|
|
108
104
|
intermediates,
|
|
109
105
|
error
|
|
@@ -118,31 +114,38 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
118
114
|
const numTriangles = indices.length / 3;
|
|
119
115
|
const trianglesArray = new TrianglesArray();
|
|
120
116
|
trianglesArray.copy(triangles);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
let bbMin;
|
|
118
|
+
let bbMax;
|
|
119
|
+
if (navMeshGeneratorConfig.bounds) {
|
|
120
|
+
bbMin = navMeshGeneratorConfig.bounds[0];
|
|
121
|
+
bbMax = navMeshGeneratorConfig.bounds[1];
|
|
122
|
+
} else {
|
|
123
|
+
const boundingBox = getBoundingBox(positions, indices);
|
|
124
|
+
bbMin = boundingBox.bbMin;
|
|
125
|
+
bbMax = boundingBox.bbMax;
|
|
126
|
+
}
|
|
125
127
|
//
|
|
126
128
|
// Step 1. Initialize build config.
|
|
127
129
|
//
|
|
128
|
-
const config =
|
|
130
|
+
const config = {
|
|
129
131
|
...soloNavMeshGeneratorConfigDefaults,
|
|
130
132
|
...navMeshGeneratorConfig
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
};
|
|
134
|
+
const rcConfig = createRcConfig(config);
|
|
135
|
+
rcConfig.minRegionArea = rcConfig.minRegionArea * rcConfig.minRegionArea; // Note: area = size*size
|
|
136
|
+
rcConfig.mergeRegionArea = rcConfig.mergeRegionArea * rcConfig.mergeRegionArea; // Note: area = size*size
|
|
137
|
+
rcConfig.detailSampleDist = rcConfig.detailSampleDist < 0.9 ? 0 : rcConfig.cs * rcConfig.detailSampleDist;
|
|
138
|
+
rcConfig.detailSampleMaxError = rcConfig.ch * rcConfig.detailSampleMaxError;
|
|
139
|
+
const gridSize = calcGridSize(bbMin, bbMax, rcConfig.cs);
|
|
140
|
+
rcConfig.width = gridSize.width;
|
|
141
|
+
rcConfig.height = gridSize.height;
|
|
139
142
|
//
|
|
140
143
|
// Step 2. Rasterize input polygon soup.
|
|
141
144
|
//
|
|
142
145
|
// Allocate voxel heightfield where we rasterize our input data to.
|
|
143
146
|
const heightfield = allocHeightfield();
|
|
144
147
|
intermediates.heightfield = heightfield;
|
|
145
|
-
if (!createHeightfield(buildContext, heightfield,
|
|
148
|
+
if (!createHeightfield(buildContext, heightfield, rcConfig.width, rcConfig.height, bbMin, bbMax, rcConfig.cs, rcConfig.ch)) {
|
|
146
149
|
return fail('Could not create heightfield');
|
|
147
150
|
}
|
|
148
151
|
// Find triangles which are walkable based on their slope and rasterize them.
|
|
@@ -150,8 +153,8 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
150
153
|
// the are type for each of the meshes and rasterize them.
|
|
151
154
|
const triangleAreasArray = new TriangleAreasArray();
|
|
152
155
|
triangleAreasArray.resize(numTriangles);
|
|
153
|
-
markWalkableTriangles(buildContext,
|
|
154
|
-
if (!rasterizeTriangles(buildContext, verticesArray, numVertices, trianglesArray, triangleAreasArray, numTriangles, heightfield,
|
|
156
|
+
markWalkableTriangles(buildContext, rcConfig.walkableSlopeAngle, verticesArray, numVertices, trianglesArray, numTriangles, triangleAreasArray);
|
|
157
|
+
if (!rasterizeTriangles(buildContext, verticesArray, numVertices, trianglesArray, triangleAreasArray, numTriangles, heightfield, rcConfig.walkableClimb)) {
|
|
155
158
|
return fail('Could not rasterize triangles');
|
|
156
159
|
}
|
|
157
160
|
triangleAreasArray.destroy();
|
|
@@ -163,9 +166,9 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
163
166
|
// Once all geoemtry is rasterized, we do initial pass of filtering to
|
|
164
167
|
// remove unwanted overhangs caused by the conservative rasterization
|
|
165
168
|
// as well as filter spans where the character cannot possibly stand.
|
|
166
|
-
filterLowHangingWalkableObstacles(buildContext,
|
|
167
|
-
filterLedgeSpans(buildContext,
|
|
168
|
-
filterWalkableLowHeightSpans(buildContext,
|
|
169
|
+
filterLowHangingWalkableObstacles(buildContext, rcConfig.walkableClimb, heightfield);
|
|
170
|
+
filterLedgeSpans(buildContext, rcConfig.walkableHeight, rcConfig.walkableClimb, heightfield);
|
|
171
|
+
filterWalkableLowHeightSpans(buildContext, rcConfig.walkableHeight, heightfield);
|
|
169
172
|
//
|
|
170
173
|
// Step 4. Partition walkable surface to simple regions.
|
|
171
174
|
//
|
|
@@ -174,7 +177,7 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
174
177
|
// between walkable cells will be calculated.
|
|
175
178
|
const compactHeightfield = allocCompactHeightfield();
|
|
176
179
|
intermediates.compactHeightfield = compactHeightfield;
|
|
177
|
-
if (!buildCompactHeightfield(buildContext,
|
|
180
|
+
if (!buildCompactHeightfield(buildContext, rcConfig.walkableHeight, rcConfig.walkableClimb, heightfield, compactHeightfield)) {
|
|
178
181
|
return fail('Failed to build compact data');
|
|
179
182
|
}
|
|
180
183
|
if (!keepIntermediates) {
|
|
@@ -182,7 +185,7 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
182
185
|
intermediates.heightfield = undefined;
|
|
183
186
|
}
|
|
184
187
|
// Erode the walkable area by agent radius.
|
|
185
|
-
if (!erodeWalkableArea(buildContext,
|
|
188
|
+
if (!erodeWalkableArea(buildContext, rcConfig.walkableRadius, compactHeightfield)) {
|
|
186
189
|
return fail('Failed to erode walkable area');
|
|
187
190
|
}
|
|
188
191
|
// (Optional) Mark areas
|
|
@@ -192,7 +195,7 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
192
195
|
return fail('Failed to build distance field');
|
|
193
196
|
}
|
|
194
197
|
// Partition the walkable surface into simple regions without holes.
|
|
195
|
-
if (!buildRegions(buildContext, compactHeightfield,
|
|
198
|
+
if (!buildRegions(buildContext, compactHeightfield, rcConfig.borderSize, rcConfig.minRegionArea, rcConfig.mergeRegionArea)) {
|
|
196
199
|
return fail('Failed to build regions');
|
|
197
200
|
}
|
|
198
201
|
//
|
|
@@ -200,7 +203,7 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
200
203
|
//
|
|
201
204
|
const contourSet = allocContourSet();
|
|
202
205
|
intermediates.contourSet = contourSet;
|
|
203
|
-
if (!buildContours(buildContext, compactHeightfield,
|
|
206
|
+
if (!buildContours(buildContext, compactHeightfield, rcConfig.maxSimplificationError, rcConfig.maxEdgeLen, contourSet, Recast.RC_CONTOUR_TESS_WALL_EDGES)) {
|
|
204
207
|
return fail('Failed to create contours');
|
|
205
208
|
}
|
|
206
209
|
//
|
|
@@ -208,7 +211,7 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
208
211
|
//
|
|
209
212
|
const polyMesh = allocPolyMesh();
|
|
210
213
|
intermediates.polyMesh = polyMesh;
|
|
211
|
-
if (!buildPolyMesh(buildContext, contourSet,
|
|
214
|
+
if (!buildPolyMesh(buildContext, contourSet, rcConfig.maxVertsPerPoly, polyMesh)) {
|
|
212
215
|
return fail('Failed to triangulate contours');
|
|
213
216
|
}
|
|
214
217
|
//
|
|
@@ -216,7 +219,7 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
216
219
|
//
|
|
217
220
|
const polyMeshDetail = allocPolyMeshDetail();
|
|
218
221
|
intermediates.polyMeshDetail = polyMeshDetail;
|
|
219
|
-
if (!buildPolyMeshDetail(buildContext, polyMesh, compactHeightfield,
|
|
222
|
+
if (!buildPolyMeshDetail(buildContext, polyMesh, compactHeightfield, rcConfig.detailSampleDist, rcConfig.detailSampleMaxError, polyMeshDetail)) {
|
|
220
223
|
return fail('Failed to build detail mesh');
|
|
221
224
|
}
|
|
222
225
|
if (!keepIntermediates) {
|
|
@@ -239,12 +242,12 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
239
242
|
const navMeshCreateParams = new NavMeshCreateParams();
|
|
240
243
|
navMeshCreateParams.setPolyMeshCreateParams(polyMesh);
|
|
241
244
|
navMeshCreateParams.setPolyMeshDetailCreateParams(polyMeshDetail);
|
|
242
|
-
navMeshCreateParams.setWalkableHeight(
|
|
243
|
-
navMeshCreateParams.setWalkableRadius(
|
|
244
|
-
navMeshCreateParams.setWalkableClimb(
|
|
245
|
-
navMeshCreateParams.setCellSize(
|
|
246
|
-
navMeshCreateParams.setCellHeight(
|
|
247
|
-
navMeshCreateParams.setBuildBvTree(
|
|
245
|
+
navMeshCreateParams.setWalkableHeight(rcConfig.walkableHeight * rcConfig.ch);
|
|
246
|
+
navMeshCreateParams.setWalkableRadius(rcConfig.walkableRadius * rcConfig.cs);
|
|
247
|
+
navMeshCreateParams.setWalkableClimb(rcConfig.walkableClimb * rcConfig.ch);
|
|
248
|
+
navMeshCreateParams.setCellSize(rcConfig.cs);
|
|
249
|
+
navMeshCreateParams.setCellHeight(rcConfig.ch);
|
|
250
|
+
navMeshCreateParams.setBuildBvTree(config.buildBvTree);
|
|
248
251
|
if (navMeshGeneratorConfig.offMeshConnections) {
|
|
249
252
|
navMeshCreateParams.setOffMeshConnections(navMeshGeneratorConfig.offMeshConnections);
|
|
250
253
|
}
|
|
@@ -252,16 +255,50 @@ const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, ke
|
|
|
252
255
|
if (!createNavMeshDataResult.success) {
|
|
253
256
|
return fail('Failed to create Detour navmesh data');
|
|
254
257
|
}
|
|
258
|
+
cleanup();
|
|
259
|
+
return {
|
|
260
|
+
navMeshData: createNavMeshDataResult.navMeshData,
|
|
261
|
+
success: true,
|
|
262
|
+
intermediates
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Builds a Solo NavMesh from the given positions and indices.
|
|
267
|
+
* @param positions a flat array of positions
|
|
268
|
+
* @param indices a flat array of indices
|
|
269
|
+
* @param navMeshGeneratorConfig optional configuration for the NavMesh generator
|
|
270
|
+
* @param keepIntermediates if true intermediates will be returned
|
|
271
|
+
*/
|
|
272
|
+
const generateSoloNavMesh = (positions, indices, navMeshGeneratorConfig = {}, keepIntermediates = false) => {
|
|
273
|
+
if (!Raw.Module) {
|
|
274
|
+
throw new Error('"init" must be called before using any recast-navigation-js APIs. See: https://github.com/isaac-mason/recast-navigation-js?tab=readme-ov-file#initialization');
|
|
275
|
+
}
|
|
276
|
+
const createNavMeshDataResult = generateSoloNavMeshData(positions, indices, navMeshGeneratorConfig, keepIntermediates);
|
|
277
|
+
if (!createNavMeshDataResult.success) {
|
|
278
|
+
return {
|
|
279
|
+
navMesh: undefined,
|
|
280
|
+
success: false,
|
|
281
|
+
intermediates: createNavMeshDataResult.intermediates,
|
|
282
|
+
error: createNavMeshDataResult.error
|
|
283
|
+
};
|
|
284
|
+
}
|
|
255
285
|
const {
|
|
256
286
|
navMeshData
|
|
257
287
|
} = createNavMeshDataResult;
|
|
288
|
+
const navMesh = new NavMesh();
|
|
258
289
|
if (!navMesh.initSolo(navMeshData)) {
|
|
259
|
-
|
|
290
|
+
navMeshData.destroy();
|
|
291
|
+
return {
|
|
292
|
+
navMesh: undefined,
|
|
293
|
+
success: false,
|
|
294
|
+
intermediates: createNavMeshDataResult.intermediates,
|
|
295
|
+
error: 'Failed to initialize solo NavMesh'
|
|
296
|
+
};
|
|
260
297
|
}
|
|
261
298
|
return {
|
|
262
299
|
success: true,
|
|
263
300
|
navMesh,
|
|
264
|
-
intermediates
|
|
301
|
+
intermediates: createNavMeshDataResult.intermediates
|
|
265
302
|
};
|
|
266
303
|
};
|
|
267
304
|
|
|
@@ -307,10 +344,16 @@ const generateTileCache = (positions, indices, navMeshGeneratorConfig = {}, keep
|
|
|
307
344
|
const numTriangles = indices.length / 3;
|
|
308
345
|
const trianglesArray = new TrianglesArray();
|
|
309
346
|
trianglesArray.copy(triangles);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
347
|
+
let bbMin;
|
|
348
|
+
let bbMax;
|
|
349
|
+
if (navMeshGeneratorConfig.bounds) {
|
|
350
|
+
bbMin = navMeshGeneratorConfig.bounds[0];
|
|
351
|
+
bbMax = navMeshGeneratorConfig.bounds[1];
|
|
352
|
+
} else {
|
|
353
|
+
const boundingBox = getBoundingBox(positions, indices);
|
|
354
|
+
bbMin = boundingBox.bbMin;
|
|
355
|
+
bbMax = boundingBox.bbMax;
|
|
356
|
+
}
|
|
314
357
|
const {
|
|
315
358
|
expectedLayersPerTile,
|
|
316
359
|
maxObstacles,
|
|
@@ -594,8 +637,271 @@ const generateTileCache = (positions, indices, navMeshGeneratorConfig = {}, keep
|
|
|
594
637
|
};
|
|
595
638
|
};
|
|
596
639
|
|
|
640
|
+
const buildTiledNavMeshRcConfig = ({
|
|
641
|
+
recastConfig,
|
|
642
|
+
navMeshBounds: [navMeshBoundsMin, navMeshBoundsMax]
|
|
643
|
+
}) => {
|
|
644
|
+
//
|
|
645
|
+
// Initialize build config.
|
|
646
|
+
//
|
|
647
|
+
const config = createRcConfig(recastConfig);
|
|
648
|
+
/* grid size */
|
|
649
|
+
const gridSize = calcGridSize(navMeshBoundsMin, navMeshBoundsMax, config.cs);
|
|
650
|
+
config.width = gridSize.width;
|
|
651
|
+
config.height = gridSize.height;
|
|
652
|
+
config.minRegionArea = config.minRegionArea * config.minRegionArea; // Note: area = size*size
|
|
653
|
+
config.mergeRegionArea = config.mergeRegionArea * config.mergeRegionArea; // Note: area = size*size
|
|
654
|
+
config.tileSize = Math.floor(config.tileSize);
|
|
655
|
+
config.borderSize = config.walkableRadius + 3; // Reserve enough padding.
|
|
656
|
+
config.width = config.tileSize + config.borderSize * 2;
|
|
657
|
+
config.height = config.tileSize + config.borderSize * 2;
|
|
658
|
+
config.detailSampleDist = config.detailSampleDist < 0.9 ? 0 : config.cs * config.detailSampleDist;
|
|
659
|
+
config.detailSampleMaxError = config.ch * config.detailSampleMaxError;
|
|
660
|
+
// tile size
|
|
661
|
+
const tileSize = Math.floor(config.tileSize);
|
|
662
|
+
const tileWidth = Math.floor((gridSize.width + tileSize - 1) / tileSize);
|
|
663
|
+
const tileHeight = Math.floor((gridSize.height + tileSize - 1) / tileSize);
|
|
664
|
+
const tcs = config.tileSize * config.cs;
|
|
665
|
+
/* Create dtNavMeshParams, initialise nav mesh for tiled use */
|
|
666
|
+
const orig = vec3.fromArray(navMeshBoundsMin);
|
|
667
|
+
// Max tiles and max polys affect how the tile IDs are caculated.
|
|
668
|
+
// There are 22 bits available for identifying a tile and a polygon.
|
|
669
|
+
let tileBits = Math.min(Math.floor(dtIlog2(dtNextPow2(tileWidth * tileHeight))), 14);
|
|
670
|
+
if (tileBits > 14) tileBits = 14;
|
|
671
|
+
const polyBits = 22 - tileBits;
|
|
672
|
+
const maxTiles = 1 << tileBits;
|
|
673
|
+
const maxPolysPerTile = 1 << polyBits;
|
|
674
|
+
return {
|
|
675
|
+
config,
|
|
676
|
+
gridSize,
|
|
677
|
+
tileSize,
|
|
678
|
+
tileWidth,
|
|
679
|
+
tileHeight,
|
|
680
|
+
tcs,
|
|
681
|
+
orig,
|
|
682
|
+
maxTiles,
|
|
683
|
+
maxPolysPerTile
|
|
684
|
+
};
|
|
685
|
+
};
|
|
597
686
|
const tiledNavMeshGeneratorConfigDefaults = {
|
|
598
|
-
...recastConfigDefaults
|
|
687
|
+
...recastConfigDefaults,
|
|
688
|
+
chunkyTriMeshTrisPerChunk: 256,
|
|
689
|
+
buildBvTree: true
|
|
690
|
+
};
|
|
691
|
+
const generateTileNavMeshData = (positions, indices, rcConfig, chunkyTriMesh, tile, options = {}, keepIntermediates = false, buildContext = new RecastBuildContext()) => {
|
|
692
|
+
const tileIntermediate = {
|
|
693
|
+
x: tile.x,
|
|
694
|
+
y: tile.y
|
|
695
|
+
};
|
|
696
|
+
const cleanup = () => {
|
|
697
|
+
if (keepIntermediates) return;
|
|
698
|
+
if (tileIntermediate.compactHeightfield) {
|
|
699
|
+
freeCompactHeightfield(tileIntermediate.compactHeightfield);
|
|
700
|
+
tileIntermediate.compactHeightfield = undefined;
|
|
701
|
+
}
|
|
702
|
+
if (tileIntermediate.heightfield) {
|
|
703
|
+
freeHeightfield(tileIntermediate.heightfield);
|
|
704
|
+
tileIntermediate.heightfield = undefined;
|
|
705
|
+
}
|
|
706
|
+
if (tileIntermediate.contourSet) {
|
|
707
|
+
freeContourSet(tileIntermediate.contourSet);
|
|
708
|
+
tileIntermediate.contourSet = undefined;
|
|
709
|
+
}
|
|
710
|
+
if (tileIntermediate.polyMesh) {
|
|
711
|
+
freePolyMesh(tileIntermediate.polyMesh);
|
|
712
|
+
tileIntermediate.polyMesh = undefined;
|
|
713
|
+
}
|
|
714
|
+
if (tileIntermediate.polyMeshDetail) {
|
|
715
|
+
freePolyMeshDetail(tileIntermediate.polyMeshDetail);
|
|
716
|
+
tileIntermediate.polyMeshDetail = undefined;
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
const failTileMesh = error => {
|
|
720
|
+
buildContext.log(Recast.RC_LOG_ERROR, error);
|
|
721
|
+
cleanup();
|
|
722
|
+
return {
|
|
723
|
+
success: false,
|
|
724
|
+
error,
|
|
725
|
+
intermediates: tileIntermediate
|
|
726
|
+
};
|
|
727
|
+
};
|
|
728
|
+
const tileConfig = cloneRcConfig(rcConfig);
|
|
729
|
+
// Expand the heightfield bounding box by border size to find the extents of geometry we need to build this tile.
|
|
730
|
+
//
|
|
731
|
+
// This is done in order to make sure that the navmesh tiles connect correctly at the borders,
|
|
732
|
+
// and the obstacles close to the border work correctly with the dilation process.
|
|
733
|
+
// No polygons (or contours) will be created on the border area.
|
|
734
|
+
//
|
|
735
|
+
// IMPORTANT!
|
|
736
|
+
//
|
|
737
|
+
// :''''''''':
|
|
738
|
+
// : +-----+ :
|
|
739
|
+
// : | | :
|
|
740
|
+
// : | |<--- tile to build
|
|
741
|
+
// : | | :
|
|
742
|
+
// : +-----+ :<-- geometry needed
|
|
743
|
+
// :.........:
|
|
744
|
+
//
|
|
745
|
+
// You should use this bounding box to query your input geometry.
|
|
746
|
+
//
|
|
747
|
+
// For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size
|
|
748
|
+
// you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8 neighbours,
|
|
749
|
+
// or use the bounding box below to only pass in a sliver of each of the 8 neighbours.
|
|
750
|
+
const expandedTileBoundsMin = [...tile.bmin];
|
|
751
|
+
const expandedTileBoundsMax = [...tile.bmax];
|
|
752
|
+
expandedTileBoundsMin[0] -= tileConfig.borderSize * tileConfig.cs;
|
|
753
|
+
expandedTileBoundsMin[2] -= tileConfig.borderSize * tileConfig.cs;
|
|
754
|
+
expandedTileBoundsMax[0] += tileConfig.borderSize * tileConfig.cs;
|
|
755
|
+
expandedTileBoundsMax[2] += tileConfig.borderSize * tileConfig.cs;
|
|
756
|
+
tileConfig.set_bmin(0, expandedTileBoundsMin[0]);
|
|
757
|
+
tileConfig.set_bmin(1, expandedTileBoundsMin[1]);
|
|
758
|
+
tileConfig.set_bmin(2, expandedTileBoundsMin[2]);
|
|
759
|
+
tileConfig.set_bmax(0, expandedTileBoundsMax[0]);
|
|
760
|
+
tileConfig.set_bmax(1, expandedTileBoundsMax[1]);
|
|
761
|
+
tileConfig.set_bmax(2, expandedTileBoundsMax[2]);
|
|
762
|
+
// Reset build timer
|
|
763
|
+
buildContext.resetTimers();
|
|
764
|
+
// Start the build process
|
|
765
|
+
buildContext.startTimer(Recast.RC_TIMER_TOTAL);
|
|
766
|
+
buildContext.log(Recast.RC_LOG_PROGRESS, `Building tile at x: ${tile.x}, y: ${tile.y}`);
|
|
767
|
+
buildContext.log(Recast.RC_LOG_PROGRESS, ` - ${tileConfig.width} x ${tileConfig.height} cells`);
|
|
768
|
+
buildContext.log(Recast.RC_LOG_PROGRESS, ` - ${positions.size / 3 / 1000}K verts, ${indices.size / 3 / 1000}K tris`);
|
|
769
|
+
// Allocate voxel heightfield where we rasterize our input data to.
|
|
770
|
+
const heightfield = allocHeightfield();
|
|
771
|
+
tileIntermediate.heightfield = heightfield;
|
|
772
|
+
if (!createHeightfield(buildContext, heightfield, tileConfig.width, tileConfig.height, expandedTileBoundsMin, expandedTileBoundsMax, tileConfig.cs, tileConfig.ch)) {
|
|
773
|
+
return failTileMesh('Could not create heightfield');
|
|
774
|
+
}
|
|
775
|
+
// Allocate array that can hold triangle flags.
|
|
776
|
+
// If you have multiple meshes you need to process, allocate
|
|
777
|
+
// and array which can hold the max number of triangles you need to process.
|
|
778
|
+
const triAreas = new TriangleAreasArray();
|
|
779
|
+
triAreas.resize(chunkyTriMesh.maxTrisPerChunk());
|
|
780
|
+
const tbmin = [expandedTileBoundsMin[0], expandedTileBoundsMin[2]];
|
|
781
|
+
const tbmax = [expandedTileBoundsMax[0], expandedTileBoundsMax[2]];
|
|
782
|
+
// TODO: Make grow when returning too many items.
|
|
783
|
+
const maxChunkIds = 512;
|
|
784
|
+
const chunkIdsArray = new ChunkIdsArray();
|
|
785
|
+
chunkIdsArray.resize(maxChunkIds);
|
|
786
|
+
const nChunksOverlapping = chunkyTriMesh.getChunksOverlappingRect(tbmin, tbmax, chunkIdsArray, maxChunkIds);
|
|
787
|
+
if (nChunksOverlapping === 0) {
|
|
788
|
+
return {
|
|
789
|
+
success: true,
|
|
790
|
+
intermediates: tileIntermediate
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
for (let i = 0; i < nChunksOverlapping; ++i) {
|
|
794
|
+
const nodeId = chunkIdsArray.get(i);
|
|
795
|
+
const node = chunkyTriMesh.nodes(nodeId);
|
|
796
|
+
const nNodeTris = node.n;
|
|
797
|
+
const nodeTrianglesArray = chunkyTriMesh.getNodeTris(nodeId);
|
|
798
|
+
const triangleAreasArray = new TriangleAreasArray();
|
|
799
|
+
triangleAreasArray.resize(nNodeTris);
|
|
800
|
+
// Find triangles which are walkable based on their slope and rasterize them.
|
|
801
|
+
// If your input data is multiple meshes, you can transform them here, calculate
|
|
802
|
+
// the are type for each of the meshes and rasterize them.
|
|
803
|
+
markWalkableTriangles(buildContext, tileConfig.walkableSlopeAngle, positions, indices.size, nodeTrianglesArray, nNodeTris, triangleAreasArray);
|
|
804
|
+
const success = rasterizeTriangles(buildContext, positions, indices.size, nodeTrianglesArray, triangleAreasArray, nNodeTris, heightfield, tileConfig.walkableClimb);
|
|
805
|
+
triangleAreasArray.destroy();
|
|
806
|
+
if (!success) {
|
|
807
|
+
return failTileMesh('Could not rasterize triangles');
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
// Once all geometry is rasterized, we do initial pass of filtering to
|
|
811
|
+
// remove unwanted overhangs caused by the conservative rasterization
|
|
812
|
+
// as well as filter spans where the character cannot possibly stand.
|
|
813
|
+
filterLowHangingWalkableObstacles(buildContext, tileConfig.walkableClimb, heightfield);
|
|
814
|
+
filterLedgeSpans(buildContext, tileConfig.walkableHeight, tileConfig.walkableClimb, heightfield);
|
|
815
|
+
filterWalkableLowHeightSpans(buildContext, tileConfig.walkableHeight, heightfield);
|
|
816
|
+
// Compact the heightfield so that it is faster to handle from now on.
|
|
817
|
+
// This will result more cache coherent data as well as the neighbours
|
|
818
|
+
// between walkable cells will be calculated.
|
|
819
|
+
const compactHeightfield = allocCompactHeightfield();
|
|
820
|
+
tileIntermediate.compactHeightfield = compactHeightfield;
|
|
821
|
+
if (!buildCompactHeightfield(buildContext, tileConfig.walkableHeight, tileConfig.walkableClimb, heightfield, compactHeightfield)) {
|
|
822
|
+
return failTileMesh('Could not build compact heightfield');
|
|
823
|
+
}
|
|
824
|
+
if (!keepIntermediates) {
|
|
825
|
+
freeHeightfield(tileIntermediate.heightfield);
|
|
826
|
+
tileIntermediate.heightfield = undefined;
|
|
827
|
+
}
|
|
828
|
+
// Erode the walkable area by agent radius
|
|
829
|
+
if (!erodeWalkableArea(buildContext, tileConfig.walkableRadius, compactHeightfield)) {
|
|
830
|
+
return failTileMesh('Could not erode walkable area');
|
|
831
|
+
}
|
|
832
|
+
// (Optional) Mark areas
|
|
833
|
+
// markConvexPolyArea(...)
|
|
834
|
+
// Prepare for region partitioning, by calculating Distance field along the walkable surface.
|
|
835
|
+
if (!buildDistanceField(buildContext, compactHeightfield)) {
|
|
836
|
+
return failTileMesh('Failed to build distance field');
|
|
837
|
+
}
|
|
838
|
+
// Partition the walkable surface into simple regions without holes.
|
|
839
|
+
if (!buildRegions(buildContext, compactHeightfield, tileConfig.borderSize, tileConfig.minRegionArea, tileConfig.mergeRegionArea)) {
|
|
840
|
+
return failTileMesh('Failed to build regions');
|
|
841
|
+
}
|
|
842
|
+
//
|
|
843
|
+
// Trace and simplify region contours.
|
|
844
|
+
//
|
|
845
|
+
const contourSet = allocContourSet();
|
|
846
|
+
tileIntermediate.contourSet = contourSet;
|
|
847
|
+
if (!buildContours(buildContext, compactHeightfield, tileConfig.maxSimplificationError, tileConfig.maxEdgeLen, contourSet, Recast.RC_CONTOUR_TESS_WALL_EDGES)) {
|
|
848
|
+
return failTileMesh('Failed to create contours');
|
|
849
|
+
}
|
|
850
|
+
//
|
|
851
|
+
// Build polygons mesh from contours.
|
|
852
|
+
//
|
|
853
|
+
const polyMesh = allocPolyMesh();
|
|
854
|
+
tileIntermediate.polyMesh = polyMesh;
|
|
855
|
+
if (!buildPolyMesh(buildContext, contourSet, tileConfig.maxVertsPerPoly, polyMesh)) {
|
|
856
|
+
return failTileMesh('Failed to triangulate contours');
|
|
857
|
+
}
|
|
858
|
+
//
|
|
859
|
+
// Create detail mesh which allows to access approximate height on each polygon.
|
|
860
|
+
//
|
|
861
|
+
const polyMeshDetail = allocPolyMeshDetail();
|
|
862
|
+
tileIntermediate.polyMeshDetail = polyMeshDetail;
|
|
863
|
+
if (!buildPolyMeshDetail(buildContext, polyMesh, compactHeightfield, tileConfig.detailSampleDist, tileConfig.detailSampleMaxError, polyMeshDetail)) {
|
|
864
|
+
return failTileMesh('Failed to build detail mesh');
|
|
865
|
+
}
|
|
866
|
+
if (!keepIntermediates) {
|
|
867
|
+
freeCompactHeightfield(compactHeightfield);
|
|
868
|
+
tileIntermediate.compactHeightfield = undefined;
|
|
869
|
+
freeContourSet(contourSet);
|
|
870
|
+
tileIntermediate.contourSet = undefined;
|
|
871
|
+
}
|
|
872
|
+
// Update poly flags from areas.
|
|
873
|
+
for (let i = 0; i < polyMesh.npolys(); i++) {
|
|
874
|
+
if (polyMesh.areas(i) == Recast.RC_WALKABLE_AREA) {
|
|
875
|
+
polyMesh.setAreas(i, 0);
|
|
876
|
+
}
|
|
877
|
+
if (polyMesh.areas(i) == 0) {
|
|
878
|
+
polyMesh.setFlags(i, 1);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const navMeshCreateParams = new NavMeshCreateParams();
|
|
882
|
+
navMeshCreateParams.setPolyMeshCreateParams(polyMesh);
|
|
883
|
+
navMeshCreateParams.setPolyMeshDetailCreateParams(polyMeshDetail);
|
|
884
|
+
navMeshCreateParams.setWalkableHeight(tileConfig.walkableHeight * tileConfig.ch);
|
|
885
|
+
navMeshCreateParams.setWalkableRadius(tileConfig.walkableRadius * tileConfig.cs);
|
|
886
|
+
navMeshCreateParams.setWalkableClimb(tileConfig.walkableClimb * tileConfig.ch);
|
|
887
|
+
navMeshCreateParams.setCellSize(tileConfig.cs);
|
|
888
|
+
navMeshCreateParams.setCellHeight(tileConfig.ch);
|
|
889
|
+
navMeshCreateParams.setBuildBvTree(options.buildBvTree ?? tiledNavMeshGeneratorConfigDefaults.buildBvTree);
|
|
890
|
+
if (options.offMeshConnections) {
|
|
891
|
+
navMeshCreateParams.setOffMeshConnections(options.offMeshConnections);
|
|
892
|
+
}
|
|
893
|
+
navMeshCreateParams.setTileX(tile.x);
|
|
894
|
+
navMeshCreateParams.setTileY(tile.y);
|
|
895
|
+
const createNavMeshDataResult = createNavMeshData(navMeshCreateParams);
|
|
896
|
+
if (!createNavMeshDataResult.success) {
|
|
897
|
+
return failTileMesh('Failed to create Detour navmesh data');
|
|
898
|
+
}
|
|
899
|
+
buildContext.log(Recast.RC_LOG_PROGRESS, `>> Polymesh: ${polyMesh.nverts()} vertices ${polyMesh.npolys()} polygons`);
|
|
900
|
+
return {
|
|
901
|
+
success: true,
|
|
902
|
+
data: createNavMeshDataResult.navMeshData,
|
|
903
|
+
intermediates: tileIntermediate
|
|
904
|
+
};
|
|
599
905
|
};
|
|
600
906
|
/**
|
|
601
907
|
* Builds a Tiled NavMesh
|
|
@@ -618,7 +924,6 @@ const generateTiledNavMesh = (positions, indices, navMeshGeneratorConfig = {}, k
|
|
|
618
924
|
const navMesh = new NavMesh();
|
|
619
925
|
/* input geometry */
|
|
620
926
|
const vertices = positions;
|
|
621
|
-
const numVertices = indices.length;
|
|
622
927
|
const verticesArray = new VerticesArray();
|
|
623
928
|
verticesArray.copy(vertices);
|
|
624
929
|
const triangles = indices;
|
|
@@ -629,29 +934,6 @@ const generateTiledNavMesh = (positions, indices, navMeshGeneratorConfig = {}, k
|
|
|
629
934
|
verticesArray.destroy();
|
|
630
935
|
trianglesArray.destroy();
|
|
631
936
|
if (keepIntermediates) return;
|
|
632
|
-
for (let i = 0; i < intermediates.tileIntermediates.length; i++) {
|
|
633
|
-
const tileIntermediate = intermediates.tileIntermediates[i];
|
|
634
|
-
if (tileIntermediate.compactHeightfield) {
|
|
635
|
-
freeCompactHeightfield(tileIntermediate.compactHeightfield);
|
|
636
|
-
tileIntermediate.compactHeightfield = undefined;
|
|
637
|
-
}
|
|
638
|
-
if (tileIntermediate.heightfield) {
|
|
639
|
-
freeHeightfield(tileIntermediate.heightfield);
|
|
640
|
-
tileIntermediate.heightfield = undefined;
|
|
641
|
-
}
|
|
642
|
-
if (tileIntermediate.contourSet) {
|
|
643
|
-
freeContourSet(tileIntermediate.contourSet);
|
|
644
|
-
tileIntermediate.contourSet = undefined;
|
|
645
|
-
}
|
|
646
|
-
if (tileIntermediate.polyMesh) {
|
|
647
|
-
freePolyMesh(tileIntermediate.polyMesh);
|
|
648
|
-
tileIntermediate.polyMesh = undefined;
|
|
649
|
-
}
|
|
650
|
-
if (tileIntermediate.polyMeshDetail) {
|
|
651
|
-
freePolyMeshDetail(tileIntermediate.polyMeshDetail);
|
|
652
|
-
tileIntermediate.polyMeshDetail = undefined;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
937
|
if (intermediates.chunkyTriMesh) {
|
|
656
938
|
intermediates.chunkyTriMesh = undefined;
|
|
657
939
|
}
|
|
@@ -669,45 +951,36 @@ const generateTiledNavMesh = (positions, indices, navMeshGeneratorConfig = {}, k
|
|
|
669
951
|
//
|
|
670
952
|
// Initialize build config.
|
|
671
953
|
//
|
|
672
|
-
const
|
|
954
|
+
const generatorConfig = {
|
|
673
955
|
...tiledNavMeshGeneratorConfigDefaults,
|
|
674
956
|
...navMeshGeneratorConfig
|
|
675
|
-
}
|
|
676
|
-
|
|
957
|
+
};
|
|
958
|
+
let bbMin;
|
|
959
|
+
let bbMax;
|
|
960
|
+
if (navMeshGeneratorConfig.bounds) {
|
|
961
|
+
bbMin = navMeshGeneratorConfig.bounds[0];
|
|
962
|
+
bbMax = navMeshGeneratorConfig.bounds[1];
|
|
963
|
+
} else {
|
|
964
|
+
const boundingBox = getBoundingBox(positions, indices);
|
|
965
|
+
bbMin = boundingBox.bbMin;
|
|
966
|
+
bbMax = boundingBox.bbMax;
|
|
967
|
+
}
|
|
677
968
|
const {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
config.width = config.tileSize + config.borderSize * 2;
|
|
690
|
-
config.height = config.tileSize + config.borderSize * 2;
|
|
691
|
-
config.detailSampleDist = config.detailSampleDist < 0.9 ? 0 : config.cs * config.detailSampleDist;
|
|
692
|
-
config.detailSampleMaxError = config.ch * config.detailSampleMaxError;
|
|
693
|
-
// tile size
|
|
694
|
-
const tileSize = Math.floor(config.tileSize);
|
|
695
|
-
const tileWidth = Math.floor((gridSize.width + tileSize - 1) / tileSize);
|
|
696
|
-
const tileHeight = Math.floor((gridSize.height + tileSize - 1) / tileSize);
|
|
697
|
-
const tcs = config.tileSize * config.cs;
|
|
698
|
-
/* Create dtNavMeshParams, initialise nav mesh for tiled use */
|
|
699
|
-
const orig = vec3.fromArray(bbMin);
|
|
700
|
-
// Max tiles and max polys affect how the tile IDs are caculated.
|
|
701
|
-
// There are 22 bits available for identifying a tile and a polygon.
|
|
702
|
-
let tileBits = Math.min(Math.floor(dtIlog2(dtNextPow2(tileWidth * tileHeight))), 14);
|
|
703
|
-
if (tileBits > 14) tileBits = 14;
|
|
704
|
-
const polyBits = 22 - tileBits;
|
|
705
|
-
const maxTiles = 1 << tileBits;
|
|
706
|
-
const maxPolysPerTile = 1 << polyBits;
|
|
969
|
+
config: rcConfig,
|
|
970
|
+
tileWidth,
|
|
971
|
+
tileHeight,
|
|
972
|
+
tcs,
|
|
973
|
+
orig,
|
|
974
|
+
maxTiles,
|
|
975
|
+
maxPolysPerTile
|
|
976
|
+
} = buildTiledNavMeshRcConfig({
|
|
977
|
+
recastConfig: generatorConfig,
|
|
978
|
+
navMeshBounds: [bbMin, bbMax]
|
|
979
|
+
});
|
|
707
980
|
const navMeshParams = NavMeshParams.create({
|
|
708
981
|
orig,
|
|
709
|
-
tileWidth:
|
|
710
|
-
tileHeight:
|
|
982
|
+
tileWidth: generatorConfig.tileSize * generatorConfig.cs,
|
|
983
|
+
tileHeight: generatorConfig.tileSize * generatorConfig.cs,
|
|
711
984
|
maxTiles,
|
|
712
985
|
maxPolys: maxPolysPerTile
|
|
713
986
|
});
|
|
@@ -717,199 +990,9 @@ const generateTiledNavMesh = (positions, indices, navMeshGeneratorConfig = {}, k
|
|
|
717
990
|
/* create chunky tri mesh */
|
|
718
991
|
const chunkyTriMesh = new RecastChunkyTriMesh();
|
|
719
992
|
intermediates.chunkyTriMesh = chunkyTriMesh;
|
|
720
|
-
if (!chunkyTriMesh.init(verticesArray, trianglesArray, numTriangles,
|
|
993
|
+
if (!chunkyTriMesh.init(verticesArray, trianglesArray, numTriangles, generatorConfig.chunkyTriMeshTrisPerChunk)) {
|
|
721
994
|
return fail('Failed to build chunky triangle mesh');
|
|
722
995
|
}
|
|
723
|
-
/** @internal */
|
|
724
|
-
const buildTileMesh = (tileX, tileY, tileBoundsMin, tileBoundsMax) => {
|
|
725
|
-
const failTileMesh = error => {
|
|
726
|
-
buildContext.log(Recast.RC_LOG_ERROR, error);
|
|
727
|
-
return {
|
|
728
|
-
success: false,
|
|
729
|
-
error
|
|
730
|
-
};
|
|
731
|
-
};
|
|
732
|
-
const tileIntermediate = {
|
|
733
|
-
tileX,
|
|
734
|
-
tileY
|
|
735
|
-
};
|
|
736
|
-
intermediates.tileIntermediates.push(tileIntermediate);
|
|
737
|
-
const tileConfig = cloneRcConfig(config);
|
|
738
|
-
// Expand the heightfield bounding box by border size to find the extents of geometry we need to build this tile.
|
|
739
|
-
//
|
|
740
|
-
// This is done in order to make sure that the navmesh tiles connect correctly at the borders,
|
|
741
|
-
// and the obstacles close to the border work correctly with the dilation process.
|
|
742
|
-
// No polygons (or contours) will be created on the border area.
|
|
743
|
-
//
|
|
744
|
-
// IMPORTANT!
|
|
745
|
-
//
|
|
746
|
-
// :''''''''':
|
|
747
|
-
// : +-----+ :
|
|
748
|
-
// : | | :
|
|
749
|
-
// : | |<--- tile to build
|
|
750
|
-
// : | | :
|
|
751
|
-
// : +-----+ :<-- geometry needed
|
|
752
|
-
// :.........:
|
|
753
|
-
//
|
|
754
|
-
// You should use this bounding box to query your input geometry.
|
|
755
|
-
//
|
|
756
|
-
// For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size
|
|
757
|
-
// you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8 neighbours,
|
|
758
|
-
// or use the bounding box below to only pass in a sliver of each of the 8 neighbours.
|
|
759
|
-
const expandedTileBoundsMin = [...tileBoundsMin];
|
|
760
|
-
const expandedTileBoundsMax = [...tileBoundsMax];
|
|
761
|
-
expandedTileBoundsMin[0] -= tileConfig.borderSize * tileConfig.cs;
|
|
762
|
-
expandedTileBoundsMin[2] -= tileConfig.borderSize * tileConfig.cs;
|
|
763
|
-
expandedTileBoundsMax[0] += tileConfig.borderSize * tileConfig.cs;
|
|
764
|
-
expandedTileBoundsMax[2] += tileConfig.borderSize * tileConfig.cs;
|
|
765
|
-
tileConfig.set_bmin(0, expandedTileBoundsMin[0]);
|
|
766
|
-
tileConfig.set_bmin(1, expandedTileBoundsMin[1]);
|
|
767
|
-
tileConfig.set_bmin(2, expandedTileBoundsMin[2]);
|
|
768
|
-
tileConfig.set_bmax(0, expandedTileBoundsMax[0]);
|
|
769
|
-
tileConfig.set_bmax(1, expandedTileBoundsMax[1]);
|
|
770
|
-
tileConfig.set_bmax(2, expandedTileBoundsMax[2]);
|
|
771
|
-
// Reset build timer
|
|
772
|
-
buildContext.resetTimers();
|
|
773
|
-
// Start the build process
|
|
774
|
-
buildContext.startTimer(Recast.RC_TIMER_TOTAL);
|
|
775
|
-
buildContext.log(Recast.RC_LOG_PROGRESS, `Building tile ${intermediates.tileIntermediates.length} at x: ${tileX}, y: ${tileY}`);
|
|
776
|
-
buildContext.log(Recast.RC_LOG_PROGRESS, ` - ${config.width} x ${config.height} cells`);
|
|
777
|
-
buildContext.log(Recast.RC_LOG_PROGRESS, ` - ${numVertices / 1000}fK verts, ${numTriangles / 1000}K tris`);
|
|
778
|
-
// Allocate voxel heightfield where we rasterize our input data to.
|
|
779
|
-
const heightfield = allocHeightfield();
|
|
780
|
-
tileIntermediate.heightfield = heightfield;
|
|
781
|
-
if (!createHeightfield(buildContext, heightfield, tileConfig.width, tileConfig.height, expandedTileBoundsMin, expandedTileBoundsMax, tileConfig.cs, tileConfig.ch)) {
|
|
782
|
-
return failTileMesh('Could not create heightfield');
|
|
783
|
-
}
|
|
784
|
-
// Allocate array that can hold triangle flags.
|
|
785
|
-
// If you have multiple meshes you need to process, allocate
|
|
786
|
-
// and array which can hold the max number of triangles you need to process.
|
|
787
|
-
const triAreas = new TriangleAreasArray();
|
|
788
|
-
triAreas.resize(chunkyTriMesh.maxTrisPerChunk());
|
|
789
|
-
const tbmin = [expandedTileBoundsMin[0], expandedTileBoundsMin[2]];
|
|
790
|
-
const tbmax = [expandedTileBoundsMax[0], expandedTileBoundsMax[2]];
|
|
791
|
-
// TODO: Make grow when returning too many items.
|
|
792
|
-
const maxChunkIds = 512;
|
|
793
|
-
const chunkIdsArray = new ChunkIdsArray();
|
|
794
|
-
chunkIdsArray.resize(maxChunkIds);
|
|
795
|
-
const nChunksOverlapping = chunkyTriMesh.getChunksOverlappingRect(tbmin, tbmax, chunkIdsArray, maxChunkIds);
|
|
796
|
-
if (nChunksOverlapping === 0) {
|
|
797
|
-
return {
|
|
798
|
-
success: true
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
for (let i = 0; i < nChunksOverlapping; ++i) {
|
|
802
|
-
const nodeId = chunkIdsArray.get(i);
|
|
803
|
-
const node = chunkyTriMesh.nodes(nodeId);
|
|
804
|
-
const nNodeTris = node.n;
|
|
805
|
-
const nodeTrianglesArray = chunkyTriMesh.getNodeTris(nodeId);
|
|
806
|
-
const triangleAreasArray = new TriangleAreasArray();
|
|
807
|
-
triangleAreasArray.resize(nNodeTris);
|
|
808
|
-
// Find triangles which are walkable based on their slope and rasterize them.
|
|
809
|
-
// If your input data is multiple meshes, you can transform them here, calculate
|
|
810
|
-
// the are type for each of the meshes and rasterize them.
|
|
811
|
-
markWalkableTriangles(buildContext, tileConfig.walkableSlopeAngle, verticesArray, numVertices, nodeTrianglesArray, nNodeTris, triangleAreasArray);
|
|
812
|
-
const success = rasterizeTriangles(buildContext, verticesArray, numVertices, nodeTrianglesArray, triangleAreasArray, nNodeTris, heightfield, tileConfig.walkableClimb);
|
|
813
|
-
triangleAreasArray.destroy();
|
|
814
|
-
if (!success) {
|
|
815
|
-
return failTileMesh('Could not rasterize triangles');
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
// Once all geometry is rasterized, we do initial pass of filtering to
|
|
819
|
-
// remove unwanted overhangs caused by the conservative rasterization
|
|
820
|
-
// as well as filter spans where the character cannot possibly stand.
|
|
821
|
-
filterLowHangingWalkableObstacles(buildContext, tileConfig.walkableClimb, heightfield);
|
|
822
|
-
filterLedgeSpans(buildContext, tileConfig.walkableHeight, tileConfig.walkableClimb, heightfield);
|
|
823
|
-
filterWalkableLowHeightSpans(buildContext, tileConfig.walkableHeight, heightfield);
|
|
824
|
-
// Compact the heightfield so that it is faster to handle from now on.
|
|
825
|
-
// This will result more cache coherent data as well as the neighbours
|
|
826
|
-
// between walkable cells will be calculated.
|
|
827
|
-
const compactHeightfield = allocCompactHeightfield();
|
|
828
|
-
tileIntermediate.compactHeightfield = compactHeightfield;
|
|
829
|
-
if (!buildCompactHeightfield(buildContext, tileConfig.walkableHeight, tileConfig.walkableClimb, heightfield, compactHeightfield)) {
|
|
830
|
-
return failTileMesh('Could not build compact heightfield');
|
|
831
|
-
}
|
|
832
|
-
if (!keepIntermediates) {
|
|
833
|
-
freeHeightfield(tileIntermediate.heightfield);
|
|
834
|
-
tileIntermediate.heightfield = undefined;
|
|
835
|
-
}
|
|
836
|
-
// Erode the walkable area by agent radius
|
|
837
|
-
if (!erodeWalkableArea(buildContext, tileConfig.walkableRadius, compactHeightfield)) {
|
|
838
|
-
return failTileMesh('Could not erode walkable area');
|
|
839
|
-
}
|
|
840
|
-
// (Optional) Mark areas
|
|
841
|
-
// markConvexPolyArea(...)
|
|
842
|
-
// Prepare for region partitioning, by calculating Distance field along the walkable surface.
|
|
843
|
-
if (!buildDistanceField(buildContext, compactHeightfield)) {
|
|
844
|
-
return failTileMesh('Failed to build distance field');
|
|
845
|
-
}
|
|
846
|
-
// Partition the walkable surface into simple regions without holes.
|
|
847
|
-
if (!buildRegions(buildContext, compactHeightfield, tileConfig.borderSize, tileConfig.minRegionArea, tileConfig.mergeRegionArea)) {
|
|
848
|
-
return failTileMesh('Failed to build regions');
|
|
849
|
-
}
|
|
850
|
-
//
|
|
851
|
-
// Trace and simplify region contours.
|
|
852
|
-
//
|
|
853
|
-
const contourSet = allocContourSet();
|
|
854
|
-
tileIntermediate.contourSet = contourSet;
|
|
855
|
-
if (!buildContours(buildContext, compactHeightfield, tileConfig.maxSimplificationError, tileConfig.maxEdgeLen, contourSet, Recast.RC_CONTOUR_TESS_WALL_EDGES)) {
|
|
856
|
-
return failTileMesh('Failed to create contours');
|
|
857
|
-
}
|
|
858
|
-
//
|
|
859
|
-
// Build polygons mesh from contours.
|
|
860
|
-
//
|
|
861
|
-
const polyMesh = allocPolyMesh();
|
|
862
|
-
tileIntermediate.polyMesh = polyMesh;
|
|
863
|
-
if (!buildPolyMesh(buildContext, contourSet, tileConfig.maxVertsPerPoly, polyMesh)) {
|
|
864
|
-
return failTileMesh('Failed to triangulate contours');
|
|
865
|
-
}
|
|
866
|
-
//
|
|
867
|
-
// Create detail mesh which allows to access approximate height on each polygon.
|
|
868
|
-
//
|
|
869
|
-
const polyMeshDetail = allocPolyMeshDetail();
|
|
870
|
-
tileIntermediate.polyMeshDetail = polyMeshDetail;
|
|
871
|
-
if (!buildPolyMeshDetail(buildContext, polyMesh, compactHeightfield, tileConfig.detailSampleDist, tileConfig.detailSampleMaxError, polyMeshDetail)) {
|
|
872
|
-
return failTileMesh('Failed to build detail mesh');
|
|
873
|
-
}
|
|
874
|
-
if (!keepIntermediates) {
|
|
875
|
-
freeCompactHeightfield(compactHeightfield);
|
|
876
|
-
tileIntermediate.compactHeightfield = undefined;
|
|
877
|
-
freeContourSet(contourSet);
|
|
878
|
-
tileIntermediate.contourSet = undefined;
|
|
879
|
-
}
|
|
880
|
-
// Update poly flags from areas.
|
|
881
|
-
for (let i = 0; i < polyMesh.npolys(); i++) {
|
|
882
|
-
if (polyMesh.areas(i) == Recast.RC_WALKABLE_AREA) {
|
|
883
|
-
polyMesh.setAreas(i, 0);
|
|
884
|
-
}
|
|
885
|
-
if (polyMesh.areas(i) == 0) {
|
|
886
|
-
polyMesh.setFlags(i, 1);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
const navMeshCreateParams = new NavMeshCreateParams();
|
|
890
|
-
navMeshCreateParams.setPolyMeshCreateParams(polyMesh);
|
|
891
|
-
navMeshCreateParams.setPolyMeshDetailCreateParams(polyMeshDetail);
|
|
892
|
-
navMeshCreateParams.setWalkableHeight(tileConfig.walkableHeight * tileConfig.ch);
|
|
893
|
-
navMeshCreateParams.setWalkableRadius(tileConfig.walkableRadius * tileConfig.cs);
|
|
894
|
-
navMeshCreateParams.setWalkableClimb(tileConfig.walkableClimb * tileConfig.ch);
|
|
895
|
-
navMeshCreateParams.setCellSize(tileConfig.cs);
|
|
896
|
-
navMeshCreateParams.setCellHeight(tileConfig.ch);
|
|
897
|
-
navMeshCreateParams.setBuildBvTree(true);
|
|
898
|
-
if (navMeshGeneratorConfig.offMeshConnections) {
|
|
899
|
-
navMeshCreateParams.setOffMeshConnections(navMeshGeneratorConfig.offMeshConnections);
|
|
900
|
-
}
|
|
901
|
-
navMeshCreateParams.setTileX(tileX);
|
|
902
|
-
navMeshCreateParams.setTileY(tileY);
|
|
903
|
-
const createNavMeshDataResult = createNavMeshData(navMeshCreateParams);
|
|
904
|
-
if (!createNavMeshDataResult.success) {
|
|
905
|
-
return failTileMesh('Failed to create Detour navmesh data');
|
|
906
|
-
}
|
|
907
|
-
buildContext.log(Recast.RC_LOG_PROGRESS, `>> Polymesh: ${polyMesh.nverts()} vertices ${polyMesh.npolys()} polygons`);
|
|
908
|
-
return {
|
|
909
|
-
success: true,
|
|
910
|
-
data: createNavMeshDataResult.navMeshData
|
|
911
|
-
};
|
|
912
|
-
};
|
|
913
996
|
buildContext.startTimer(Recast.RC_TIMER_TEMP);
|
|
914
997
|
const lastBuiltTileBmin = [0, 0, 0];
|
|
915
998
|
const lastBuiltTileBmax = [0, 0, 0];
|
|
@@ -921,12 +1004,23 @@ const generateTiledNavMesh = (positions, indices, navMeshGeneratorConfig = {}, k
|
|
|
921
1004
|
lastBuiltTileBmax[0] = bbMin[0] + (x + 1) * tcs;
|
|
922
1005
|
lastBuiltTileBmax[1] = bbMax[1];
|
|
923
1006
|
lastBuiltTileBmax[2] = bbMin[2] + (y + 1) * tcs;
|
|
924
|
-
const
|
|
1007
|
+
const tile = {
|
|
1008
|
+
x,
|
|
1009
|
+
y,
|
|
1010
|
+
bmin: lastBuiltTileBmin,
|
|
1011
|
+
bmax: lastBuiltTileBmax
|
|
1012
|
+
};
|
|
1013
|
+
const generatorOptions = {
|
|
1014
|
+
offMeshConnections: generatorConfig.offMeshConnections,
|
|
1015
|
+
buildBvTree: generatorConfig.buildBvTree
|
|
1016
|
+
};
|
|
1017
|
+
const result = generateTileNavMeshData(verticesArray, trianglesArray, rcConfig, chunkyTriMesh, tile, generatorOptions, keepIntermediates, buildContext);
|
|
1018
|
+
intermediates.tileIntermediates.push(result.intermediates);
|
|
925
1019
|
if (result.success && result.data) {
|
|
926
1020
|
navMesh.removeTile(navMesh.getTileRefAt(x, y, 0));
|
|
927
1021
|
const addTileResult = navMesh.addTile(result.data, Detour.DT_TILE_FREE_DATA, 0);
|
|
928
1022
|
if (statusFailed(addTileResult.status)) {
|
|
929
|
-
buildContext.log(Recast.RC_LOG_WARNING, `Failed to add tile to nav mesh
|
|
1023
|
+
buildContext.log(Recast.RC_LOG_WARNING, `Failed to add tile to nav mesh tx: ${x}, ty: ${y}, status: ${statusToReadableString(addTileResult.status)} (${addTileResult.status})`);
|
|
930
1024
|
result.data.destroy();
|
|
931
1025
|
}
|
|
932
1026
|
}
|
|
@@ -970,4 +1064,4 @@ const mergePositionsAndIndices = meshes => {
|
|
|
970
1064
|
return [Float32Array.from(mergedPositions), Uint32Array.from(mergedIndices)];
|
|
971
1065
|
};
|
|
972
1066
|
|
|
973
|
-
export { createDefaultTileCacheMeshProcess, dtIlog2, dtNextPow2, generateSoloNavMesh, generateTileCache, generateTiledNavMesh, getBoundingBox, mergePositionsAndIndices, soloNavMeshGeneratorConfigDefaults, tileCacheGeneratorConfigDefaults, tiledNavMeshGeneratorConfigDefaults };
|
|
1067
|
+
export { buildTiledNavMeshRcConfig, createDefaultTileCacheMeshProcess, dtIlog2, dtNextPow2, generateSoloNavMesh, generateSoloNavMeshData, generateTileCache, generateTileNavMeshData, generateTiledNavMesh, getBoundingBox, mergePositionsAndIndices, soloNavMeshGeneratorConfigDefaults, tileCacheGeneratorConfigDefaults, tiledNavMeshGeneratorConfigDefaults };
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"email": "isaac@isaacmason.com",
|
|
7
7
|
"url": "https://isaacmason.com/"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.39.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"homepage": "https://github.com/isaac-mason/recast-navigation-js",
|
|
12
12
|
"bugs": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"lint": "eslint src"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@recast-navigation/core": "0.
|
|
31
|
-
"@recast-navigation/wasm": "0.
|
|
30
|
+
"@recast-navigation/core": "0.39.0",
|
|
31
|
+
"@recast-navigation/wasm": "0.39.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@babel/core": "^7.24.5",
|