import { Interior } from "./interior" ; import { MissionElementSimGroup , MissionElementType , MissionElementPathedInterior , MissionElementPath , MisParser , MissionElementTrigger } from "./parsing/mis_parser" ; import { Util } from "./util" ; import { TimeState , PHYSICS_TICK_RATE , Level } from "./level" ; import { MustChangeTrigger } from "./triggers/must_change_trigger" ; import { AudioManager , AudioSource } from "./audio" ; import { Matrix4 } from "./math/matrix4" ; import { Vector3 } from "./math/vector3" ; import { Quaternion } from "./math/quaternion" ; let v1 = new Vector3 ( ) ; let m1 = new Matrix4 ( ) ; interface MarkerData { msToNext : number ; smoothingType : string ; position : Vector3 ; rotation : Quaternion ; } /** Represents a Torque 3D Pathed Interior moving along a set path. */ export class PathedInterior extends Interior { path : MissionElementPath ; markerData : MarkerData [ ] ; simGroup : MissionElementSimGroup ; element : MissionElementPathedInterior ; triggers : MustChangeTrigger [ ] = [ ] ; hasCollision : boolean ; /** Some pathed interiors emit a sound; this is for that. */ soundSource : AudioSource ; soundPosition : Vector3 ; /** The total duration of the path. */ duration : number ; /** The source time */ currentTime = 0 ; /** The destination time */ targetTime = 0 ; /** The start reference point in time of interior interpolation */ changeTime = 0 ; basePosition : Vector3 ; baseOrientation : Quaternion ; baseScale : Vector3 ; prevPosition = new Vector3 ( ) ; currentPosition = new Vector3 ( ) ; allowSpecialMaterials = false ; /** Creates a PathedInterior from a sim group containing it and its path (and possible triggers). */ static async createFromSimGroup ( simGroup , level ) { let interiorElement = simGroup . elements . find ( ( element ) => element . _type === MissionElementType . PathedInterior ) as MissionElementPathedInterior ; let { dif : difFile , path } = await level . mission . getDif ( interiorElement . interiorresource ) ; if ( ! difFile ) return null ; let pathedInterior = new PathedInterior ( difFile , path , level , MisParser . parseNumber ( interiorElement . interiorindex ) ) ; pathedInterior . simGroup = simGroup ; pathedInterior . element = interiorElement ; level . interiors . push ( pathedInterior ) ; await Util . wait ( 10 ) ; await pathedInterior . init ( interiorElement . _id ) ; return pathedInterior ; } async init ( id ) { await super . init ( id ) ; this . basePosition = MisParser . parseVector3 ( this . element . baseposition ) ; this . baseOrientation = MisParser . parseRotation ( this . element . baserotation ) ; this . baseScale = MisParser . parseVector3 ( this . element . basescale ) ; this . hasCollision = this . baseScale . x !== 0 && this . baseScale . y !== 0 && this . baseScale . z !== 0 ; if ( this . baseScale . x === 0 ) this . baseScale . x = 0.0001 ; if ( this . baseScale . y === 0 ) this . baseScale . y = 0.0001 ; if ( this . baseScale . z === 0 ) this . baseScale . z = 0.0001 ; this . body . onBeforeIntegrate = this . onBeforeIntegrate . bind ( this ) ; this . body . orientation . copy ( this . baseOrientation ) ; for ( let i = 0 ; i < this . detailLevel . convexHulls . length ; i ++ ) this . addConvexHull ( i , this . baseScale ) ; this . path = this . simGroup . elements . find ( ( element ) => element . _type === MissionElementType . Path ) as MissionElementPath ; this . markerData = this . path . markers . map ( x => { return { msToNext : MisParser . parseNumber ( x . mstonext ) , smoothingType : x . smoothingtype , position : MisParser . parseVector3 ( x . position ) , rotation : MisParser . parseRotation ( x . rotation ) } ; } ) ; if ( MisParser . parseBoolean ( this . path . isLooping ) ) this . markerData . push ( this . markerData [ 0 ] ) ; this . computeDuration ( ) ; let triggers = this . simGroup . elements . filter ( ( element ) => element . _type === MissionElementType . Trigger ) as MissionElementTrigger [ ] ; for ( let triggerElement of triggers ) { if ( ! triggerElement . targettime ) continue ; let trigger = new MustChangeTrigger ( triggerElement , this ) ; this . triggers . push ( trigger ) ; } if ( this . element . datablock ?. toLowerCase ( ) === 'pathedmovingblock' ) { this . soundPosition = new Vector3 ( ) ; this . soundSource = AudioManager . createAudioSource ( 'movingblockloop.wav' , AudioManager . soundGain , this . soundPosition ) ; this . soundSource . setLoop ( true ) ; await this . soundSource . promise ; } this . reset ( ) ; } async onLevelStart ( ) { this . soundSource ?. play ( ) ; } /** Computes the total duration of the path. */ computeDuration ( ) { let total = 0 ; for ( let i = 0 ; i < this . markerData . length - 1 ; i ++ ) { total += this . markerData [ i ] . msToNext ; } this . duration = total ; } setTargetTime ( now , target ) { let currentInternalTime = this . getInternalTime ( now . currentAttemptTime ) ; this . currentTime = currentInternalTime ; this . targetTime = target ; this . changeTime = now . currentAttemptTime ; } /** Gets the internal time along the path. Is guaranteed to be in [0, duration]. */ getInternalTime ( externalTime ) { if ( this . targetTime < 0 ) { let direction = ( this . targetTime === - 1 ) ? 1 : ( this . targetTime === - 2 ) ? - 1 : 0 ; return Util . adjustedMod ( this . currentTime + ( externalTime - this . changeTime ) * direction , this . duration ) ; } else { let dur = Math . abs ( this . currentTime - this . targetTime ) ; let completion = Util . clamp ( dur ? ( externalTime - this . changeTime ) / dur : 1 , 0 , 1 ) ; return Util . clamp ( Util . lerp ( this . currentTime , this . targetTime , completion ) , 0 , this . duration ) ; } } tick ( time ) { this . body . position . copy ( this . currentPosition ) ; let transform = this . getTransformAtTime ( m1 , this . getInternalTime ( time . currentAttemptTime ) ) ; this . prevPosition . copy ( this . currentPosition ) ; this . currentPosition . setFromMatrixPosition ( transform ) ; let velocity = v1 . copy ( this . currentPosition ) . sub ( this . prevPosition ) . multiplyScalar ( PHYSICS_TICK_RATE ) ; this . body . linearVelocity . copy ( velocity ) ; this . soundPosition ?. copy ( this . currentPosition ) . add ( this . markerData [ 0 ] ?. position ?? new Vector3 ( ) ) ; } onBeforeIntegrate ( ) { this . body . position . copy ( this . currentPosition ) ; this . body . syncShapes ( ) ; } /** Computes the transform of the interior at a point in time along the path. */ getTransformAtTime ( dst , time ) { let m1 = this . markerData [ 0 ] ; let m2 = this . markerData [ 1 ] ; if ( ! m1 ) { dst . compose ( this . basePosition , this . baseOrientation , this . baseScale ) ; return dst ; } let currentEndTime = m1 . msToNext ; let i = 2 ; while ( currentEndTime < time && i < this . markerData . length ) { m1 = m2 ; m2 = this . markerData [ i ++ ] ; currentEndTime += m1 . msToNext ; } if ( ! m2 ) m2 = m1 ; let m1Time = currentEndTime - m1 . msToNext ; let m2Time = currentEndTime ; let duration = m2Time - m1Time ; let position ; let completion = Util . clamp ( duration ? ( time - m1Time ) / duration : 1 , 0 , 1 ) ; if ( m1 . smoothingType === "Accelerate" ) { completion = Math . sin ( completion * Math . PI - ( Math . PI / 2 ) ) * 0.5 + 0.5 ; } else if ( m1 . smoothingType === "Spline" ) { let preStart = ( i - 2 ) - 1 ; let postEnd = ( i - 1 ) + 1 ; if ( postEnd >= this . path . markers . length ) postEnd = 0 ; if ( preStart < 0 ) preStart = this . path . markers . length - 1 ; let p0 = this . markerData [ preStart ] . position ; let p1 = m1 . position ; let p2 = m2 . position ; let p3 = this . markerData [ postEnd ] . position ; position = v1 ; position . x = Util . catmullRom ( completion , p0 . x , p1 . x , p2 . x , p3 . x ) ; position . y = Util . catmullRom ( completion , p0 . y , p1 . y , p2 . y , p3 . y ) ; position . z = Util . catmullRom ( completion , p0 . z , p1 . z , p2 . z , p3 . z ) ; } if ( ! position ) { let p1 = m1 . position ; let p2 = m2 . position ; position = v1 . copy ( p1 ) . lerp ( p2 , completion ) ; } let firstPosition = this . markerData [ 0 ] . position ; position . sub ( firstPosition ) ; position . add ( this . basePosition ) ; dst . compose ( position , this . baseOrientation , this . baseScale ) ; return dst ; } render ( time ) { let transform = this . getTransformAtTime ( m1 , this . getInternalTime ( time . currentAttemptTime ) ) ; this . mesh . transform . copy ( transform ) ; this . mesh . changedTransform ( ) ; this . body . position . setFromMatrixPosition ( transform ) ; this . body . syncShapes ( ) ; } /** Resets the movement state of the pathed interior to the beginning values. */ reset ( ) { this . currentTime = 0 ; this . targetTime = 0 ; this . changeTime = 0 ; if ( this . element . initialposition ) { this . currentTime = MisParser . parseNumber ( this . element . initialposition ) ; } if ( this . element . initialtargetposition ) { this . targetTime = MisParser . parseNumber ( this . element . initialtargetposition ) ; if ( this . targetTime > 0 && this . targetTime < 50 ) this . currentTime = this . duration ; } let transform = this . getTransformAtTime ( m1 , this . getInternalTime ( 0 ) ) ; this . prevPosition . setFromMatrixPosition ( transform ) ; this . currentPosition . setFromMatrixPosition ( transform ) ; this . body . position . copy ( this . currentPosition ) ; this . body . syncShapes ( ) ; this . soundPosition ?. copy ( this . currentPosition ) . add ( this . markerData [ 0 ] ?. position ?? new Vector3 ( ) ) ; } }