Files
D8H 361f6b3a87 Add experimental "navmesh-based" pathfinding behavior for 2D and 3D (#8943)
- This pathfinding works in 2D and 3D is more flexible than the "grid-based" existing pathfinding.
- For 3D models, you can optionally choose, like for 3D physics, to follow the exact mesh of the 3D model for pathfinding - allowing characters and objects to move on or around the 3D model naturally.
- It handles "crowds" of characters, which can all go to a destination avoiding each others.
2026-08-11 11:11:44 +02:00

193 lines
6.1 KiB
TypeScript

/*
GDevelop - Pathfinding Behavior Extension
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
class NavMeshDebuggerPixiRenderer {
obstaclesManager: NavMeshObstaclesManager;
debugDrawerUtils: RecastNav.DebugDrawerUtils | null = null;
enabled = false;
meshes: Array<THREE.Mesh> = [];
material: THREE.MeshStandardMaterial | null = null;
isRegisteredFor2D = false;
constructor(obstaclesManager: NavMeshObstaclesManager) {
this.obstaclesManager = obstaclesManager;
}
setEnabled(
instanceContainer: gdjs.RuntimeInstanceContainer,
enabled: boolean
): void {
this.enabled = enabled;
if (enabled) {
this.renderFor3D();
if (!this.isRegisteredFor2D) {
this.isRegisteredFor2D = true;
this.registerFor2D(instanceContainer);
}
} else {
this.removeFor3D();
}
}
private registerFor2D(instanceContainer: gdjs.RuntimeInstanceContainer) {
instanceContainer
.getScene()
.registerDebugDrawHook((rendererObject: PIXI.Graphics) => {
if (!this.enabled || this.obstaclesManager.is3D) {
return;
}
const { navMesh, obstacles } = this.obstaclesManager;
if (!navMesh) {
return;
}
const firstObstacle: gdjs.NavMeshObstacleRuntimeBehavior = obstacles
.values()
.next().value;
if (!firstObstacle) {
return;
}
const scene = firstObstacle.owner.getRuntimeScene();
const layer = scene.getLayer(firstObstacle.owner.getLayer());
const workingPoint: FloatPoint = [0, 0];
rendererObject.beginFill();
rendererObject.fill.alpha = 0.5;
rendererObject.fill.color = 0x778ee8;
if (!this.debugDrawerUtils) {
this.debugDrawerUtils = new RecastNav.DebugDrawerUtils();
}
const primitives = this.debugDrawerUtils.drawNavMesh(navMesh);
const speedScaleY = this.obstaclesManager.speedScaleY;
for (const primitive of primitives) {
switch (primitive.type) {
case 'tris':
for (let i = 0; i + 2 < primitive.vertices.length; i += 3) {
const [x0, y0, z0] = primitive.vertices[i];
if (y0 < 1) {
const [x1, _y1, z1] = primitive.vertices[i + 1];
const [x2, _y2, z2] = primitive.vertices[i + 2];
const positions: Array<float> = [];
positions.push.apply(
positions,
layer.applyLayerTransformation(
x0,
z0 * speedScaleY,
0,
workingPoint
)
);
positions.push.apply(
positions,
layer.applyLayerTransformation(
x1,
z1 * speedScaleY,
0,
workingPoint
)
);
positions.push.apply(
positions,
layer.applyLayerTransformation(
x2,
z2 * speedScaleY,
0,
workingPoint
)
);
rendererObject.drawPolygon(positions);
}
}
break;
}
}
rendererObject.endFill();
});
}
removeFor3D() {
for (const mesh of this.meshes) {
mesh.removeFromParent();
}
this.meshes.length = 0;
}
renderFor3D() {
this.removeFor3D();
if (!this.enabled || !this.obstaclesManager.is3D) {
return;
}
const { navMesh, obstacles } = this.obstaclesManager;
if (!navMesh) {
return;
}
const firstObstacle: gdjs.NavMeshObstacleRuntimeBehavior = obstacles
.values()
.next().value;
const scene = firstObstacle.owner.getRuntimeScene();
const layer = scene.getLayer(firstObstacle.owner.getLayer());
const rendererObject = layer.get3DRendererObject();
if (!rendererObject) {
return;
}
if (!this.debugDrawerUtils) {
this.debugDrawerUtils = new RecastNav.DebugDrawerUtils();
}
const primitives = this.debugDrawerUtils.drawNavMesh(navMesh);
for (const primitive of primitives) {
switch (primitive.type) {
case 'tris':
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(primitive.vertices.length * 3);
for (let i = 0; i < primitive.vertices.length; i++) {
const [x, y, z] = primitive.vertices[i];
// Invert triangles side
const triangleVertexIndex = i % 3;
const vertexIndex =
triangleVertexIndex === 0
? i + 1
: triangleVertexIndex === 1
? i - 1
: i;
positions[vertexIndex * 3 + 0] = x;
positions[vertexIndex * 3 + 1] = z;
positions[vertexIndex * 3 + 2] = y;
}
geometry.setAttribute(
'position',
new THREE.BufferAttribute(positions, 3)
);
if (!this.material) {
this.material = new THREE.MeshStandardMaterial({
color: 0x778ee8,
});
}
const mesh = new THREE.Mesh(geometry, this.material);
this.meshes.push(mesh);
rendererObject.add(mesh);
break;
}
}
}
}
// Register the class to let the engine use it.
/**
* @category Debugging > Debugger Renderer
*/
export type NavMeshDebuggerRenderer = NavMeshDebuggerPixiRenderer;
/**
* @category Debugging > Debugger Renderer
*/
export const NavMeshDebuggerRenderer = NavMeshDebuggerPixiRenderer;
}