You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-19 15:06:20 +00:00
337 lines
10 KiB
TypeScript
337 lines
10 KiB
TypeScript
import { IRoomObjectController, IRoomObjectUpdateMessage, IVector3D, RoomObjectVariable } from '@nitrots/api';
|
|
import { Vector3d } from '@nitrots/utils';
|
|
import { ObjectMoveUpdateMessage } from '../../messages';
|
|
import { RoomObjectLogicBase } from './RoomObjectLogicBase';
|
|
|
|
export class MovingObjectLogic extends RoomObjectLogicBase
|
|
{
|
|
public static DEFAULT_UPDATE_INTERVAL: number = 500;
|
|
private static LOCATION_EPSILON: number = 0.01;
|
|
private static TEMP_VECTOR: Vector3d = new Vector3d();
|
|
|
|
private _liftAmount: number;
|
|
|
|
private _location: Vector3d;
|
|
private _locationDelta: Vector3d;
|
|
private _followObject: IRoomObjectController;
|
|
private _followOffset: Vector3d;
|
|
private _lastUpdateTime: number;
|
|
private _changeTime: number;
|
|
private _updateInterval: number;
|
|
private _queuedMoveMessages: ObjectMoveUpdateMessage[];
|
|
|
|
constructor()
|
|
{
|
|
super();
|
|
|
|
this._liftAmount = 0;
|
|
|
|
this._location = new Vector3d();
|
|
this._locationDelta = new Vector3d();
|
|
this._followObject = null;
|
|
this._followOffset = new Vector3d();
|
|
this._lastUpdateTime = 0;
|
|
this._changeTime = 0;
|
|
this._updateInterval = MovingObjectLogic.DEFAULT_UPDATE_INTERVAL;
|
|
this._queuedMoveMessages = [];
|
|
}
|
|
|
|
public dispose(): void
|
|
{
|
|
this._liftAmount = 0;
|
|
this._queuedMoveMessages = [];
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
public update(time: number): void
|
|
{
|
|
super.update(time);
|
|
|
|
const locationOffset = this.getLocationOffset();
|
|
const model = this.object && this.object.model;
|
|
let completedInterpolation = false;
|
|
|
|
if(model)
|
|
{
|
|
if(locationOffset)
|
|
{
|
|
if(this._liftAmount !== locationOffset.z)
|
|
{
|
|
this._liftAmount = locationOffset.z;
|
|
|
|
model.setValue(RoomObjectVariable.FURNITURE_LIFT_AMOUNT, this._liftAmount);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(this._liftAmount !== 0)
|
|
{
|
|
this._liftAmount = 0;
|
|
|
|
model.setValue(RoomObjectVariable.FURNITURE_LIFT_AMOUNT, this._liftAmount);
|
|
}
|
|
}
|
|
}
|
|
|
|
if((this._locationDelta.length > 0) || locationOffset)
|
|
{
|
|
const vector = MovingObjectLogic.TEMP_VECTOR;
|
|
|
|
let difference = (this.time - this._changeTime);
|
|
|
|
if(difference === (this._updateInterval >> 1)) difference++;
|
|
|
|
if(difference > this._updateInterval) difference = this._updateInterval;
|
|
|
|
if(this._followObject)
|
|
{
|
|
vector.assign(this._followObject.getLocation());
|
|
vector.add(this._followOffset);
|
|
}
|
|
else if(this._locationDelta.length > 0)
|
|
{
|
|
vector.assign(this._locationDelta);
|
|
vector.multiply((difference / this._updateInterval));
|
|
vector.add(this._location);
|
|
}
|
|
else
|
|
{
|
|
vector.assign(this._location);
|
|
}
|
|
|
|
if(locationOffset) vector.add(locationOffset);
|
|
|
|
this.object.setLocation(vector);
|
|
|
|
if(difference === this._updateInterval)
|
|
{
|
|
if(this._followObject)
|
|
{
|
|
this._location.assign(this.object.getLocation());
|
|
this._followObject = null;
|
|
this._followOffset.assign(new Vector3d());
|
|
}
|
|
|
|
this._locationDelta.x = 0;
|
|
this._locationDelta.y = 0;
|
|
this._locationDelta.z = 0;
|
|
completedInterpolation = true;
|
|
}
|
|
}
|
|
|
|
this._lastUpdateTime = this.time;
|
|
|
|
if(completedInterpolation) this.processQueuedMoveMessage();
|
|
}
|
|
|
|
public setObject(object: IRoomObjectController): void
|
|
{
|
|
super.setObject(object);
|
|
|
|
if(object) this._location.assign(object.getLocation());
|
|
}
|
|
|
|
public processUpdateMessage(message: IRoomObjectUpdateMessage): void
|
|
{
|
|
if(!message) return;
|
|
|
|
if(message instanceof ObjectMoveUpdateMessage)
|
|
{
|
|
if(this.shouldApplyInstantMoveMessage(message))
|
|
{
|
|
super.processUpdateMessage(message);
|
|
|
|
if(message.location) this._location.assign(message.location);
|
|
|
|
this.resetInterpolationState();
|
|
|
|
return;
|
|
}
|
|
|
|
const requiresCustomMoveHandling = !!message.anchorObject || (message.elapsed > 0);
|
|
|
|
if(requiresCustomMoveHandling)
|
|
{
|
|
if(this.object && message.direction) this.object.setDirection(message.direction);
|
|
|
|
return this.processMoveMessage(message);
|
|
}
|
|
}
|
|
|
|
super.processUpdateMessage(message);
|
|
|
|
if(message.location) this._location.assign(message.location);
|
|
|
|
if(message instanceof ObjectMoveUpdateMessage) return this.processMoveMessage(message);
|
|
}
|
|
|
|
private shouldApplyInstantMoveMessage(message: ObjectMoveUpdateMessage): boolean
|
|
{
|
|
if(!message || !message.location || message.isSlide || !!message.anchorObject || (message.elapsed > 0)) return false;
|
|
|
|
return this.matchesLocation(message.location, message.targetLocation);
|
|
}
|
|
|
|
private processMoveMessage(message: ObjectMoveUpdateMessage): void
|
|
{
|
|
if(!message || !this.object || !message.location) return;
|
|
|
|
if(this.shouldQueueMoveMessage(message))
|
|
{
|
|
this.queueMoveMessage(message);
|
|
|
|
return;
|
|
}
|
|
|
|
const hadActiveInterpolation = this.isInterpolating();
|
|
const duration = ((message.duration > 0) ? message.duration : ObjectMoveUpdateMessage.DEFAULT_DURATION);
|
|
const startLocation = hadActiveInterpolation
|
|
? this.object.getLocation()
|
|
: message.location;
|
|
const elapsed = Math.max(0, Math.min(duration, message.elapsed));
|
|
|
|
this._location.assign(startLocation);
|
|
this.object.setLocation(this._location);
|
|
this._followObject = message.anchorObject;
|
|
|
|
if(message.anchorOffset) this._followOffset.assign(message.anchorOffset);
|
|
else this._followOffset.assign(new Vector3d());
|
|
|
|
this._changeTime = (this._lastUpdateTime - elapsed);
|
|
this.updateInterval = duration;
|
|
|
|
this._locationDelta.assign(message.targetLocation);
|
|
this._locationDelta.subtract(this._location);
|
|
|
|
if(this._followObject)
|
|
{
|
|
const vector = MovingObjectLogic.TEMP_VECTOR;
|
|
|
|
vector.assign(this._followObject.getLocation());
|
|
vector.add(this._followOffset);
|
|
|
|
const locationOffset = this.getLocationOffset();
|
|
|
|
if(locationOffset) vector.add(locationOffset);
|
|
|
|
this.object.setLocation(vector);
|
|
}
|
|
else if(elapsed > 0)
|
|
{
|
|
const vector = MovingObjectLogic.TEMP_VECTOR;
|
|
|
|
vector.assign(this._locationDelta);
|
|
vector.multiply((elapsed / this._updateInterval));
|
|
vector.add(this._location);
|
|
|
|
const locationOffset = this.getLocationOffset();
|
|
|
|
if(locationOffset) vector.add(locationOffset);
|
|
|
|
this.object.setLocation(vector);
|
|
}
|
|
else if(hadActiveInterpolation && message.isSlide)
|
|
{
|
|
this.object.setLocation(this._location);
|
|
}
|
|
}
|
|
|
|
private resetInterpolationState(): void
|
|
{
|
|
this._locationDelta.x = 0;
|
|
this._locationDelta.y = 0;
|
|
this._locationDelta.z = 0;
|
|
this._followObject = null;
|
|
this._followOffset.assign(new Vector3d());
|
|
this._queuedMoveMessages = [];
|
|
this._changeTime = this._lastUpdateTime;
|
|
}
|
|
|
|
private isInterpolating(): boolean
|
|
{
|
|
return (this._locationDelta.length > 0) && ((this.time - this._changeTime) < this._updateInterval);
|
|
}
|
|
|
|
private shouldQueueMoveMessage(message: ObjectMoveUpdateMessage): boolean
|
|
{
|
|
if(!message.isSlide || !!message.anchorObject || !this.isInterpolating() || !message.location || !message.targetLocation) return false;
|
|
|
|
const expectedStartLocation = this.getQueuedMovementTailLocation();
|
|
|
|
if(!expectedStartLocation) return false;
|
|
|
|
return this.matchesLocation(message.location, expectedStartLocation)
|
|
&& !this.matchesLocation(message.targetLocation, expectedStartLocation);
|
|
}
|
|
|
|
private queueMoveMessage(message: ObjectMoveUpdateMessage): void
|
|
{
|
|
this._queuedMoveMessages.push(new ObjectMoveUpdateMessage(
|
|
message.location,
|
|
message.targetLocation,
|
|
message.direction,
|
|
message.isSlide,
|
|
message.duration,
|
|
message.elapsed,
|
|
message.anchorObject,
|
|
message.anchorOffset));
|
|
}
|
|
|
|
private processQueuedMoveMessage(): void
|
|
{
|
|
if(!this._queuedMoveMessages.length) return;
|
|
|
|
const nextMoveMessage = this._queuedMoveMessages.shift();
|
|
|
|
if(!nextMoveMessage) return;
|
|
|
|
this.processMoveMessage(nextMoveMessage);
|
|
}
|
|
|
|
private getQueuedMovementTailLocation(): IVector3D
|
|
{
|
|
if(this._queuedMoveMessages.length)
|
|
{
|
|
const queuedMoveMessage = this._queuedMoveMessages[this._queuedMoveMessages.length - 1];
|
|
|
|
if(queuedMoveMessage?.targetLocation) return queuedMoveMessage.targetLocation;
|
|
}
|
|
|
|
if(this._locationDelta.length <= 0) return null;
|
|
|
|
const targetLocation = new Vector3d();
|
|
|
|
targetLocation.assign(this._location);
|
|
targetLocation.add(this._locationDelta);
|
|
|
|
return targetLocation;
|
|
}
|
|
|
|
private matchesLocation(first: IVector3D, second: IVector3D): boolean
|
|
{
|
|
if(!first || !second) return false;
|
|
|
|
return (Math.abs(first.x - second.x) <= MovingObjectLogic.LOCATION_EPSILON)
|
|
&& (Math.abs(first.y - second.y) <= MovingObjectLogic.LOCATION_EPSILON)
|
|
&& (Math.abs(first.z - second.z) <= MovingObjectLogic.LOCATION_EPSILON);
|
|
}
|
|
|
|
protected getLocationOffset(): IVector3D
|
|
{
|
|
return null;
|
|
}
|
|
|
|
protected get lastUpdateTime(): number
|
|
{
|
|
return this._lastUpdateTime;
|
|
}
|
|
|
|
protected set updateInterval(interval: number)
|
|
{
|
|
if(interval <= 0) interval = 1;
|
|
|
|
this._updateInterval = interval;
|
|
}
|
|
}
|