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
Improve wired movement rendering and follow sync
This commit is contained in:
@@ -1816,13 +1816,13 @@ export class RoomEngine implements IRoomEngine, IRoomCreator, IRoomEngineService
|
||||
if(roomOwnObject && roomOwnObject.logic && maskUpdate) roomOwnObject.logic.processUpdateMessage(maskUpdate);
|
||||
}
|
||||
|
||||
public rollRoomObjectFloor(roomId: number, objectId: number, location: IVector3D, targetLocation: IVector3D, duration: number = ObjectMoveUpdateMessage.DEFAULT_DURATION, direction: IVector3D = null): void
|
||||
public rollRoomObjectFloor(roomId: number, objectId: number, location: IVector3D, targetLocation: IVector3D, duration: number = ObjectMoveUpdateMessage.DEFAULT_DURATION, direction: IVector3D = null, elapsed: number = 0, anchorObject: IRoomObjectController = null, anchorOffset: IVector3D = null): void
|
||||
{
|
||||
const object = this.getRoomObjectFloor(roomId, objectId);
|
||||
|
||||
if(!object) return;
|
||||
|
||||
object.processUpdateMessage(new ObjectMoveUpdateMessage(location, targetLocation, direction, !!targetLocation, duration));
|
||||
object.processUpdateMessage(new ObjectMoveUpdateMessage(location, targetLocation, direction, !!targetLocation, duration, elapsed, anchorObject, anchorOffset));
|
||||
}
|
||||
|
||||
public updateRoomObjectWallLocation(roomId: number, objectId: number, location: IVector3D): boolean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AvatarGuideStatus, IConnection, IMessageEvent, IRoomCreator, IVector3D, LegacyDataType, ObjectRolling, PetType, RoomObjectType, RoomObjectUserType, RoomObjectVariable } from '@nitrots/api';
|
||||
import { AreaHideMessageEvent, DiceValueMessageEvent, FloorHeightMapEvent, FurnitureAliasesComposer, FurnitureAliasesEvent, FurnitureDataEvent, FurnitureFloorAddEvent, FurnitureFloorDataParser, FurnitureFloorEvent, FurnitureFloorRemoveEvent, FurnitureFloorUpdateEvent, FurnitureWallAddEvent, FurnitureWallDataParser, FurnitureWallEvent, FurnitureWallRemoveEvent, FurnitureWallUpdateEvent, GetCommunication, GetRoomEntryDataMessageComposer, GuideSessionEndedMessageEvent, GuideSessionErrorMessageEvent, GuideSessionStartedMessageEvent, IgnoreResultEvent, ItemDataUpdateMessageEvent, ObjectsDataUpdateEvent, ObjectsRollingEvent, OneWayDoorStatusMessageEvent, PetExperienceEvent, PetFigureUpdateEvent, RoomEntryTileMessageEvent, RoomEntryTileMessageParser, RoomHeightMapEvent, RoomHeightMapUpdateEvent, RoomPaintEvent, RoomReadyMessageEvent, RoomUnitChatEvent, RoomUnitChatShoutEvent, RoomUnitChatWhisperEvent, RoomUnitDanceEvent, RoomUnitEffectEvent, RoomUnitEvent, RoomUnitExpressionEvent, RoomUnitHandItemEvent, RoomUnitIdleEvent, RoomUnitInfoEvent, RoomUnitNumberEvent, RoomUnitRemoveEvent, RoomUnitStatusEvent, RoomUnitStatusMessage, RoomUnitTypingEvent, RoomVisualizationSettingsEvent, UserInfoEvent, WiredMovementsEvent, WiredUserDirectionUpdateData, WiredUserMovementData, YouArePlayingGameEvent } from '@nitrots/communication';
|
||||
import { AvatarGuideStatus, IConnection, IMessageEvent, IRoomCreator, IRoomObjectController, IVector3D, LegacyDataType, ObjectRolling, PetType, RoomObjectType, RoomObjectUserType, RoomObjectVariable } from '@nitrots/api';
|
||||
import { AreaHideMessageEvent, DiceValueMessageEvent, FloorHeightMapEvent, FurnitureAliasesComposer, FurnitureAliasesEvent, FurnitureDataEvent, FurnitureFloorAddEvent, FurnitureFloorDataParser, FurnitureFloorEvent, FurnitureFloorRemoveEvent, FurnitureFloorUpdateEvent, FurnitureWallAddEvent, FurnitureWallDataParser, FurnitureWallEvent, FurnitureWallRemoveEvent, FurnitureWallUpdateEvent, GetCommunication, GetRoomEntryDataMessageComposer, GuideSessionEndedMessageEvent, GuideSessionErrorMessageEvent, GuideSessionStartedMessageEvent, IgnoreResultEvent, ItemDataUpdateMessageEvent, ObjectsDataUpdateEvent, ObjectsRollingEvent, OneWayDoorStatusMessageEvent, PetExperienceEvent, PetFigureUpdateEvent, RoomEntryTileMessageEvent, RoomEntryTileMessageParser, RoomHeightMapEvent, RoomHeightMapUpdateEvent, RoomPaintEvent, RoomReadyMessageEvent, RoomUnitChatEvent, RoomUnitChatShoutEvent, RoomUnitChatWhisperEvent, RoomUnitDanceEvent, RoomUnitEffectEvent, RoomUnitEvent, RoomUnitExpressionEvent, RoomUnitHandItemEvent, RoomUnitIdleEvent, RoomUnitInfoEvent, RoomUnitNumberEvent, RoomUnitRemoveEvent, RoomUnitStatusEvent, RoomUnitStatusMessage, RoomUnitTypingEvent, RoomVisualizationSettingsEvent, UserInfoEvent, WiredFurniMovementData, WiredMovementsEvent, WiredUserDirectionUpdateData, WiredUserMovementData, YouArePlayingGameEvent } from '@nitrots/communication';
|
||||
import { GetRoomSessionManager, GetSessionDataManager } from '@nitrots/session';
|
||||
import { Vector3d } from '@nitrots/utils';
|
||||
import { GetRoomEngine } from './GetRoomEngine';
|
||||
@@ -9,6 +9,10 @@ import { FurnitureStackingHeightMap, LegacyWallGeometry } from './utils';
|
||||
|
||||
export class RoomMessageHandler
|
||||
{
|
||||
private static WIRED_FURNI_ANCHOR_NONE = 0;
|
||||
private static WIRED_FURNI_ANCHOR_USER = 1;
|
||||
private static WIRED_FURNI_ANCHOR_FURNI = 2;
|
||||
private static ROOM_USER_WALK_DURATION = 500;
|
||||
private static WIRED_MOVEMENT_STATUS_GRACE = 250;
|
||||
private static WIRED_MOVEMENT_Z_EPSILON = 0.01;
|
||||
|
||||
@@ -18,6 +22,7 @@ export class RoomMessageHandler
|
||||
private _latestEntryTileEvent: RoomEntryTileMessageEvent = null;
|
||||
private _messageEvents: IMessageEvent[] = [];
|
||||
private _activeWiredUserMovements = new Map<number, { expiresAt: number, targetX: number, targetY: number, targetZ: number }>();
|
||||
private _activeRoomUserWalks = new Map<number, { startedAt: number, targetX: number, targetY: number, targetZ: number, duration: number }>();
|
||||
|
||||
private _currentRoomId: number = 0;
|
||||
private _ownUserId: number = 0;
|
||||
@@ -104,6 +109,7 @@ export class RoomMessageHandler
|
||||
this._roomEngine = null;
|
||||
this._latestEntryTileEvent = null;
|
||||
this._activeWiredUserMovements.clear();
|
||||
this._activeRoomUserWalks.clear();
|
||||
}
|
||||
|
||||
public setRoomId(id: number): void
|
||||
@@ -116,6 +122,7 @@ export class RoomMessageHandler
|
||||
this._currentRoomId = id;
|
||||
this._latestEntryTileEvent = null;
|
||||
this._activeWiredUserMovements.clear();
|
||||
this._activeRoomUserWalks.clear();
|
||||
}
|
||||
|
||||
public clearRoomId(): void
|
||||
@@ -123,6 +130,7 @@ export class RoomMessageHandler
|
||||
this._currentRoomId = 0;
|
||||
this._latestEntryTileEvent = null;
|
||||
this._activeWiredUserMovements.clear();
|
||||
this._activeRoomUserWalks.clear();
|
||||
}
|
||||
|
||||
private onUserInfoEvent(event: UserInfoEvent): void
|
||||
@@ -404,13 +412,18 @@ export class RoomMessageHandler
|
||||
{
|
||||
if(!movement) continue;
|
||||
|
||||
const resolvedMovement = this.resolveAnchoredFurniMovement(movement);
|
||||
|
||||
this._roomEngine.rollRoomObjectFloor(
|
||||
this._currentRoomId,
|
||||
movement.id,
|
||||
movement.location,
|
||||
movement.targetLocation,
|
||||
movement.duration,
|
||||
new Vector3d(movement.rotation));
|
||||
resolvedMovement.location,
|
||||
resolvedMovement.targetLocation,
|
||||
resolvedMovement.duration,
|
||||
new Vector3d(movement.rotation),
|
||||
resolvedMovement.elapsed,
|
||||
resolvedMovement.anchorObject,
|
||||
resolvedMovement.anchorOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,6 +462,90 @@ export class RoomMessageHandler
|
||||
}
|
||||
}
|
||||
|
||||
private resolveAnchoredFurniMovement(movement: WiredFurniMovementData): { location: IVector3D, targetLocation: IVector3D, duration: number, elapsed: number, anchorObject: IRoomObjectController, anchorOffset: IVector3D }
|
||||
{
|
||||
if(!movement || !movement.anchorType || (movement.anchorType === RoomMessageHandler.WIRED_FURNI_ANCHOR_NONE))
|
||||
{
|
||||
return {
|
||||
location: movement.location,
|
||||
targetLocation: movement.targetLocation,
|
||||
duration: movement.duration,
|
||||
elapsed: movement.elapsed,
|
||||
anchorObject: null,
|
||||
anchorOffset: null
|
||||
};
|
||||
}
|
||||
|
||||
const anchorObject = this.getWiredFurniAnchorObject(movement);
|
||||
const activeUserWalk = (movement.anchorType === RoomMessageHandler.WIRED_FURNI_ANCHOR_USER)
|
||||
? this.getActiveRoomUserWalk(movement.anchorId)
|
||||
: null;
|
||||
|
||||
if(activeUserWalk)
|
||||
{
|
||||
const walkElapsed = Math.max(0, Math.min(activeUserWalk.duration, (Date.now() - activeUserWalk.startedAt)));
|
||||
|
||||
return {
|
||||
location: movement.location,
|
||||
targetLocation: new Vector3d(activeUserWalk.targetX, activeUserWalk.targetY, activeUserWalk.targetZ),
|
||||
duration: activeUserWalk.duration,
|
||||
elapsed: walkElapsed,
|
||||
anchorObject: null,
|
||||
anchorOffset: null
|
||||
};
|
||||
}
|
||||
|
||||
if(!anchorObject)
|
||||
{
|
||||
return {
|
||||
location: movement.location,
|
||||
targetLocation: movement.targetLocation,
|
||||
duration: movement.duration,
|
||||
elapsed: movement.elapsed,
|
||||
anchorObject: null,
|
||||
anchorOffset: null
|
||||
};
|
||||
}
|
||||
|
||||
const anchorLocation = anchorObject.getLocation();
|
||||
|
||||
if(!anchorLocation)
|
||||
{
|
||||
return {
|
||||
location: movement.location,
|
||||
targetLocation: movement.targetLocation,
|
||||
duration: movement.duration,
|
||||
elapsed: movement.elapsed,
|
||||
anchorObject: null,
|
||||
anchorOffset: null
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
location: new Vector3d(anchorLocation.x, anchorLocation.y, anchorLocation.z),
|
||||
targetLocation: movement.targetLocation,
|
||||
duration: Math.max(1, movement.duration - Math.max(0, movement.elapsed)),
|
||||
elapsed: 0,
|
||||
anchorObject,
|
||||
anchorOffset: new Vector3d(0, 0, movement.location.z - anchorLocation.z)
|
||||
};
|
||||
}
|
||||
|
||||
private getWiredFurniAnchorObject(movement: WiredFurniMovementData)
|
||||
{
|
||||
if(!movement || !movement.anchorId) return null;
|
||||
|
||||
switch(movement.anchorType)
|
||||
{
|
||||
case RoomMessageHandler.WIRED_FURNI_ANCHOR_USER:
|
||||
return this._roomEngine.getRoomObjectUser(this._currentRoomId, movement.anchorId);
|
||||
case RoomMessageHandler.WIRED_FURNI_ANCHOR_FURNI:
|
||||
return this._roomEngine.getRoomObjectFloor(this._currentRoomId, movement.anchorId);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private applyWiredUserMovement(movement: WiredUserMovementData): void
|
||||
{
|
||||
const isSlide = (movement.movementType === ObjectRolling.SLIDE);
|
||||
@@ -842,6 +939,7 @@ export class RoomMessageHandler
|
||||
{
|
||||
if(!(event instanceof RoomUnitRemoveEvent) || !event.connection || !this._roomEngine) return;
|
||||
|
||||
this._activeRoomUserWalks.delete(event.getParser().unitId);
|
||||
this._roomEngine.removeRoomObjectUser(this._currentRoomId, event.getParser().unitId);
|
||||
|
||||
this.updateGuideMarker();
|
||||
@@ -876,6 +974,8 @@ export class RoomMessageHandler
|
||||
|
||||
if(status.didMove) goal = new Vector3d(status.targetX, status.targetY, status.targetZ);
|
||||
|
||||
this.trackRoomUserWalkStatus(status);
|
||||
|
||||
if(!this.shouldSuppressWiredStatusLocation(status))
|
||||
{
|
||||
this._roomEngine.updateRoomObjectUserLocation(this._currentRoomId, status.id, location, goal, status.canStandUp, height, direction, status.headDirection);
|
||||
@@ -958,6 +1058,56 @@ export class RoomMessageHandler
|
||||
this.updateGuideMarker();
|
||||
}
|
||||
|
||||
private trackRoomUserWalkStatus(status: RoomUnitStatusMessage): void
|
||||
{
|
||||
if(!status) return;
|
||||
|
||||
if(status.didMove)
|
||||
{
|
||||
this._activeRoomUserWalks.set(status.id, {
|
||||
startedAt: Date.now(),
|
||||
targetX: status.targetX,
|
||||
targetY: status.targetY,
|
||||
targetZ: status.targetZ,
|
||||
duration: RoomMessageHandler.ROOM_USER_WALK_DURATION
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const activeWalk = this._activeRoomUserWalks.get(status.id);
|
||||
|
||||
if(activeWalk)
|
||||
{
|
||||
const walkExpiresAt = (activeWalk.startedAt + activeWalk.duration + RoomMessageHandler.WIRED_MOVEMENT_STATUS_GRACE);
|
||||
const reachedTrackedTarget = ((status.x === activeWalk.targetX)
|
||||
&& (status.y === activeWalk.targetY)
|
||||
&& (Math.abs(status.z - activeWalk.targetZ) <= RoomMessageHandler.WIRED_MOVEMENT_Z_EPSILON));
|
||||
|
||||
if(reachedTrackedTarget && (Date.now() < walkExpiresAt)) return;
|
||||
}
|
||||
|
||||
this._activeRoomUserWalks.delete(status.id);
|
||||
}
|
||||
|
||||
private getActiveRoomUserWalk(id: number): { startedAt: number, targetX: number, targetY: number, targetZ: number, duration: number }
|
||||
{
|
||||
const activeWalk = this._activeRoomUserWalks.get(id);
|
||||
|
||||
if(!activeWalk) return null;
|
||||
|
||||
const walkExpiresAt = (activeWalk.startedAt + activeWalk.duration + RoomMessageHandler.WIRED_MOVEMENT_STATUS_GRACE);
|
||||
|
||||
if(Date.now() >= walkExpiresAt)
|
||||
{
|
||||
this._activeRoomUserWalks.delete(id);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return activeWalk;
|
||||
}
|
||||
|
||||
private onRoomUnitChatEvent(event: RoomUnitChatEvent): void
|
||||
{
|
||||
if(!event.connection || !this._roomEngine) return;
|
||||
|
||||
@@ -9,7 +9,7 @@ export class ObjectAvatarUpdateMessage extends ObjectMoveUpdateMessage
|
||||
|
||||
constructor(location: IVector3D, targetLocation: IVector3D, direction: IVector3D, headDirection: number, canStandUp: boolean, baseY: number, isSlide: boolean = false, duration: number = ObjectMoveUpdateMessage.DEFAULT_DURATION)
|
||||
{
|
||||
super(location, targetLocation, direction, isSlide, duration);
|
||||
super(location, targetLocation, direction, isSlide, duration, 0);
|
||||
|
||||
this._headDirection = headDirection;
|
||||
this._canStandUp = canStandUp;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IVector3D } from '@nitrots/api';
|
||||
import { IRoomObjectController, IVector3D } from '@nitrots/api';
|
||||
import { RoomObjectUpdateMessage } from './RoomObjectUpdateMessage';
|
||||
|
||||
export class ObjectMoveUpdateMessage extends RoomObjectUpdateMessage
|
||||
@@ -8,14 +8,20 @@ export class ObjectMoveUpdateMessage extends RoomObjectUpdateMessage
|
||||
private _targetLocation: IVector3D;
|
||||
private _isSlide: boolean;
|
||||
private _duration: number;
|
||||
private _elapsed: number;
|
||||
private _anchorObject: IRoomObjectController;
|
||||
private _anchorOffset: IVector3D;
|
||||
|
||||
constructor(location: IVector3D, targetLocation: IVector3D, direction: IVector3D, isSlide: boolean = false, duration: number = ObjectMoveUpdateMessage.DEFAULT_DURATION)
|
||||
constructor(location: IVector3D, targetLocation: IVector3D, direction: IVector3D, isSlide: boolean = false, duration: number = ObjectMoveUpdateMessage.DEFAULT_DURATION, elapsed: number = 0, anchorObject: IRoomObjectController = null, anchorOffset: IVector3D = null)
|
||||
{
|
||||
super(location, direction);
|
||||
|
||||
this._targetLocation = targetLocation;
|
||||
this._isSlide = isSlide;
|
||||
this._duration = duration;
|
||||
this._elapsed = elapsed;
|
||||
this._anchorObject = anchorObject;
|
||||
this._anchorOffset = anchorOffset;
|
||||
}
|
||||
|
||||
public get targetLocation(): IVector3D
|
||||
@@ -34,4 +40,19 @@ export class ObjectMoveUpdateMessage extends RoomObjectUpdateMessage
|
||||
{
|
||||
return this._duration;
|
||||
}
|
||||
|
||||
public get elapsed(): number
|
||||
{
|
||||
return this._elapsed;
|
||||
}
|
||||
|
||||
public get anchorObject(): IRoomObjectController
|
||||
{
|
||||
return this._anchorObject;
|
||||
}
|
||||
|
||||
public get anchorOffset(): IVector3D
|
||||
{
|
||||
return this._anchorOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ export class MovingObjectLogic extends RoomObjectLogicBase
|
||||
|
||||
private _location: Vector3d;
|
||||
private _locationDelta: Vector3d;
|
||||
private _followObject: IRoomObjectController;
|
||||
private _followOffset: Vector3d;
|
||||
private _lastUpdateTime: number;
|
||||
private _changeTime: number;
|
||||
private _updateInterval: number;
|
||||
@@ -24,6 +26,8 @@ export class MovingObjectLogic extends RoomObjectLogicBase
|
||||
|
||||
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;
|
||||
@@ -75,7 +79,12 @@ export class MovingObjectLogic extends RoomObjectLogicBase
|
||||
|
||||
if(difference > this._updateInterval) difference = this._updateInterval;
|
||||
|
||||
if(this._locationDelta.length > 0)
|
||||
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));
|
||||
@@ -92,6 +101,13 @@ export class MovingObjectLogic extends RoomObjectLogicBase
|
||||
|
||||
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;
|
||||
@@ -112,6 +128,18 @@ export class MovingObjectLogic extends RoomObjectLogicBase
|
||||
{
|
||||
if(!message) return;
|
||||
|
||||
if(message instanceof ObjectMoveUpdateMessage)
|
||||
{
|
||||
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);
|
||||
@@ -123,11 +151,61 @@ export class MovingObjectLogic extends RoomObjectLogicBase
|
||||
{
|
||||
if(!message || !this.object || !message.location) return;
|
||||
|
||||
this._changeTime = this._lastUpdateTime;
|
||||
const hadActiveInterpolation = this.isInterpolating();
|
||||
const startLocation = hadActiveInterpolation
|
||||
? this.object.getLocation()
|
||||
: message.location;
|
||||
const elapsed = Math.max(0, Math.min(message.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 = message.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 isInterpolating(): boolean
|
||||
{
|
||||
return (this._locationDelta.length > 0) && ((this.time - this._changeTime) < this._updateInterval);
|
||||
}
|
||||
|
||||
protected getLocationOffset(): IVector3D
|
||||
|
||||
Reference in New Issue
Block a user