UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

213 lines (160 loc) • 7.3 kB
import { BVH } from "../../../core/bvh2/bvh3/BVH.js"; import { BinaryTopology, NULL_POINTER } from "../../../core/geom/3d/topology/struct/binary/BinaryTopology.js"; import { bt_faces_shared_loop } from "../../../core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js"; import Vector3 from "../../../core/geom/Vector3.js"; import { bt_mesh_face_find_path } from "./bt_mesh_face_find_path.js"; import { navmesh_build_topology } from "./build/navmesh_build_topology.js"; import { bvh_build_from_bt_mesh } from "./bvh_build_from_bt_mesh.js"; import { bvh_query_nearest_face } from "./bvh_query_nearest_face.js"; import { funnel_string_pull } from "./funnel_string_pull.js"; /** * Hard cap on the number of faces we traverse in a single path query. * Limits scratch buffer sizes; paths longer than this are truncated by the A* search. * @type {number} */ const MAX_FACE_PATH_LENGTH = 1024; // face path IDs or (later) final path vertex indices const scratch_array_u32 = new Uint32Array(MAX_FACE_PATH_LENGTH); // one more portal than face-path length (start portal + one between each consecutive pair of faces + goal portal) const MAX_PORTAL_COUNT = MAX_FACE_PATH_LENGTH + 1; // [left0, right0, left1, right1, ...] const scratch_portal_vertices = new Uint32Array(MAX_PORTAL_COUNT * 2); // flat XYZ triples, one per portal const scratch_portal_normals = new Float32Array(MAX_PORTAL_COUNT * 3); // flat XYZ triples; indices 0 and 1 are start/goal, then 2 vertices per intermediate portal const scratch_vertices = new Float32Array((2 + (MAX_PORTAL_COUNT - 2) * 2) * 3); export class NavigationMesh { topology = new BinaryTopology(); /** * Used for raycasts and neighborhood search. * @type {BVH} */ bvh = new BVH(); /** * Build from given scene geometry * @param {BinaryTopology} source * @param {number} [agent_radius] * @param {number} [agent_height] * @param {number} [agent_max_climb_angle] In radians, how steep of an angle can the agent go up by * @param {Vector3} [up] Defines world's "UP" direction, this is what the agent will respect for climbing constraint */ build({ source, agent_radius = 0, agent_height = 0, agent_max_climb_angle = Math.PI / 4, up = Vector3.up, }) { navmesh_build_topology({ destination: this.topology, source, agent_radius, agent_height, agent_max_climb_angle, up, }); bvh_build_from_bt_mesh(this.bvh, this.topology); } /** * Compute a walkable path between the two points. * The result is a sequence of 3d points written into `output`. First point matches start, last point matches goal (snapped onto the mesh). * * @param {Float32Array} output packed XYZ triples * @param {number} start_x * @param {number} start_y * @param {number} start_z * @param {number} goal_x * @param {number} goal_y * @param {number} goal_z * @returns {number} number of 3d points written to `output` (0 if no path was found) */ find_path( output, start_x, start_y, start_z, goal_x, goal_y, goal_z ) { const mesh = this.topology; const bvh = this.bvh; const start_face_id = bvh_query_nearest_face(bvh, mesh, start_x, start_y, start_z); if (start_face_id === NULL_POINTER) { // probably topology is empty return 0; } const goal_face_id = bvh_query_nearest_face(bvh, mesh, goal_x, goal_y, goal_z); if (goal_face_id === NULL_POINTER) { // should never happen if we got the start face return 0; } if (goal_face_id === start_face_id) { // path within the same triangle output[0] = start_x; output[1] = start_y; output[2] = start_z; output[3] = goal_x; output[4] = goal_y; output[5] = goal_z; return 2; } const face_path_length = bt_mesh_face_find_path(scratch_array_u32, start_face_id, goal_face_id, mesh); if (face_path_length === 0) { // no face path exists (disconnected topology) return 0; } // build portals // ================== // initialize vertex data pool, vertex index 0 = start, vertex index 1 = goal scratch_vertices[0] = start_x; scratch_vertices[1] = start_y; scratch_vertices[2] = start_z; scratch_vertices[3] = goal_x; scratch_vertices[4] = goal_y; scratch_vertices[5] = goal_z; let next_vertex_index = 2; // start portal, degenerate portal at the start point, exiting the start face scratch_portal_vertices[0] = 0; scratch_portal_vertices[1] = 0; mesh.face_read_normal(scratch_portal_normals, 0, start_face_id); let portal_index = 1; // intermediate portals sit on the shared edge between two consecutive faces along the path; // the edge is read in the winding order of the face we are exiting, giving a directed (left, right) pair for (let i = 1; i < face_path_length; i++) { const face_from = scratch_array_u32[i - 1]; const face_to = scratch_array_u32[i]; const loop = bt_faces_shared_loop(mesh, face_from, face_to); const left_vertex = mesh.loop_read_vertex(loop); const right_vertex = mesh.loop_read_vertex(mesh.loop_read_next(loop)); const left_index = next_vertex_index++; const right_index = next_vertex_index++; mesh.vertex_read_coordinate(scratch_vertices, left_index * 3, left_vertex); mesh.vertex_read_coordinate(scratch_vertices, right_index * 3, right_vertex); const portal_address = portal_index * 2; scratch_portal_vertices[portal_address] = left_index; scratch_portal_vertices[portal_address + 1] = right_index; mesh.face_read_normal(scratch_portal_normals, portal_index * 3, face_from); portal_index++; } // goal portal, degenerate at the goal point, uses the normal of the face containing the goal const goal_portal_address = portal_index * 2; scratch_portal_vertices[goal_portal_address] = 1; scratch_portal_vertices[goal_portal_address + 1] = 1; mesh.face_read_normal(scratch_portal_normals, portal_index * 3, goal_face_id); portal_index++; // execute string pulling const path_vertex_count = funnel_string_pull( scratch_array_u32, 0, scratch_portal_vertices, scratch_portal_normals, portal_index, scratch_vertices, ); // build the final path for (let i = 0; i < path_vertex_count; i++) { const vertex_index = scratch_array_u32[i]; output[i * 3] = scratch_vertices[vertex_index * 3]; output[i * 3 + 1] = scratch_vertices[vertex_index * 3 + 1]; output[i * 3 + 2] = scratch_vertices[vertex_index * 3 + 2]; } return path_vertex_count; } }