import { Vector3 } from '../math/Vector3.js';
import { Logger } from '../core/Logger.js';
import { SteeringBehavior } from './SteeringBehavior';
import { AlignmentBehavior } from './behaviors/AlignmentBehavior';
import { ArriveBehavior } from './behaviors/ArriveBehavior';
import { CohesionBehavior } from './behaviors/CohesionBehavior';
import { EvadeBehavior } from './behaviors/EvadeBehavior';
import { FleeBehavior } from './behaviors/FleeBehavior';
import { FollowPathBehavior } from './behaviors/FollowPathBehavior';
import { InterposeBehavior } from './behaviors/InterposeBehavior';
import { ObstacleAvoidanceBehavior } from './behaviors/ObstacleAvoidanceBehavior';
import { OffsetPursuitBehavior } from './behaviors/OffsetPursuitBehavior';
import { PursuitBehavior } from './behaviors/PursuitBehavior';
import { SeekBehavior } from './behaviors/SeekBehavior';
import { SeparationBehavior } from './behaviors/SeparationBehavior';
import { WanderBehavior } from './behaviors/WanderBehavior';
const force = new Vector3();
/**
* This class is responsible for managing the steering of a single vehicle. The steering manager
* can manage multiple steering behaviors and combine their produced force into a single one used
* by the vehicle.
*
* @author {@link https://github.com/Mugen87|Mugen87}
*/
class SteeringManager {
/**
* Constructs a new steering manager.
*
* @param {Vehicle} vehicle - The vehicle that owns this steering manager.
*/
constructor( vehicle ) {
/**
* The vehicle that owns this steering manager.
* @type {Vehicle}
*/
this.vehicle = vehicle;
/**
* A list of all steering behaviors.
* @type {Array<SteeringBehavior>}
* @readonly
*/
this.behaviors = new Array();
this._steeringForce = new Vector3(); // the calculated steering force per simulation step
this._typesMap = new Map(); // used for deserialization of custom behaviors
}
/**
* Adds the given steering behavior to this steering manager.
*
* @param {SteeringBehavior} behavior - The steering behavior to add.
* @return {SteeringManager} A reference to this steering manager.
*/
add( behavior ) {
this.behaviors.push( behavior );
return this;
}
/**
* Removes the given steering behavior from this steering manager.
*
* @param {SteeringBehavior} behavior - The steering behavior to remove.
* @return {SteeringManager} A reference to this steering manager.
*/
remove( behavior ) {
const index = this.behaviors.indexOf( behavior );
this.behaviors.splice( index, 1 );
return this;
}
/**
* Clears the internal state of this steering manager.
*
* @return {SteeringManager} A reference to this steering manager.
*/
clear() {
this.behaviors.length = 0;
return this;
}
/**
* Calculates the steering forces for all active steering behaviors and
* combines it into a single result force. This method is called in
* {@link Vehicle#update}.
*
* @param {Number} delta - The time delta.
* @param {Vector3} result - The force/result vector.
* @return {Vector3} The force/result vector.
*/
calculate( delta, result ) {
this._calculateByOrder( delta );
return result.copy( this._steeringForce );
}
// this method calculates how much of its max steering force the vehicle has
// left to apply and then applies that amount of the force to add
_accumulate( forceToAdd ) {
// calculate how much steering force the vehicle has used so far
const magnitudeSoFar = this._steeringForce.length();
// calculate how much steering force remains to be used by this vehicle
const magnitudeRemaining = this.vehicle.maxForce - magnitudeSoFar;
// return false if there is no more force left to use
if ( magnitudeRemaining <= 0 ) return false;
// calculate the magnitude of the force we want to add
const magnitudeToAdd = forceToAdd.length();
// restrict the magnitude of forceToAdd, so we don't exceed the max force of the vehicle
if ( magnitudeToAdd > magnitudeRemaining ) {
forceToAdd.normalize().multiplyScalar( magnitudeRemaining );
}
// add force
this._steeringForce.add( forceToAdd );
return true;
}
_calculateByOrder( delta ) {
const behaviors = this.behaviors;
// reset steering force
this._steeringForce.set( 0, 0, 0 );
// calculate for each behavior the respective force
for ( let i = 0, l = behaviors.length; i < l; i ++ ) {
const behavior = behaviors[ i ];
if ( behavior.active === true ) {
force.set( 0, 0, 0 );
behavior.calculate( this.vehicle, force, delta );
force.multiplyScalar( behavior.weight );
if ( this._accumulate( force ) === false ) return;
}
}
}
/**
* Transforms this instance into a JSON object.
*
* @return {Object} The JSON object.
*/
toJSON() {
const data = {
type: 'SteeringManager',
behaviors: new Array()
};
const behaviors = this.behaviors;
for ( let i = 0, l = behaviors.length; i < l; i ++ ) {
const behavior = behaviors[ i ];
data.behaviors.push( behavior.toJSON() );
}
return data;
}
/**
* Restores this instance from the given JSON object.
*
* @param {Object} json - The JSON object.
* @return {SteeringManager} A reference to this steering manager.
*/
fromJSON( json ) {
this.clear();
const behaviorsJSON = json.behaviors;
for ( let i = 0, l = behaviorsJSON.length; i < l; i ++ ) {
const behaviorJSON = behaviorsJSON[ i ];
const type = behaviorJSON.type;
let behavior;
switch ( type ) {
case 'SteeringBehavior':
behavior = new SteeringBehavior().fromJSON( behaviorJSON );
break;
case 'AlignmentBehavior':
behavior = new AlignmentBehavior().fromJSON( behaviorJSON );
break;
case 'ArriveBehavior':
behavior = new ArriveBehavior().fromJSON( behaviorJSON );
break;
case 'CohesionBehavior':
behavior = new CohesionBehavior().fromJSON( behaviorJSON );
break;
case 'EvadeBehavior':
behavior = new EvadeBehavior().fromJSON( behaviorJSON );
break;
case 'FleeBehavior':
behavior = new FleeBehavior().fromJSON( behaviorJSON );
break;
case 'FollowPathBehavior':
behavior = new FollowPathBehavior().fromJSON( behaviorJSON );
break;
case 'InterposeBehavior':
behavior = new InterposeBehavior().fromJSON( behaviorJSON );
break;
case 'ObstacleAvoidanceBehavior':
behavior = new ObstacleAvoidanceBehavior().fromJSON( behaviorJSON );
break;
case 'OffsetPursuitBehavior':
behavior = new OffsetPursuitBehavior().fromJSON( behaviorJSON );
break;
case 'PursuitBehavior':
behavior = new PursuitBehavior().fromJSON( behaviorJSON );
break;
case 'SeekBehavior':
behavior = new SeekBehavior().fromJSON( behaviorJSON );
break;
case 'SeparationBehavior':
behavior = new SeparationBehavior().fromJSON( behaviorJSON );
break;
case 'WanderBehavior':
behavior = new WanderBehavior().fromJSON( behaviorJSON );
break;
default:
// handle custom type
const ctor = this._typesMap.get( type );
if ( ctor !== undefined ) {
behavior = new ctor().fromJSON( behaviorJSON );
} else {
Logger.warn( 'YUKA.SteeringManager: Unsupported steering behavior type:', type );
continue;
}
}
this.add( behavior );
}
return this;
}
/**
* Registers a custom type for deserialization. When calling {@link SteeringManager#fromJSON}
* the steering manager is able to pick the correct constructor in order to create custom
* steering behavior.
*
* @param {String} type - The name of the behavior type.
* @param {Function} constructor - The constructor function.
* @return {SteeringManager} A reference to this steering manager.
*/
registerType( type, constructor ) {
this._typesMap.set( type, constructor );
return this;
}
/**
* Restores UUIDs with references to GameEntity objects.
*
* @param {Map<String,GameEntity>} entities - Maps game entities to UUIDs.
* @return {SteeringManager} A reference to this steering manager.
*/
resolveReferences( entities ) {
const behaviors = this.behaviors;
for ( let i = 0, l = behaviors.length; i < l; i ++ ) {
const behavior = behaviors[ i ];
behavior.resolveReferences( entities );
}
return this;
}
}
export { SteeringManager };