UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

189 lines (145 loc) • 6.23 kB
import Quaternion from "../../../../core/geom/Quaternion.js"; import Vector3 from "../../../../core/geom/Vector3.js"; import { min2 } from "../../../../core/math/min2.js"; import { ResourceAccessKind } from "../../../../core/model/ResourceAccessKind.js"; import { ResourceAccessSpecification } from "../../../../core/model/ResourceAccessSpecification.js"; import { System } from '../../../ecs/System.js'; import { Transform } from '../../../ecs/transform/Transform.js'; import Path from '../components/Path.js'; import PathFollower from './PathFollower.js'; import { PathFollowerEventType } from "./PathFollowerEventType.js"; import { PathFollowerFlags } from "./PathFollowerFlags.js"; const v3_forward = new Vector3(); const v3_temp1 = new Vector3(); const v3_temp2 = new Vector3(); const scratch_quaternion = new Quaternion(); /** * * @param {PathFollower} pathFollower * @param {Path} path * @param {Transform} transform * @param {number} timeDelta */ function performStep(pathFollower, path, transform, timeDelta) { let remainingDistance = pathFollower.speed.getValue() * timeDelta; const path_length = path.length; while (remainingDistance > 0) { let stepLimit = pathFollower.maxMoveDistance; if (stepLimit <= 0) { console.warn('pathFollower.maxMoveDistance <= 0, defaulting to 1'); stepLimit = 1; } const stepDistance = min2(remainingDistance, stepLimit); remainingDistance -= stepDistance; /** * * @type {Vector3} */ const nextPosition = v3_temp1; const next_offset = pathFollower.position + stepDistance; const next_offset_truncated = next_offset % path_length; if (next_offset > path_length && !pathFollower.getFlag(PathFollowerFlags.Loop)) { // we're not looping remainingDistance = 0; path.sample(nextPosition, path_length); pathFollower.setFlag(PathFollowerFlags.Finished); pathFollower.position = path_length; } else { path.sample(nextPosition, next_offset_truncated); pathFollower.position = next_offset_truncated; } const position = transform.position; if (!pathFollower.getFlag(PathFollowerFlags.WritePositionX)) { nextPosition.setX(position.x); } if (!pathFollower.getFlag(PathFollowerFlags.WritePositionY)) { nextPosition.setY(position.y); } if (!pathFollower.getFlag(PathFollowerFlags.WritePositionZ)) { nextPosition.setZ(position.z); } if (!nextPosition.equals(position)) { const oldPosition = position; const alignment_x = pathFollower.getFlag(PathFollowerFlags.WriteRotationX); const alignment_y = pathFollower.getFlag(PathFollowerFlags.WriteRotationY); const alignment_z = pathFollower.getFlag(PathFollowerFlags.WriteRotationZ); if (alignment_x || alignment_y || alignment_z) { // TODO we can extract tangent and normal from the path itself, no need to do euler approximation //compute old facing direction vector v3_forward.copy(Vector3.forward); v3_forward.applyQuaternion(transform.rotation); const positionDelta = v3_temp2; positionDelta.subVectors(nextPosition, oldPosition); if (!alignment_x) { positionDelta.x = v3_forward.x; } if (!alignment_y) { positionDelta.y = v3_forward.y; } if (!alignment_z) { positionDelta.z = v3_forward.z; } positionDelta.normalize(); const angularLimit = pathFollower.rotationSpeed.getValue() * timeDelta; // console.log("Angular limit:", angularLimit, positionDelta.toJSON()); scratch_quaternion.lookRotation(positionDelta); transform.rotation.rotateTowards(scratch_quaternion,angularLimit); } position.copy(nextPosition); } } } class PathFollowingSystem extends System { constructor() { super(); this.dependencies = [PathFollower]; this.components_used = [ ResourceAccessSpecification.from(PathFollower, ResourceAccessKind.Read | ResourceAccessKind.Write), ResourceAccessSpecification.from(Transform, ResourceAccessKind.Read | ResourceAccessKind.Write), ResourceAccessSpecification.from(Path, ResourceAccessKind.Read), ]; /** * * @type {number} * @private */ this.__time_delta = 0; } /** * * @param {PathFollower} pathFollower * @param {Transform} transform * @param {Path} path * @param {number} entity Entity ID */ __visit_entity(pathFollower, transform, path, entity) { if (!pathFollower.getFlag(PathFollowerFlags.Active)) { //follower is not active, skip return; } if (path.isEmpty()) { //path is empty, do nothing return; } performStep(pathFollower, path, transform, this.__time_delta); if (pathFollower.getFlag(PathFollowerFlags.Finished)) { //deactivate pathFollower.clearFlag(PathFollowerFlags.Active); const dataset = this.entityManager.dataset dataset.sendEvent(entity, PathFollowerEventType.EndReached); } } /** * * @param {number} timeDelta Time in seconds */ update(timeDelta) { this.__time_delta = timeDelta; const entityManager = this.entityManager; const dataset = entityManager.dataset; if (dataset !== null) { dataset.traverseEntities([PathFollower, Transform, Path], this.__visit_entity, this); } } } export default PathFollowingSystem;