@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
236 lines (174 loc) • 5.66 kB
JavaScript
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);
}
}
}