UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

236 lines (174 loc) 5.66 kB
import { IllegalStateException } from "../../../../../core/fsm/exceptions/IllegalStateException.js"; import { EntityPathMarker } from "./EntityPathMarker.js"; import { Transform } from "../../../../ecs/transform/Transform.js"; import Vector3 from "../../../../../core/geom/Vector3.js"; import { assert } from "../../../../../core/assert.js"; import { binarySearchHighIndex } from "../../../../../core/collection/array/binarySearchHighIndex.js"; import { EPSILON } from "../../../../../core/math/EPSILON.js"; const scratch_v3 = new Vector3(); /** * * @param {number} offset * @param {EntityPathMarker} marker * @returns {number} */ function compareOffsetToPathMarker(offset, marker) { return offset - marker.offset; } export class EntityPath { constructor() { /** * * @type {EntityPathStyle} */ this.style = null; /** * * @type {Path} */ this.path = null; /** * * @type {EntityPathMarker[]} */ this.markers = []; } /** * * @param {Path} path */ setPath(path) { assert.notNull(path, 'path'); assert.defined(path, 'path'); this.path = path; } /** * * @returns {Path} */ getPath() { return this.path; } /** * @param {EntityPathStyle} style */ setStyle(style) { this.style = style; } /** * * @returns {EntityPathStyle} */ getStyle() { return this.style; } /** * * @param {number} offset * @param {EntityPathMarkerDefinition} def * @private */ __build_marker(offset, def) { const marker = new EntityPathMarker(); const blueprint = def.blueprint; marker.offset = offset; marker.entity = blueprint.build({}); marker.definition = def; this.__position_marker(marker); this.markers.push(marker); } /** * * @param {EntityPathMarker} marker * @private */ __position_marker(marker) { /** * * @type {Transform} */ const transform = marker.entity.getComponent(Transform); const offset = marker.offset; const path = this.path; path.sample(transform.position, offset); const def = marker.definition; if (def.align) { // get next point position to figure out orientation if (path.sample(scratch_v3, offset + EPSILON)) { scratch_v3.sub(transform.position); } else { // sample behind path.sample(scratch_v3, offset - EPSILON); scratch_v3.subVectors(transform.position, scratch_v3); } scratch_v3.normalize(); transform.rotation.lookRotation(scratch_v3); } transform.multiplyTransforms(transform, def.transform); } updateMarkerPositions() { const length = this.path.computeLength(); const marker_count = this.markers.length; const spacing = length / (marker_count - 1); let offset = 0; for (let i = 0; i < marker_count; i++) { const marker = this.markers[i]; marker.offset = offset; this.__position_marker(marker); offset += spacing; } } build() { if (this.style === null) { throw new IllegalStateException(`No style specified`); } if (this.path === null) { throw new IllegalStateException(`No path specified`); } const path_length = this.path.computeLength(); // build first marker this.__build_marker(0, this.style.marker_start); // we're going to justify the marks, for this we are going to determine justified spacing const spacing = this.style.spacing; const expected_main_marker_count = Math.floor((path_length / spacing) - 2); const justified_spacing = (path_length / (expected_main_marker_count + 1)); let offset = 0; for (let i = 0; i < expected_main_marker_count; i++) { offset += justified_spacing; this.__build_marker(offset, this.style.marker_main); } this.__build_marker(path_length, this.style.marker_end); } /** * * @param {number} offset * @param {function(EntityPathMarker)} callback * @param {*} [thisArg] */ visitMarkersAfter(offset, callback, thisArg) { const markers = this.markers; const marker_count = markers.length; const start = binarySearchHighIndex(markers, offset, compareOffsetToPathMarker, 0, marker_count - 1); for (let i = start; i < marker_count; i++) { const marker = markers[i]; callback.call(thisArg, marker); } } /** * * @param {number} offset * @param {function(EntityPathMarker)} callback * @param {*} [thisArg] */ visitMarkersBefore(offset, callback, thisArg) { const markers = this.markers; const marker_count = markers.length; for (let i = 0; i < marker_count; i++) { const marker = markers[i]; if (marker.offset >= offset) { return; } callback.call(thisArg, marker); } } }