UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

225 lines (176 loc) • 7.44 kB
import { assert } from "../../../core/assert.js"; /** * * @param {number} i0 * @param {number} i1 * @param {number[]|ArrayLike<number>} vertex_data * @returns {boolean} */ function vertices_equal(i0, i1, vertex_data) { assert.isNonNegativeInteger(i0, "i0"); assert.isNonNegativeInteger(i1, "i1"); const address_0 = i0 * 3; const x0 = vertex_data[address_0]; const y0 = vertex_data[address_0 + 1]; const z0 = vertex_data[address_0 + 2]; const address_1 = i1 * 3; const x1 = vertex_data[address_1]; const y1 = vertex_data[address_1 + 1]; const z1 = vertex_data[address_1 + 2]; const dx = x1 - x0; const dy = y1 - y0; const dz = z1 - z0; const distance_sqr = dx * dx + dy * dy + dz * dz; return distance_sqr < 1e-7; } /** * * @param {number} i0 * @param {number} i1 * @param {number} i2 * @param {number[]|Float32Array} vertex_data * @param {number} normal_x * @param {number} normal_y * @param {number} normal_z */ function triangle_signed_area( i0, i1, i2, vertex_data, normal_x, normal_y, normal_z ) { assert.isNonNegativeInteger(i0, "i0"); assert.isNonNegativeInteger(i1, "i1"); assert.isNonNegativeInteger(i2, "i2"); assert.isNumber(normal_x, "normal_x"); assert.isNumber(normal_y, "normal_y"); assert.isNumber(normal_z, "normal_z"); const address_0 = i0 * 3; const x0 = vertex_data[address_0]; const y0 = vertex_data[address_0 + 1]; const z0 = vertex_data[address_0 + 2]; const address_1 = i1 * 3; const x1 = vertex_data[address_1]; const y1 = vertex_data[address_1 + 1]; const z1 = vertex_data[address_1 + 2]; const address_2 = i2 * 3; const x2 = vertex_data[address_2]; const y2 = vertex_data[address_2 + 1]; const z2 = vertex_data[address_2 + 2]; // 1. Calculate Vector U (b - a) const ux = x1 - x0; const uy = y1 - y0; const uz = z1 - z0; // 2. Calculate Vector V (c - a) const vx = x2 - x0; const vy = y2 - y0; const vz = z2 - z0; // 3. Calculate 3D Cross Product (U x V) const crossX = uy * vz - uz * vy; const crossY = uz * vx - ux * vz; const crossZ = ux * vy - uy * vx; // 4. Dot Product with the Surface Normal return (crossX * normal_x) + (crossY * normal_y) + (crossZ * normal_z); } /** * Implementation of the string-pulling algorithm for funnel path refinement. * Does not allocate memory. * * * @param {Uint32Array|number[]|ArrayLike<number>} output Indices of vertices are written here * @param {number} output_offset where to start writing into the output, typically you want to pass 0 here. * @param {Uint32Array|Uint16Array|Uint8Array|number[]} portal_vertices Flat array of vertex index pairs [left0,right0, left1, right1 ... leftN, rightN] * @param {Float32Array|number[]|ArrayLike<number>} portal_normals Normal vectors for each portal. Should be set to the triangle normal, which we exit through this portal. can be filled with [up, up, up... ] if your portals lay in the horizontal plane mostly. * @param {number} portal_count how many portals do we have to work with * @param {Float32Array|number[]} vertex_data actual vertex positions * @returns number of vertices written to the output */ export function funnel_string_pull( output, output_offset, portal_vertices, portal_normals, portal_count, vertex_data ) { assert.isArrayLike(output, "output"); assert.isNonNegativeInteger(output_offset, "output_offset"); assert.isArrayLike(portal_vertices, "portal_vertices"); assert.isArrayLike(portal_normals, "portal_normals"); assert.isNonNegativeInteger(portal_count, "portal_count"); assert.isArrayLike(vertex_data, "vertex_data"); if (portal_count === 0) { // special case, no data return 0; } // see https://github.com/donmccurdy/three-pathfinding/blob/1f0345a516b7d1a1dac6649cdb6e815b47df6dce/src/Channel.js#L16 let output_cursor = output_offset; // Init scan state let apexIndex = 0, leftIndex = 0, rightIndex = 0; let portalApex = portal_vertices[0]; let portalLeft = portal_vertices[0]; let portalRight = portal_vertices[1]; // Add start point. output[output_cursor++] = portalApex; for (let i = 1; i < portal_count; i++) { const portal_address = i * 2; const left = portal_vertices[portal_address]; const right = portal_vertices[portal_address + 1]; const portal_normal_x = portal_normals[i * 3]; const portal_normal_y = portal_normals[i * 3 + 1]; const portal_normal_z = portal_normals[i * 3 + 2]; // Update right vertex. if (triangle_signed_area(portalApex, portalRight, right, vertex_data, portal_normal_x, portal_normal_y, portal_normal_z) <= 0.0) { if (vertices_equal(portalApex, portalRight, vertex_data) || triangle_signed_area(portalApex, portalLeft, right, vertex_data, portal_normal_x, portal_normal_y, portal_normal_z) > 0.0) { // Tighten the funnel. portalRight = right; rightIndex = i; } else { // Right over left, insert left to path and restart scan from portal left point. output[output_cursor++] = portalLeft; // Make current left the new apex. portalApex = portalLeft; apexIndex = leftIndex; // Reset portal portalLeft = portalApex; portalRight = portalApex; leftIndex = apexIndex; rightIndex = apexIndex; // Restart scan i = apexIndex; continue; } } // Update left vertex. if (triangle_signed_area(portalApex, portalLeft, left, vertex_data, portal_normal_x, portal_normal_y, portal_normal_z) >= 0.0) { if (vertices_equal(portalApex, portalLeft, vertex_data) || triangle_signed_area(portalApex, portalRight, left, vertex_data, portal_normal_x, portal_normal_y, portal_normal_z) < 0.0) { // Tighten the funnel. portalLeft = left; leftIndex = i; } else { // Left over right, insert right to path and restart scan from portal right point. output[output_cursor++] = portalRight; // Make current right the new apex. portalApex = portalRight; apexIndex = rightIndex; // Reset portal portalLeft = portalApex; portalRight = portalApex; leftIndex = apexIndex; rightIndex = apexIndex; // Restart scan i = apexIndex; } } } const last_portal_address = (portal_count - 1) * 2; if ( (output_cursor === output_offset) || (!vertices_equal(output[output_cursor - 1], portal_vertices[last_portal_address], vertex_data)) ) { // Append last point to path. output[output_cursor++] = portal_vertices[last_portal_address] } return output_cursor - output_offset; }