This commit is contained in:
wuyanchen 2025-12-11 23:53:43 +08:00
parent 32fed2399c
commit 381fd5f99f
2 changed files with 174 additions and 154 deletions

View File

@ -130,7 +130,7 @@ export class Load3D {
engine = this.engine!
const mapName = "Rock"
scene.useRightHandedSystem = true;//计算导航使用右手坐标系与cocos匹配
// scene.useRightHandedSystem = true;//计算导航使用右手坐标系与cocos匹配
const meshes = await this.agent.loadMap(`./3d/model/${mapName}.glb`, scene)
this.envLight = this.light.createEnvLight(scene)
this.topLight = this.light.createDirectionalLight(scene)

View File

@ -1,75 +1,80 @@
import * as BABYLON from '@babylonjs/core/Legacy/legacy'
import Recast from 'recast-detour'
import { saveAs } from 'file-saver';
import * as BABYLON from "@babylonjs/core/Legacy/legacy";
import Recast from "recast-detour";
import { saveAs } from "file-saver";
export class PlayerAgent {
// private navMeshAgent: BABYLON.ICrowd | null = null
private navigationPlugin: BABYLON.RecastJSPlugin | null = null
private meshInfo: any = null
private maps: Map<string, any> = new Map<string, any>()
private mapName: string = ""
private navigationPlugin: BABYLON.RecastJSPlugin | null = null;
private meshInfo: any = null;
private maps: Map<string, BABYLON.INavMeshParameters> = new Map<
string,
BABYLON.INavMeshParameters
>();
private mapName: string = "";
constructor() {
this.maps.set("SD_V2", {
borderSize: 2, //高度场周围不可导航边框的大小。
cs: 0.11, //xz平面单元格大小
ch: 0.01, //y轴单元格大小
borderSize: 2,//高度场周围不可导航边框的大小。
cs: 0.11,//xz平面单元格大小
ch: 0.01,//y轴单元格大小
walkableSlopeAngle: 70, //可以步行的最大坡度。[限制0 <= 值 < 90] [单位:度]
walkableHeight: 10, //到达天花板最小可行走的距离
walkableClimb: 5.6, //可以通过的最大岩架高度
walkableRadius: 1.5, //距离障碍物的距离(半径)
walkableSlopeAngle: 70,//可以步行的最大坡度。[限制0 <= 值 < 90] [单位:度]
walkableHeight: 10,//到达天花板最小可行走的距离
walkableClimb: 5.6,//可以通过的最大岩架高度
walkableRadius: 1.5,//距离障碍物的距离(半径)
maxEdgeLen: 12,//沿网格边界的轮廓边的最大允许长度
maxSimplificationError: 1.3,//简化轮廓的边界边缘应偏离原始原始轮廓的最大距离。
minRegionArea: 8,//允许形成孤岛区域的最小单元 面积
mergeRegionArea: 20,//如果可能,跨度计数小于此值的任何区域都将与更大的区域合并
maxVertsPerPoly: 6,//过程中生成的多边形所允许的最大顶点数
detailSampleDist: 6,//设置生成细节网格时使用的采样距离(仅适用于高度详细信息。)
detailSampleMaxError: 1,//细节网格表面应偏离高度场数据的最大距离。(仅适用于高度详细信息。)
})
maxEdgeLen: 12, //沿网格边界的轮廓边的最大允许长度
maxSimplificationError: 1.3, //简化轮廓的边界边缘应偏离原始原始轮廓的最大距离。
minRegionArea: 8, //允许形成孤岛区域的最小单元 面积
mergeRegionArea: 20, //如果可能,跨度计数小于此值的任何区域都将与更大的区域合并
maxVertsPerPoly: 6, //过程中生成的多边形所允许的最大顶点数
detailSampleDist: 6, //设置生成细节网格时使用的采样距离(仅适用于高度详细信息。)
detailSampleMaxError: 1, //细节网格表面应偏离高度场数据的最大距离。(仅适用于高度详细信息。)
tileSize: 16,//瓦片尺寸
});
this.maps.set("Pyramid", {
borderSize: 2, //高度场周围不可导航边框的大小。
cs: 0.01, //xz平面单元格大小
ch: 0.01, //y轴单元格大小
borderSize: 2,//高度场周围不可导航边框的大小。
cs: 0.01,//xz平面单元格大小
ch: 0.01,//y轴单元格大小
walkableSlopeAngle: 50, //可以步行的最大坡度。[限制0 <= 值 < 90] [单位:度]
walkableHeight: 10, //到达天花板最小可行走的距离
walkableClimb: 2, //可以通过的最大岩架高度
walkableRadius: 1.2, //距离障碍物的距离(半径)
walkableSlopeAngle: 50,//可以步行的最大坡度。[限制0 <= 值 < 90] [单位:度]
walkableHeight: 10,//到达天花板最小可行走的距离
walkableClimb: 2,//可以通过的最大岩架高度
walkableRadius: 1.2,//距离障碍物的距离(半径)
maxEdgeLen: 12,//沿网格边界的轮廓边的最大允许长度
maxSimplificationError: 1.3,//简化轮廓的边界边缘应偏离原始原始轮廓的最大距离。
minRegionArea: 8,//允许形成孤岛区域的最小单元 面积
mergeRegionArea: 20,//如果可能,跨度计数小于此值的任何区域都将与更大的区域合并
maxVertsPerPoly: 6,//过程中生成的多边形所允许的最大顶点数
detailSampleDist: 6,//设置生成细节网格时使用的采样距离(仅适用于高度详细信息。)
detailSampleMaxError: 1,//细节网格表面应偏离高度场数据的最大距离。(仅适用于高度详细信息。)
})
maxEdgeLen: 12, //沿网格边界的轮廓边的最大允许长度
maxSimplificationError: 1.3, //简化轮廓的边界边缘应偏离原始原始轮廓的最大距离。
minRegionArea: 8, //允许形成孤岛区域的最小单元 面积
mergeRegionArea: 20, //如果可能,跨度计数小于此值的任何区域都将与更大的区域合并
maxVertsPerPoly: 6, //过程中生成的多边形所允许的最大顶点数
detailSampleDist: 6, //设置生成细节网格时使用的采样距离(仅适用于高度详细信息。)
detailSampleMaxError: 1, //细节网格表面应偏离高度场数据的最大距离。(仅适用于高度详细信息。)
tileSize: 16,//瓦片尺寸
});
this.maps.set("Rock", {
borderSize: 2, //高度场周围不可导航边框的大小。
cs: 0.05, //xz平面单元格大小
ch: 0.05, //y轴单元格大小
borderSize: 2,//高度场周围不可导航边框的大小。
cs: 0.05,//xz平面单元格大小
ch: 0.05,//y轴单元格大小
walkableSlopeAngle: 50, //可以步行的最大坡度。[限制0 <= 值 < 90] [单位:度]
walkableHeight: 10, //到达天花板最小可行走的距离
walkableClimb: 0.9, //可以通过的最大岩架高度
walkableRadius: 1.5, //距离障碍物的距离(半径)
walkableSlopeAngle: 50,//可以步行的最大坡度。[限制0 <= 值 < 90] [单位:度]
walkableHeight: 10,//到达天花板最小可行走的距离
walkableClimb: 0.9,//可以通过的最大岩架高度
walkableRadius: 1.5,//距离障碍物的距离(半径)
maxEdgeLen: 12,//沿网格边界的轮廓边的最大允许长度
maxSimplificationError: 1.3,//简化轮廓的边界边缘应偏离原始原始轮廓的最大距离。
minRegionArea: 8,//允许形成孤岛区域的最小单元 面积
mergeRegionArea: 20,//如果可能,跨度计数小于此值的任何区域都将与更大的区域合并
maxVertsPerPoly: 6,//过程中生成的多边形所允许的最大顶点数
detailSampleDist: 6,//设置生成细节网格时使用的采样距离(仅适用于高度详细信息。)
detailSampleMaxError: 1,//细节网格表面应偏离高度场数据的最大距离。(仅适用于高度详细信息。)
})
maxEdgeLen: 12, //沿网格边界的轮廓边的最大允许长度
maxSimplificationError: 1.3, //简化轮廓的边界边缘应偏离原始原始轮廓的最大距离。
minRegionArea: 8, //允许形成孤岛区域的最小单元 面积
mergeRegionArea: 20, //如果可能,跨度计数小于此值的任何区域都将与更大的区域合并
maxVertsPerPoly: 6, //过程中生成的多边形所允许的最大顶点数
detailSampleDist: 6, //设置生成细节网格时使用的采样距离(仅适用于高度详细信息。)
detailSampleMaxError: 1, //细节网格表面应偏离高度场数据的最大距离。(仅适用于高度详细信息。)
tileSize: 16,//瓦片尺寸
});
}
public loadMap(modelPath: string, scene: BABYLON.Scene): Promise<Array<BABYLON.AbstractMesh>> {
public loadMap(
modelPath: string,
scene: BABYLON.Scene
): Promise<Array<BABYLON.AbstractMesh>> {
// scene.debugLayer.show({
// embedMode: true,
// showExplorer: true,
@ -77,35 +82,37 @@ export class PlayerAgent {
// handleResize: true,
// })//启用场景调试界面
return new Promise((resolve) => {
BABYLON.ImportMeshAsync(
modelPath,
scene
).then(result => {
resolve(result.meshes)
})
})
BABYLON.ImportMeshAsync(modelPath, scene).then((result) => {
resolve(result.meshes);
});
});
}
public async createAgent(mapName: string, scene: BABYLON.Scene, meshes: Array<BABYLON.Mesh>) {
public async createAgent(
mapName: string,
scene: BABYLON.Scene,
meshes: Array<BABYLON.Mesh>
) {
if (!this.maps.has(mapName)) {
return
return;
}
this.mapName = mapName
const recast = await Recast()
this.mapName = mapName;
const recast = await Recast();
this.navigationPlugin = new BABYLON.RecastJSPlugin(recast);
const parameters = this.maps.get(mapName)
const parameters = this.maps.get(mapName)!;
this.navigationPlugin.createNavMesh(meshes, parameters);
const extend = this.calculateTotalBound(meshes).extendSizeWorld
const extend = this.calculateTotalBound(meshes).extendSizeWorld;
// this.meshInfo = {
// extend: { x: extend.x, y: extend.z, z: extend.y }, //y,z 坐标对换才和cocos里对的上
// };
this.meshInfo = {
extend: { x: extend.x, y: extend.z, z: extend.y }//y,z 坐标对换才和cocos里对的上
}
extend: { x: extend.x, y: extend.y, z: extend.z }, //正常坐标
};
// navigationPlugin.buildFromNavmeshData(navigationPlugin.getNavmeshData())
// const poses = this.navigationPlugin.computePathSmooth(new BABYLON.Vector3(0, 0, 0), new BABYLON.Vector3(1, 0, 1))
// console.log(poses)
// this.navMeshAgent = navigationPlugin.createCrowd(10, 0.5, scene)
@ -127,35 +134,43 @@ export class PlayerAgent {
// // 导航至目标点(自动寻找最近可行走点)
// this.navMeshAgent.agentGoto(playerAgentIndex, new BABYLON.Vector3(1, 0, 1));
const navmeshdebug = this.navigationPlugin.createDebugNavMesh(scene);
const matdebug = new BABYLON.StandardMaterial("matdebug", scene);
matdebug.backFaceCulling = false;//启用双面材质
matdebug.backFaceCulling = false; //启用双面材质
matdebug.diffuseColor = new BABYLON.Color3(0.1, 0.2, 1);
matdebug.alpha = 0.5;
navmeshdebug.material = matdebug;
// scene.getEngine().stopRenderLoop()
}
private getMinAMaxPosExtend(mesh: BABYLON.Mesh): BABYLON.Vector3[] {
const bound = mesh.getBoundingInfo()
const absPos = mesh.absolutePosition
const extendSize = bound.boundingBox.extendSize
extendSize.set(extendSize.x * mesh.scaling.x, extendSize.y * mesh.scaling.y, extendSize.z * mesh.scaling.z)
const min = new BABYLON.Vector3(absPos.x - extendSize.x, absPos.y - extendSize.y, absPos.z - extendSize.z)
const max = new BABYLON.Vector3(absPos.x + extendSize.x, absPos.y + extendSize.y, absPos.z + extendSize.z)
return [min, max]
const bound = mesh.getBoundingInfo();
const absPos = mesh.absolutePosition;
const extendSize = bound.boundingBox.extendSize;
extendSize.set(
extendSize.x * mesh.scaling.x,
extendSize.y * mesh.scaling.y,
extendSize.z * mesh.scaling.z
);
const min = new BABYLON.Vector3(
absPos.x - extendSize.x,
absPos.y - extendSize.y,
absPos.z - extendSize.z
);
const max = new BABYLON.Vector3(
absPos.x + extendSize.x,
absPos.y + extendSize.y,
absPos.z + extendSize.z
);
return [min, max];
}
private calculateTotalBound(meshes: BABYLON.Mesh[]): BABYLON.BoundingBox {
let min = new BABYLON.Vector3(Infinity, Infinity, Infinity);
let max = new BABYLON.Vector3(-Infinity, -Infinity, -Infinity);
meshes.forEach(mesh => {
meshes.forEach((mesh) => {
const loc = this.getMinAMaxPosExtend(mesh);
min = BABYLON.Vector3.Minimize(min, loc[0]);
max = BABYLON.Vector3.Maximize(max, loc[1]);
@ -165,15 +180,20 @@ export class PlayerAgent {
}
public exportNavDataToFileDownload() {
const data = this.navigationPlugin!.getNavmeshData()
saveAs(new Blob([data], {
const data = this.navigationPlugin!.getNavmeshData();
saveAs(
new Blob([data as unknown as BlobPart], {
type: "application/octet-stream",
}), `${this.mapName}.txt`)
}),
`${this.mapName}.txt`
);
const meshInfoJson = JSON.stringify(this.meshInfo)
saveAs(new Blob([meshInfoJson], {
type: "application/json",
}), `${this.mapName}_info.json`)
// const meshInfoJson = JSON.stringify(this.meshInfo);
// saveAs(
// new Blob([meshInfoJson], {
// type: "application/json",
// }),
// `${this.mapName}_info.json`
// );
}
}