@hology/core 0.0.265 → 0.0.266
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/effects/sequence/sequence-player.js +1 -1
- package/dist/effects/vfx/gpu-material-uniforms.d.ts +23 -0
- package/dist/effects/vfx/gpu-material-uniforms.js +4 -0
- package/dist/effects/vfx/gpu-sprite-runtime.js +1 -1
- package/dist/effects/vfx/vfx-actor.d.ts +6 -0
- package/dist/effects/vfx/vfx-actor.js +1 -1
- package/dist/effects/vfx/vfx-materializer.d.ts +1 -0
- package/dist/effects/vfx/vfx-materializer.js +1 -1
- package/dist/effects/vfx/vfx-param.js +1 -1
- package/dist/effects/vfx/vfx-render-preparation.d.ts +8 -0
- package/dist/effects/vfx/vfx-render-preparation.js +4 -0
- package/dist/gameplay/actors/builtin/components/character/character-animation.d.ts +53 -0
- package/dist/gameplay/actors/builtin/components/character/character-animation.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
- package/dist/gameplay/ai/behavior-tree/behavior-tree.d.ts +8 -1
- package/dist/gameplay/ai/behavior-tree/behavior-tree.js +1 -1
- package/dist/gameplay/ai/behavior-tree/behavior-tree.test.js +1 -1
- package/dist/gameplay/ai/behavior-tree/bt.d.ts +9 -0
- package/dist/gameplay/ai/behavior-tree/bt.js +1 -1
- package/dist/gameplay/ai/behavior-tree/bt.test.js +1 -1
- package/dist/gameplay/animation/foot-contact-baker.d.ts +23 -0
- package/dist/gameplay/animation/foot-contact-baker.js +4 -0
- package/dist/gameplay/animation/foot-contact.d.ts +27 -0
- package/dist/gameplay/animation/foot-contact.js +4 -0
- package/dist/gameplay/animation/locomotion-body.d.ts +62 -0
- package/dist/gameplay/animation/locomotion-body.js +4 -0
- package/dist/gameplay/animation/pose-inertialization.d.ts +46 -0
- package/dist/gameplay/animation/pose-inertialization.js +4 -0
- package/dist/gameplay/animation/root-motion.js +1 -1
- package/dist/gameplay/index.d.ts +4 -0
- package/dist/gameplay/index.js +1 -1
- package/dist/gameplay/initiate.js +1 -1
- package/dist/gameplay/input/input-service.d.ts +4 -0
- package/dist/gameplay/input/input-service.js +1 -1
- package/dist/gameplay/rendering-host.d.ts +2 -1
- package/dist/gameplay/rendering-host.js +1 -1
- package/dist/gameplay/services/physics/physics-system.d.ts +7 -0
- package/dist/gameplay/services/physics/physics-system.js +1 -1
- package/dist/gameplay/services/render.d.ts +2 -0
- package/dist/gameplay/services/render.js +1 -1
- package/dist/rendering/webgpu-experimental-backend.d.ts +1 -1
- package/dist/rendering/webgpu-experimental-backend.js +1 -1
- package/dist/rendering.d.ts +4 -2
- package/dist/rendering.js +1 -1
- package/dist/scene/animation-binary.d.ts +6 -0
- package/dist/scene/animation-binary.js +4 -0
- package/dist/scene/asset-resource-loader.d.ts +3 -1
- package/dist/scene/asset-resource-loader.js +1 -1
- package/dist/scene/landscape/landscape-manager.d.ts +4 -1
- package/dist/scene/landscape/landscape-manager.js +1 -1
- package/dist/scene/landscape/landscape-static-draw-sets.d.ts +18 -0
- package/dist/scene/landscape/landscape-static-draw-sets.js +4 -0
- package/dist/scene/materializer.d.ts +2 -0
- package/dist/scene/materializer.js +1 -1
- package/dist/shader/shader.d.ts +8 -0
- package/dist/test/animation-binary.test.d.ts +2 -0
- package/dist/test/animation-binary.test.js +4 -0
- package/dist/test/character-foot-contact.test.d.ts +2 -0
- package/dist/test/character-foot-contact.test.js +4 -0
- package/dist/test/character-inertialization.test.d.ts +2 -0
- package/dist/test/character-inertialization.test.js +4 -0
- package/dist/test/character-movement-solve.test.d.ts +2 -0
- package/dist/test/character-movement-solve.test.js +4 -0
- package/dist/test/foot-contact.test.d.ts +2 -0
- package/dist/test/foot-contact.test.js +4 -0
- package/dist/test/gpu-material-uniforms.test.d.ts +2 -0
- package/dist/test/gpu-material-uniforms.test.js +4 -0
- package/dist/test/gpu-sprite-pipeline-cache.test.d.ts +2 -0
- package/dist/test/gpu-sprite-pipeline-cache.test.js +4 -0
- package/dist/test/input.test.js +1 -1
- package/dist/test/landscape-static-draw-sets.test.d.ts +2 -0
- package/dist/test/landscape-static-draw-sets.test.js +4 -0
- package/dist/test/locomotion-body.test.d.ts +2 -0
- package/dist/test/locomotion-body.test.js +4 -0
- package/dist/test/physics-ray-object.test.d.ts +2 -0
- package/dist/test/physics-ray-object.test.js +4 -0
- package/dist/test/pose-inertialization.test.d.ts +2 -0
- package/dist/test/pose-inertialization.test.js +4 -0
- package/dist/test/renderer-loading.test.d.ts +2 -0
- package/dist/test/renderer-loading.test.js +4 -0
- package/dist/test/rendering-host.test.js +1 -1
- package/dist/test/sequence-property-parameters.test.js +1 -1
- package/dist/test/sequence-vfx.test.js +1 -1
- package/dist/test/vfx-render-preparation.test.d.ts +2 -0
- package/dist/test/vfx-render-preparation.test.js +4 -0
- package/dist/utils/structs.d.ts +4 -0
- package/dist/utils/structs.js +4 -0
- package/package.json +4 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as t,__metadata as e}from"tslib";import i from"@dimforge/rapier3d-simd-compat";import{takeUntil as o}from"rxjs";import*as s from"three";import{ArrowHelper as n,MathUtils as r,Quaternion as a,Vector3 as h}from"three";import{RootMotionClip as l}from"../../../../animation/root-motion.js";import{inject as c}from"../../../../inject.js";import{ActionInput as d,AxisInput as u,RotationInput as p}from"../../../../input/index.js";import{NetRole as m}from"../../../../net/service/net-actor-role.js";import{RunOnClient as y,RunOnNotOwner as v,RunOnServer as g}from"../../../../net/service/rpc-decorator.js";import{NetService as S}from"../../../../net/service/net-service.js";import{PhysicsSystem as M,RayTestResult as f}from"../../../../services/physics/physics-system.js";import{CapsuleCollisionShape as x}from"../../../../../scene/collision/collision-shape.js";import{Parameter as T}from"../../../../../shader/parameter.js";import{PhysicsBodyType as C}from"../../../../services/physics/physics-system.js";import{ActorComponent as w,Component as R}from"../../../component.js";import{CharacterMovementMode as A}from"./modes.js";import{calculateRootMotionTargetApproachCorrection as D,constrainRootMotionTargetApproach as q,warpRootMotionTargetApproach as b}from"./root-motion-constraint.js";import{DEFAULT_MAX_CLIENT_TIME_AHEAD as z,DEFAULT_LARGE_CORRECTION_THRESHOLD as k,DEFAULT_MAX_COMBINED_MOVE_DELTA_TIME as P,DEFAULT_MAX_MOVE_DELTA_TIME as I,DEFAULT_MAX_MOVES_PER_PACKET as O,DEFAULT_MAX_REPLAYED_MOVES as j,DEFAULT_MAX_SERVER_MOVES_PER_TICK as G,DEFAULT_MAX_SAVED_MOVES as Y,DEFAULT_MAX_SERVER_MOVE_DELTA_TIME_SCALAR as N,DEFAULT_MAX_SERVER_MOVE_TIME_BUDGET as F,DEFAULT_MAX_SUBSTEP_DELTA_TIME as V,DEFAULT_MOVE_SEND_INTERVAL as H,DEFAULT_SIMULATED_INTERPOLATION_DELAY as U,DEFAULT_SIMULATED_STATE_INTERVAL as X,DEFAULT_SMALL_CORRECTION_TOLERANCE as E,DEFAULT_TELEPORT_SNAP_THRESHOLD as L,MOVE_FLAG_CROUCH as B,MOVE_FLAG_JUMP as Z,MOVE_FLAG_ROOT_MOTION as W,MOVE_FLAG_SLIDE_START as $,MOVE_FLAG_SPRINT as Q,MOVE_FLAG_SPRINT_START as J,SNAPSHOT_FLAG_CROUCH_SUPPRESSED as K,SNAPSHOT_FLAG_CROUCHING as _,NetCharacterProxySmoother as tt,NetCharacterSavedMoveBuffer as et,accrueNetCharacterServerMoveTime as it,createNetCharacterMovementSnapshot as ot,createNetCharacterServerMoveTimingState as st,consumeNetCharacterServerMoveTime as nt,decodeNetCharacterMoveBatch as rt,decodeNetCharacterOwnerAck as at,decodeNetCharacterOwnerCorrection as ht,decodeNetCharacterSimulatedState as lt,encodeNetCharacterMoveBatch as ct,encodeNetCharacterOwnerAck as dt,encodeNetCharacterOwnerCorrection as ut,encodeNetCharacterSimulatedState as pt,validateNetCharacterMove as mt}from"./net-character-movement-protocol.js";import{NetMode as yt}from"../../../../net/net-session.js";import{setSlideJumpVelocity as vt,shouldContinueSlide as gt,shouldStartSlide as St,updateSlideVelocity as Mt,updateSlideVelocityFromDisplacement as ft}from"./character-slide.js";export*from"./net-character-movement-protocol.js";const xt=131070,Tt=new h(0,1,0),Ct=new h,wt=new h,Rt=new h,At=new h,Dt=new h,qt=new h,bt=new h,zt=new h,kt=new h,Pt=new h,It=new h,Ot=new h,jt=new h,Gt=new h,Yt=new h(0,1,0),Nt=new h(0,-.05,0),Ft=new h,Vt=new a,Ht=new s.Euler,Ut=new h,Xt=new h,Et=new h,Lt=new h,Bt=new i.CharacterCollision;let Zt=class extends w{get autoStepMinWidth(){return this.cc.autostepMinWidth()}set autoStepMinWidth(t){this.cc.enableAutostep(this.cc.autostepMaxHeight(),t,this.cc.autostepIncludesDynamicBodies())}get autoStepDynamicObjects(){return this.cc.autostepIncludesDynamicBodies()}set autoStepDynamicObjects(t){this.cc.enableAutostep(this.cc.autostepMaxHeight(),this.cc.autostepMinWidth(),t)}get autoStepMaxHeight(){return this.cc.autostepMaxHeight()}set autoStepMaxHeight(t){this.cc.enableAutostep(t,this.cc.autostepMinWidth(),this.cc.autostepIncludesDynamicBodies())}get snapToGround(){return this.cc.snapToGroundDistance()}set snapToGround(t){this.cc.enableSnapToGround(t)}set offset(t){this.cc.setOffset(t)}get offset(){return this.cc.offset()}set normalNudgeFactor(t){this.cc.setNormalNudgeFactor(t)}get normalNudgeFactor(){return this.cc.normalNudgeFactor()}teleport(t,e=this.actor.rotation.y){this.state.position.copy(t),this.state.velocity.set(0,0,0),this.state.yaw=e,this.actor.position.copy(t),this.actor.rotation.y=e,this.rotationInput.rotation.y=e,this.physicsSystem.setPosition(this.actor,t),this.physicsSystem.setRotation(this.actor,this.actor.quaternion),this.physicsSystem.flushModifiedBodyPositionsToColliders()}constructor(){super(),this.isDefaultCharacterMovementComponent=!0,this.directionInput=new u,this.jumpInput=new d,this.sprintInput=new d,this.crouchInput=new d,this.rotationInput=new p,this.horizontalSpeed=0,this.maxSpeed=8,this.maxSpeedBackwards=8,this.maxSpeedSprint=12,this.maxSpeedCrouch=4,this.slideEnabled=!1,this.slideMinStartSpeed=8,this.slideMinExitSpeed=4,this.slideDeceleration=3,this.slideGravityScale=1,this.slideSteeringRate=2,this.sprintStartBoostSpeed=0,this.sprintStartBoostDamping=6,this.jumpVelocity=7,this.fallingMovementControl=.5,this.fallingReorientation=!1,this.groundAccelerationRate=4,this.gravityOverride=null,this.colliderHeight=2,this.colliderRadius=.5,this.crouchColliderHeight=1,this.crouchColliderRadius=null,this.jumpInAir=!1,this.mass=50,this.allowSliding=!0,this.minSlopeSlideAngle=r.degToRad(70),this.maxSlopeClimbAngle=r.degToRad(70),this.applyImpulsesToDynamicBodies=!0,this.normalizedDirection=!0,this.characterCollision=!1,this.allowRootMotionJumpCancel=!1,this.enabled=!0,this.rotateToMovementDirection=!1,this.smoothRotation=!0,this.rotationSpeed=40,this.maxRotationSpeed=100,this.impulseDamping=2,this.moveSendInterval=H,this.simulatedStateInterval=X,this.maxSavedMoves=Y,this.maxReplayedMoves=j,this.maxServerMovesPerTick=G,this.maxMovesPerPacket=O,this.maxMoveDeltaTime=I,this.maxCombinedMoveDeltaTime=P,this.maxServerMoveDeltaTimeScalar=N,this.maxClientTimeAhead=z,this.maxServerMoveTimeBudget=F,this.maxSubstepDeltaTime=V,this.fallGraceTime=.1,this.correctionToleranceSq=E*E,this.largeCorrectionThreshold=k,this.teleportSnapThreshold=L,this.listenServerSmoothing=!0,this.listenServerSmoothLocationTime=.04,this.listenServerMaxSmoothUpdateDistance=1,this.listenServerNoSmoothUpdateDistance=2.5,this.debugCorrections=!0,this.suppressUncrouchLift=!1,this.velocity=new h,this.visualSmoothingOffset=new h,this.slideDirection=new h,this.slideSurfaceNormal=new h(0,1,0),this.mode=A.walking,this.isSprinting=!1,this.isCrouching=!1,this.isSliding=!1,this.pressedJump=!1,this.rayTestResult=new f,this.groundRayTestOptions={debug:!1,excludeTriggers:!0,excludeActor:void 0,resolveActor:!1},this.physicsSystem=c(M),this.netService=c(S),this.state={sequence:0,position:new h,velocity:new h,yaw:0,lookYaw:0,mode:A.walking,grounded:!1,currentSpeed:0,fallGraceTime:0,prevInputDirection:new h,prevRotateToMovementDirection:!1},this.movementModes=new Map,this.savedMoves=new et(Y),this.serverMoveTiming=st(),this.serverMoveQueue=[],this.inputMoveScratch={sequence:0,clientTime:0,dt:0,inputX:0,inputY:0,yaw:0,flags:0,clientX:0,clientY:0,clientZ:0,rootMotionX:0,rootMotionY:0,rootMotionZ:0},this.authoritativeMoveScratch={sequence:0,clientTime:0,dt:0,inputX:0,inputY:0,yaw:0,flags:0,clientX:0,clientY:0,clientZ:0,rootMotionX:0,rootMotionY:0,rootMotionZ:0},this.movementModeContext={component:this,state:this.state,move:this.inputMoveScratch,deltaTime:0,rotatedDirection:wt,platformVelocity:At,outVelocity:Rt,rootMotionActive:!1,startedSprinting:!1},this.proxySmoother=new tt(U,L),this.proxySample=ot(),this.pendingSnapshot=ot(),this.impulse=new h,this.prevRootMotionPos=new h,this.rootMotionOptions={},this.rootMotionTargetConstraints=new Set,this.resetRootMotion=!1,this.nextMoveSequence=0,this.clientMoveTime=0,this.lastSentClientMoveTime=0,this.moveSendAccumulator=0,this.simulatedStateAccumulator=0,this.proxyTime=0,this.lastServerProcessedSequence=0,this.lastServerReceivedSequence=0,this.lastAutonomousMoveFlags=0,this.previousSprintInput=!1,this.previousCrouchInput=!1,this.crouchSuppressedUntilRelease=!1,this.slideOnLandingRequested=!1,this.initialized=!1,this.cc=this.physicsSystem.getCharacterController(.1),this.cc.enableSnapToGround(.1),this.cc.enableAutostep(0,.1,!1),this.registerMovementMode(Wt),this.registerMovementMode($t),this.registerMovementMode({mode:A.sliding,update:t=>this.updateSlidingMode(t)})}onInit(){this.groundRayTestOptions.excludeActor=this.actor,this.configureCharacterController(),this.physicsSystem.addActor(this.actor,[this.createCollisionShape()],{mass:0,type:C.kinematic,continousCollisionDetection:!1,friction:0,restitution:.5,ignoreForNavMesh:!0}),this.rotationInput.rotation.copy(this.actor.rotation),this.initializeStateFromActor(),this.initialized=!0,this.physicsSystem.beforeStep.pipe(o(this.disposed)).subscribe(t=>this.tick(t))}onLateUpdate(t){this.updateVisualSmoothing(t)}registerMovementMode(t){this.movementModes.set(t.mode,t)}applyImpulse(t){this.impulse.add(t)}getEffectiveGravity(){return this.gravityOverride??this.physicsSystem.getGravity().y}get isGrounded(){return this.rayTestResult.hasHit||this.cc.computedGrounded()}setRootMotionAction(t,e={}){const i=t?.getClip();if(i instanceof l){this.rootMotionAction=t,this.rootMotionOptions=e,this.resetRootMotion=!0;const o=[];this.rootMotionInterpolant=i.motionTrack.InterpolantFactoryMethodSmooth(o);const n=s.PropertyBinding.parseTrackName(i.motionTrack.name);this.rootMotionRootBone=this.actor.object.getObjectByName(n.nodeName)}}addRootMotionTargetConstraint(t,e,i=0){const o={targetPosition:t,minimumHorizontalDistance:Math.max(0,e),maximumAddedHorizontalDistance:Math.max(0,i),initialHorizontalDistance:Math.hypot(t.x-this.actor.position.x,t.z-this.actor.position.z),addedHorizontalDistance:0,requestedProgress:0};this.rootMotionTargetConstraints.add(o);let s=!1;return{setProgress:t=>{o.requestedProgress=Math.max(o.requestedProgress,Math.min(1,Math.max(0,t)))},dispose:()=>{s||(s=!0,this.rootMotionTargetConstraints.delete(o))}}}serverMove(t){const e=rt(t);if(e.ok)for(const t of e.value.moves)mt(t,this.lastServerReceivedSequence,this.maxMoveDeltaTime)&&(this.lastServerReceivedSequence=t.sequence,this.serverMoveQueue.push(t))}clientMoveAck(t){const e=at(t);e.ok&&this.actor.netRole===m.autonomousProxy&&(this.savedMoves.ack(e.value.sequence),this.sprintPolicy?.ack?.(e.value.sequence,!1))}clientMoveCorrection(t){const e=ht(t);e.ok&&this.actor.netRole===m.autonomousProxy&&(this.debugCorrections&&(null==this.correctionDebug&&(this.correctionDebug=new _t(ee(this.actor.object))),this.correctionDebug.update(this.state.position,e.value.snapshot)),this.applyCorrection(e.value.snapshot))}simulatedState(t){const e=lt(t);e.ok&&this.actor.netRole===m.simulatedProxy&&this.proxySmoother.push(e.value,this.proxyTime)}tick(t){if(!this.initialized)return;t=Math.min(.1,t);const e=this.actor.netRole,i=this.netService.mode;e!==m.simulatedProxy?this.enabled&&(e!==m.autonomousProxy?this.isRemoteControlledAuthority(i,e)?this.tickRemoteAuthority(t):this.tickAuthority(t,i):this.tickAutonomousProxy(t)):this.tickSimulatedProxy(t)}tickAutonomousProxy(t){const e=this.state.mode,i=this.lastAutonomousMoveFlags,o=this.createMoveFromInput(t,++this.nextMoveSequence,{quantizeClientTime:!0});this.performMove(o,ie),this.resetToggleCrouchAfterSlide(e);const s=this.state.mode!==e||o.flags!==i;this.lastAutonomousMoveFlags=o.flags,this.savedMoves.capacity=this.maxSavedMoves,this.savedMoves.maxCombinedDeltaTime=Math.min(this.maxCombinedMoveDeltaTime,this.maxMoveDeltaTime),this.savedMoves.push(o),this.sendSavedMoves(t,s)}tickAuthority(t,e){const i=this.state.mode,o=this.createMoveFromInput(t,++this.nextMoveSequence);this.performMove(o,ie),this.resetToggleCrouchAfterSlide(i),e!==yt.none&&this.sendSimulatedState(t,!1)}resetToggleCrouchAfterSlide(t){t===A.sliding&&this.state.mode!==A.sliding&&"toggle"===this.crouchInput.mode&&this.crouchInput.activated&&this.crouchInput.setActivated(!1)}tickRemoteAuthority(t){it(this.serverMoveTiming,t,this.maxServerMoveTimeBudget);let e=!1;const i=Number.isFinite(this.maxServerMovesPerTick)?Math.max(1,Math.floor(this.maxServerMovesPerTick)):this.serverMoveQueue.length,o=Math.min(this.serverMoveQueue.length,i),s=Et.copy(this.actor.position);let n=!1;for(let t=0;t<o;t++){const t=this.serverMoveQueue.shift();if(null==t)break;if(!mt(t,this.lastServerProcessedSequence,this.maxMoveDeltaTime)){console.log("Net: Invalid move",t),this.sendOwnerCorrection(!0),e=!0;continue}const i=nt(this.serverMoveTiming,t,{maxMoveDeltaTime:this.maxMoveDeltaTime,maxServerMoveDeltaTimeScalar:this.maxServerMoveDeltaTimeScalar,maxClientTimeAhead:this.maxClientTimeAhead});if(i<0){this.sendOwnerCorrection(!0),e=!0;continue}const o=Jt(t,this.authoritativeMoveScratch);o.dt=i,o.rootMotionX=0,o.rootMotionY=0,o.rootMotionZ=0,o.flags&=~W,this.performMove(o,{immediate:!0,savedRootMotion:!1}),n=!0,this.lastServerProcessedSequence=o.sequence;const s=Kt(this.state,t);if(s>this.correctionToleranceSq){console.log(`Net server: Correction error: ${Math.sqrt(s)}`);const o=this.state.position.x-t.clientX,n=this.state.position.y-t.clientY,r=this.state.position.z-t.clientZ;console.log(`diff x: ${o}, y: ${n}, z: ${r}`),console.log({sequence:t.sequence,moveDt:t.dt,serverMoveDeltaTime:i,flags:t.flags,mode:this.state.mode,grounded:this.state.grounded,fallGraceTime:this.state.fallGraceTime}),this.sendOwnerCorrection(s>this.largeCorrectionThreshold*this.largeCorrectionThreshold),e=!0}}0===this.lastServerProcessedSequence||e||(this.sprintPolicy?.ack?.(this.lastServerProcessedSequence,!1),this.clientMoveAck(dt(this.lastServerProcessedSequence))),n&&this.accumulateListenServerVisualSmoothing(s,this.actor.position),this.sendSimulatedState(t,!1)}tickSimulatedProxy(t){this.proxyTime+=t,this.proxySmoother.sample(this.proxyTime,this.proxySample)&&this.applySnapshot(this.proxySample,!0)}accumulateListenServerVisualSmoothing(t,e){if(!this.shouldSmoothListenServerRemoteAuthority())return void this.visualSmoothingOffset.set(0,0,0);Lt.subVectors(t,e);const i=Lt.lengthSq();i>1e-8&&(i>this.listenServerNoSmoothUpdateDistance*this.listenServerNoSmoothUpdateDistance?this.visualSmoothingOffset.set(0,0,0):(i>this.listenServerMaxSmoothUpdateDistance*this.listenServerMaxSmoothUpdateDistance&&Lt.setLength(this.listenServerMaxSmoothUpdateDistance),this.visualSmoothingOffset.add(Lt)))}updateVisualSmoothing(t){if(this.visualSmoothingOffset.lengthSq()<=1e-8)return void this.visualSmoothingOffset.set(0,0,0);if(!this.shouldSmoothListenServerRemoteAuthority())return void this.visualSmoothingOffset.set(0,0,0);const e=Math.max(.001,this.listenServerSmoothLocationTime);t<e?this.visualSmoothingOffset.multiplyScalar(1-t/e):this.visualSmoothingOffset.set(0,0,0),this.visualSmoothingOffset.lengthSq()<=1e-4&&this.visualSmoothingOffset.set(0,0,0)}shouldSmoothListenServerRemoteAuthority(){return this.listenServerSmoothing&&this.actor.netRole===m.authority&&this.netService.isServer&&!this.netService.isDedicatedServer&&this.isRemoteControlledAuthority()}createMoveFromInput(t,e,i={}){const o=r.clamp(t,.001,this.maxMoveDeltaTime);this.clientMoveTime+=o;let s=this.clientMoveTime,n=o;!0===i.quantizeClientTime&&(s=Math.max(this.lastSentClientMoveTime+.001,Math.round(1e3*this.clientMoveTime)/1e3),n=r.clamp(s-this.lastSentClientMoveTime,.001,this.maxMoveDeltaTime),this.lastSentClientMoveTime=s),this.sampleRootMotion(bt);const a=this.crouchInput.activated,h=a&&!this.previousCrouchInput;this.previousCrouchInput=a;const l=!a&&this.sprintInput.activated,c=l&&!this.previousSprintInput;this.previousSprintInput=l;const d=(this.jumpInput.activated?Z:0)|(l?Q:0)|(a?B:0)|(h&&this.slideEnabled?$:0)|(c?J:0)|(bt.lengthSq()>0?W:0),u=function(t,e){e.set(t.x,0,t.y),e.lengthSq()>1&&e.normalize();return e}(this.directionInput.vector,Ut),p=this.inputMoveScratch;return p.sequence=e>>>0,p.clientTime=s,p.dt=n,p.inputX=u.x,p.inputY=u.z,p.yaw=this.rotationInput.rotation.y,p.flags=d,p.clientX=this.state.position.x,p.clientY=this.state.position.y,p.clientZ=this.state.position.z,p.rootMotionX=bt.x,p.rootMotionY=bt.y,p.rootMotionZ=bt.z,p}performMove(t,e){const i=r.clamp(t.dt,.001,this.maxMoveDeltaTime),o=Math.max(1,Math.ceil(i/this.maxSubstepDeltaTime)),s=0!==(t.flags&Q)&&0===(t.flags&B);this.sprintPolicy?.update?.(i,t.sequence,s),Ot.set(e.savedRootMotion?t.rootMotionX:0,e.savedRootMotion?t.rootMotionY:0,e.savedRootMotion?t.rootMotionZ:0),e.savedRootMotion||this.sampleRootMotion(Ot);let n=i;const a=i/o;for(let s=0;s<o;s++){const o=i>0?a/i:1;this.performSubstep(t,Math.min(a,n),Ot,o,e.immediate,0===s),n-=a}t.clientX=this.state.position.x,t.clientY=this.state.position.y,t.clientZ=this.state.position.z,t.flags=Ot.lengthSq()>0?t.flags|W:t.flags&~W,this.state.sequence=t.sequence,this.syncPublicState()}performSubstep(t,e,i,o,s,n){if(e<.001)return;this.checkGrounded(this.state.position),this.state.grounded=this.isGrounded;const r=null!=this.rootMotionAction&&this.rootMotionAction.enabled,a=i.lengthSq()>0,h=!r&&0!==(t.flags&Z),l=r||a;this.updateCrouchState(t,n,l,h),this.pressedJump=h,this.isSprinting=!1,this.updateRotation(t,e,r,a),this.resolvePlatformVelocity(At);const c=this.movementModeContext,d=this.movementModes.get(this.state.mode);if(null!=d){c.move=t,c.deltaTime=e,c.rootMotionActive=l,c.startedSprinting=n&&!l&&0===(t.flags&B)&&0!==(t.flags&J);const i=d.update(c);"number"==typeof i&&(this.state.mode=i)}else Rt.set(0,0,0);if(a){zt.copy(i).multiplyScalar(o).applyQuaternion(this.actor.quaternion),kt.copy(zt).divideScalar(e),this.state.mode===A.walking?(Rt.x=kt.x+At.x,Rt.z=kt.z+At.z):(Rt.x=kt.x,Rt.z=kt.z);const t=Math.sqrt(kt.x*kt.x+kt.z*kt.z);t>.4&&(this.state.currentSpeed=t)}if(this.applyImpulseToVelocity(Rt,e),h&&this.isSprinting){const t=Math.max(0,this.maxSpeedSprint),e=Qt(Rt);if(e>t){const i=t/e;Rt.x*=i,Rt.z*=i,this.state.velocity.x*=i,this.state.velocity.z*=i}}this.state.mode===A.walking&&this.state.velocity.copy(Rt),this.applyMovement(Rt,e,s),this.state.mode===A.sliding&&(ft(this.state.velocity,qt,At,this.slideSurfaceNormal,e),this.state.currentSpeed=Qt(this.state.velocity)),this.checkGrounded(this.state.position),this.state.grounded=this.isGrounded,this.updateModeAfterMove(Rt,e),this.state.mode===A.sliding&&this.state.velocity.length()<Math.max(0,this.slideMinExitSpeed)&&(this.state.mode=A.walking)}updateRotation(t,e,i,o){Ct.set(-t.inputX,0,t.inputY),Ct.lengthSq()>1&&Ct.normalize();const s=this.state.prevRotateToMovementDirection!==this.rotateToMovementDirection;let n=r.euclideanModulo(t.yaw-this.state.lookYaw+Math.PI,2*Math.PI)-Math.PI;if(s&&(this.actor.object.quaternion.setFromEuler(Ht.set(0,t.yaw,0)),n=0,this.state.lookYaw=t.yaw,this.state.prevRotateToMovementDirection=this.rotateToMovementDirection),wt.set(0,0,0),i)this.state.lookYaw=t.yaw;else if(this.rotateToMovementDirection){if(Ct.lengthSq()>0&&this.state.mode!==A.falling){const i=Ht.setFromQuaternion(this.actor.object.quaternion,"YXZ").y,o=Math.atan2(Ct.x,Ct.z),s=.99*t.yaw+o;if(this.smoothRotation){const t=r.euclideanModulo(s-i+Math.PI,2*Math.PI)-Math.PI;let o=r.clamp(t*this.rotationSpeed*e,-this.maxRotationSpeed*e,this.maxRotationSpeed*e);Math.abs(o)>Math.abs(t)&&(o=t),this.actor.object.quaternion.setFromEuler(Ht.set(0,i+o,0))}else this.actor.object.quaternion.setFromEuler(Ht.set(0,s,0));this.state.prevInputDirection.lerp(Ct,e*this.rotationSpeed),this.state.lookYaw+=n,wt.copy(this.actor.object.getWorldDirection(Ut).normalize())}}else o||(this.state.lookYaw=t.yaw,this.actor.object.quaternion.setFromEuler(Ht.set(0,t.yaw,0)),Ct.lengthSq()>0&&wt.copy(Ct).applyAxisAngle(Tt,t.yaw).normalize())}applyMovement(t,e,i){if(t.lengthSq()>0||!this.state.grounded){if(Dt.copy(t).multiplyScalar(e),this.state.grounded&&((o=this.state.mode)===A.walking||o===A.sliding)&&(this.state.mode===A.walking&&this.rayTestResult.distance<=this.cc.offset()&&(Dt.y=Math.max(0,At.y*e)),this.physicsSystem.getActorComputedMovement(this.actor,this.cc,Dt,this.getCollisionGroup()),this.cc.computedCollision(0,Bt),null!=Bt.normal1)){const t=Ut.copy(Bt.normal1);Math.acos(r.clamp(t.dot(Tt),-1,1))>this.cc.maxSlopeClimbAngle()&&(Dt.y=.016*this.getEffectiveGravity()*.5)}qt.copy(this.physicsSystem.getActorComputedMovement(this.actor,this.cc,Dt,this.getCollisionGroup()))}else qt.set(0,0,0);var o;this.suppressUncrouchLift&&(this.suppressUncrouchLift=!1,this.state.grounded&&this.state.mode===A.walking&&Dt.y<=1e-6&&qt.y>0&&(qt.y=0)),this.state.position.add(qt),i?(this.actor.position.copy(this.state.position),this.physicsSystem.updateActorTransform(this.actor),this.physicsSystem.flushModifiedBodyPositionsToColliders()):this.physicsSystem.setNextKinematicPosition(this.actor,this.state.position)}updateModeAfterMove(t,e){const i=function(t){if(0===t.numComputedCollisions())return!1;const e=t.computedCollision(0);Ft.set(e.normal2.x,e.normal2.y,e.normal2.z);const i=Ft.angleTo(Tt);Ft.set(e.normal1.x,e.normal1.y,e.normal1.z);const o=Ft.angleTo(Tt);if(i<100)return!1;return o>t.minSlopeSlideAngle()}(this.cc);!this.state.grounded||i?this.state.mode!==A.falling&&(this.state.fallGraceTime+=e,this.state.fallGraceTime>this.fallGraceTime+1e-6&&(this.state.mode=A.falling,this.state.velocity.copy(t))):this.state.velocity.y<=0&&(this.state.mode===A.falling&&(this.state.mode=A.walking),this.state.mode!==A.sliding&&(this.state.velocity.y=0),this.state.fallGraceTime=0)}applyImpulseToVelocity(t,e){if(this.impulse.lengthSq()<=.1)return void this.impulse.set(0,0,0);const i=Math.min(1,this.impulse.length()/5);t.x=r.lerp(t.x,this.impulse.x,i),t.z=r.lerp(t.z,this.impulse.z,i),t.y+=this.impulse.y;const o=Math.exp(-this.impulseDamping*e);this.impulse.x*=o,this.impulse.y>0?this.impulse.y+=e*this.getEffectiveGravity():this.impulse.y*=o,this.impulse.z*=o}sendSavedMoves(t,e=!1){if(this.moveSendAccumulator+=t,!e&&this.moveSendAccumulator<this.moveSendInterval)return;this.moveSendAccumulator=0;const i=this.savedMoves.getMovesForSend(this.maxMovesPerPacket);0!==i.length&&this.serverMove(ct(i))}sendOwnerCorrection(t){const e=this.toSnapshot(this.pendingSnapshot);t&&(e.horizontalSpeed=this.horizontalSpeed),this.sprintPolicy?.ack?.(e.sequence,!0),this.clientMoveCorrection(ut(e))}sendSimulatedState(t,e){this.simulatedStateAccumulator+=t,!e&&this.simulatedStateAccumulator<this.simulatedStateInterval||(this.simulatedStateAccumulator=0,this.simulatedState(pt(this.toSnapshot(this.pendingSnapshot))))}applyCorrection(t){this.sprintPolicy?.restore?.(t.sequence),this.savedMoves.ack(t.sequence),this.sprintPolicy?.ack?.(t.sequence,!0),this.applySnapshot(t,!0),this.savedMoves.replayAfter(t.sequence,t=>(this.performMove(t,{immediate:!0,savedRootMotion:!0}),null),this.maxReplayedMoves)}applySnapshot(t,e){this.state.sequence=t.sequence,this.state.position.set(t.x,t.y,t.z),this.state.velocity.set(t.velocityX,t.velocityY,t.velocityZ),this.state.yaw=t.yaw,this.state.lookYaw=t.yaw,this.state.mode=t.mode,this.state.currentSpeed=t.horizontalSpeed,this.state.fallGraceTime=0,this.actor.position.copy(this.state.position),this.actor.object.quaternion.setFromEuler(Ht.set(0,t.yaw,0)),this.crouchSuppressedUntilRelease=0!==(t.flags&K),this.setCrouching(0!==(t.flags&_)||t.mode===A.sliding),e&&(this.physicsSystem.updateActorTransform(this.actor),this.physicsSystem.flushModifiedBodyPositionsToColliders()),this.checkGrounded(this.state.position),this.state.grounded=this.isGrounded,this.syncPublicState()}toSnapshot(t){return t.sequence=this.state.sequence,t.x=this.state.position.x,t.y=this.state.position.y,t.z=this.state.position.z,t.velocityX=this.state.velocity.x,t.velocityY=this.state.velocity.y,t.velocityZ=this.state.velocity.z,t.yaw=Ht.setFromQuaternion(this.actor.object.quaternion,"YXZ").y,t.mode=this.state.mode,t.flags=(this.isCrouching?_:0)|(this.crouchSuppressedUntilRelease?K:0),t.horizontalSpeed=this.state.currentSpeed,t}syncPublicState(){this.velocity.copy(this.state.velocity),this.mode=this.state.mode,this.horizontalSpeed=this.state.currentSpeed,this.isSliding=this.state.mode===A.sliding,this.isSliding?(this.resolveGroundNormal(this.slideSurfaceNormal),this.slideDirection.copy(this.state.velocity).projectOnPlane(this.slideSurfaceNormal),this.slideDirection.lengthSq()>1e-8?this.slideDirection.normalize():this.slideDirection.set(0,0,0)):(this.slideDirection.set(0,0,0),this.slideSurfaceNormal.copy(Tt))}sampleRootMotion(t){if(t.set(0,0,0),null==this.rootMotionAction||this.rootMotionAction.isRunning()||this.clearRootMotionAction(this.rootMotionAction),null!=this.rootMotionAction&&this.rootMotionOptions.cancelWithJump&&this.jumpInput.activated&&(this.isGrounded||this.jumpInAir)){const e=this.rootMotionAction,i=this.rootMotionOptions.onJumpCancel;return this.clearRootMotionAction(e),e.stop(),i?.(),t}if(null==this.rootMotionAction)return t;if(!(this.rootMotionAction.getClip()instanceof l))return t;const e=this.rootMotionInterpolant;this.resetRootMotion&&(this.prevRootMotionPos.fromArray(e.evaluate(0)),this.resetRootMotion=!1),It.fromArray(e.evaluate(this.rootMotionAction.time)),t.subVectors(It,this.prevRootMotionPos),this.prevRootMotionPos.copy(It);if((this.rootMotionRootBone??this.rootMotionAction.getRoot()).getWorldScale(Pt),t.multiply(Pt),this.rootMotionTargetConstraints.size>0){zt.copy(t).applyQuaternion(this.actor.quaternion);for(const t of this.rootMotionTargetConstraints)if(t.requestedProgress>0&&t.addedHorizontalDistance<t.maximumAddedHorizontalDistance){const e=r.lerp(t.initialHorizontalDistance,t.minimumHorizontalDistance,t.requestedProgress),i=Math.min(D(zt,this.actor.position,t.targetPosition,e),t.maximumAddedHorizontalDistance-t.addedHorizontalDistance);t.addedHorizontalDistance+=b(zt,this.actor.position,t.targetPosition,t.minimumHorizontalDistance,i)}else q(zt,this.actor.position,t.targetPosition,t.minimumHorizontalDistance);t.copy(zt).applyQuaternion(Vt.copy(this.actor.quaternion).invert())}return t}clearRootMotionAction(t){null!=t&&this.rootMotionAction!==t||(this.rootMotionAction=null,this.rootMotionOptions={},this.resetRootMotion=!1)}isRemoteControlledAuthority(t=this.netService.mode,e=this.actor.netRole){if(t===yt.none||e!==m.authority||t>=yt.client)return!1;return null!=this.netService.resolveActorConnection(this.actor)&&!this.netService.isLocalOwner(this.actor)}configureCharacterController(){const t=this.cc;t.setApplyImpulsesToDynamicBodies(this.applyImpulsesToDynamicBodies),t.setMinSlopeSlideAngle(this.minSlopeSlideAngle),t.setMaxSlopeClimbAngle(this.maxSlopeClimbAngle),t.setCharacterMass(this.mass),t.setSlideEnabled(this.allowSliding)}initializeStateFromActor(){this.state.position.copy(this.actor.position),this.state.velocity.set(0,0,0),this.state.yaw=this.actor.rotation.y,this.state.lookYaw=this.rotationInput.rotation.y,this.state.mode=this.mode,this.state.currentSpeed=this.horizontalSpeed,this.state.prevRotateToMovementDirection=this.rotateToMovementDirection}createCollisionShape(){const t=this.getColliderHeight(!1),e=this.getColliderRadius(!1),i=new x(t,e);return i.offset.y=this.getCapsuleOffsetY(t,e),i.collisionGroup=xt,i}updateCrouchState(t,e,i,o){if(!(0!==(t.flags&B)))return this.crouchSuppressedUntilRelease=!1,this.slideOnLandingRequested=!1,this.state.mode===A.sliding&&(this.state.mode=A.walking),void this.tryUncrouch();if(this.state.mode===A.sliding&&(!this.slideEnabled||i))return this.slideOnLandingRequested=!1,this.state.mode=A.walking,void(this.crouchSuppressedUntilRelease||this.setCrouching(!0));if(this.state.mode===A.sliding&&o)return this.crouchSuppressedUntilRelease=!0,this.slideOnLandingRequested=!1,void this.tryUncrouch();if(this.crouchSuppressedUntilRelease)return void(this.slideOnLandingRequested=!1);if(this.setCrouching(!0),!this.slideEnabled||i)return void(this.slideOnLandingRequested=!1);if(this.state.mode===A.falling)return void(this.slideOnLandingRequested=!0);this.resolveGroundNormal(Xt);const s=this.slideOnLandingRequested&&this.state.mode===A.walking&&this.state.grounded,n=e&&0!==(t.flags&$)||s;s&&(this.slideOnLandingRequested=!1);const r=Qt(this.state.velocity);this.state.mode===A.walking&&St(this.slideEnabled,n,this.state.grounded,this.isWalkableSlideSurface(Xt),i,r,this.slideMinStartSpeed)&&(this.state.mode=A.sliding,this.slideSurfaceNormal.copy(Xt),this.state.velocity.projectOnPlane(this.slideSurfaceNormal),this.state.currentSpeed=Qt(this.state.velocity))}updateSlidingMode(t){const e=t.state,i=t.move;this.resolveGroundNormal(this.slideSurfaceNormal);const o=this.isWalkableSlideSurface(this.slideSurfaceNormal),s=e.velocity.length();if(!e.grounded||!o)return e.mode=A.falling,$t.update(t),A.falling;if(this.slideEnabled&&0!==(i.flags&B)&&!t.rootMotionActive&&0!==(i.flags&Z))return vt(t.outVelocity,e.velocity,this.jumpVelocity),e.velocity.copy(t.outVelocity),e.currentSpeed=Qt(e.velocity),A.falling;if(!gt(this.slideEnabled,0!==(i.flags&B),e.grounded,o,t.rootMotionActive,s,this.slideMinExitSpeed))return e.mode=A.walking,Wt.update(t),A.walking;const n=Mt(e.velocity,e.velocity,this.slideSurfaceNormal,t.rotatedDirection,this.getEffectiveGravity(),t.deltaTime,this.slideDeceleration,this.slideGravityScale,this.slideSteeringRate);if(e.currentSpeed=Qt(e.velocity),n<Math.max(0,this.slideMinExitSpeed))return e.mode=A.walking,Wt.update(t),A.walking;t.outVelocity.copy(e.velocity).add(t.platformVelocity)}tryUncrouch(){this.isCrouching&&this.canUncrouch()&&this.setCrouching(!1)&&(this.suppressUncrouchLift=!0,this.state.fallGraceTime=0)}resolveGroundNormal(t){return!this.rayTestResult.hasHit||this.rayTestResult.hitNormal.lengthSq()<=1e-8?t.copy(Tt):(t.copy(this.rayTestResult.hitNormal).normalize(),t.dot(Tt)<0&&t.multiplyScalar(-1),t)}isWalkableSlideSurface(t){return t.dot(Tt)>=Math.cos(this.cc.maxSlopeClimbAngle())}setCrouching(t){return this.isCrouching!==t&&(this.isCrouching=t,this.syncColliderShape(),!0)}canUncrouch(){const t=this.getColliderHeight(!1),e=this.getColliderRadius(!1);return!this.physicsSystem.hasActorCapsuleIntersection(this.actor,this.state.position,t,e,this.getCapsuleOffsetY(t,e),this.getCollisionGroup())}syncColliderShape(){const t=this.getColliderHeight(this.isCrouching),e=this.getColliderRadius(this.isCrouching);this.physicsSystem.setActorCapsuleCollider(this.actor,t,e,this.getCapsuleOffsetY(t,e))}getColliderHeight(t){const e=Number.isFinite(this.colliderHeight)?Math.max(0,this.colliderHeight):0;if(!t)return e;const i=Number.isFinite(this.crouchColliderHeight)?this.crouchColliderHeight:e;return r.clamp(i,0,e)}getColliderRadius(t){const e=Number.isFinite(this.colliderRadius)?Math.max(.001,this.colliderRadius):.001;if(!t)return e;const i=this.crouchColliderRadius??e,o=Number.isFinite(i)?i:e;return r.clamp(o,.001,e)}getCapsuleOffsetY(t,e){return e+t/2+this.offset}checkGrounded(t){Nt.y=-Math.max(.05,this.cc.snapToGroundDistance()),this.physicsSystem.rayTest(jt.addVectors(t,Yt.set(0,this.offset,0)),Gt.addVectors(t,Nt),this.rayTestResult,this.groundRayTestOptions)}resolvePlatformVelocity(t){return t.set(0,0,0),this.rayTestResult.hasHit&&null!=this.rayTestResult.actor&&this.physicsSystem.getLinearVelocity(this.rayTestResult.actor,t),t}getCollisionGroup(){return this.characterCollision?null:xt}};t([T(),e("design:type",Number)],Zt.prototype,"colliderHeight",void 0),t([T(),e("design:type",Number)],Zt.prototype,"colliderRadius",void 0),t([T(),e("design:type",Number)],Zt.prototype,"crouchColliderHeight",void 0),t([T({optional:!0}),e("design:type",Number)],Zt.prototype,"crouchColliderRadius",void 0),t([g(!1),e("design:type",Function),e("design:paramtypes",[Uint8Array]),e("design:returntype",void 0)],Zt.prototype,"serverMove",null),t([y(!1),e("design:type",Function),e("design:paramtypes",[Uint8Array]),e("design:returntype",void 0)],Zt.prototype,"clientMoveAck",null),t([y(!0),e("design:type",Function),e("design:paramtypes",[Uint8Array]),e("design:returntype",void 0)],Zt.prototype,"clientMoveCorrection",null),t([v(!1),e("design:type",Function),e("design:paramtypes",[Uint8Array]),e("design:returntype",void 0)],Zt.prototype,"simulatedState",null),Zt=t([R({inEditor:!1}),e("design:paramtypes",[])],Zt);export{Zt as CharacterMovementComponent};const Wt={mode:A.walking,update(t){const e=t.component,i=t.move,o=t.state,s=t.rotatedDirection,n=Math.min(1,Math.sqrt(i.inputX*i.inputX+i.inputY*i.inputY)),r=!e.rotateToMovementDirection&&i.inputY<0,a=e.isCrouching,h=!t.rootMotionActive&&!a&&0===(i.flags&B)&&0!==(i.flags&Q)&&!r&&0!==s.lengthSq()&&(e.sprintPolicy?.canSprint()??!0),l=(a?e.maxSpeedCrouch:r?e.maxSpeedBackwards:h?e.maxSpeedSprint:e.maxSpeed)*(e.normalizedDirection?1:n);if(0!==s.lengthSq()){e.isSprinting=h,h&&e.sprintPolicy?.commitSprint(t.deltaTime,i.sequence);let a=Math.min(l,o.currentSpeed),c=e.groundAccelerationRate;if(h&&t.startedSprinting&&e.sprintStartBoostSpeed>0){const t=e.normalizedDirection?1:n;a=Math.max(a,l+e.sprintStartBoostSpeed*t),c=e.sprintStartBoostDamping}else h&&!r&&e.sprintStartBoostSpeed>0&&o.currentSpeed>l&&(a=o.currentSpeed,c=e.sprintStartBoostDamping);o.currentSpeed=function(t,e,i,o){return e+(t-e)*Math.exp(-i*o)}(a,l,c,t.deltaTime),t.outVelocity.copy(s).multiplyScalar(function(t,e,i,o){const s=i*o;if(s<=1e-6)return t;return e+(t-e)*(1-Math.exp(-s))/s}(a,l,c,t.deltaTime))}else o.currentSpeed=0,t.outVelocity.set(0,0,0);if(0!==(i.flags&Z))return o.mode=A.falling,o.velocity.copy(t.outVelocity),o.velocity.y=e.jumpVelocity,A.falling;t.outVelocity.y=t.deltaTime*e.getEffectiveGravity(),t.outVelocity.add(t.platformVelocity)}},$t={mode:A.falling,update(t){const e=t.component,i=t.state,o=t.move;0!==(o.flags&Z)&&e.jumpInAir&&(i.velocity.copy(t.outVelocity),i.velocity.y=e.jumpVelocity),i.velocity.y+=t.deltaTime*e.getEffectiveGravity(),t.outVelocity.copy(i.velocity),t.outVelocity.addScaledVector(t.rotatedDirection,e.fallingMovementControl),e.fallingReorientation&&t.outVelocity.applyAxisAngle(Tt,r.euclideanModulo(o.yaw-i.lookYaw+Math.PI,2*Math.PI)-Math.PI)}};function Qt(t){return Math.sqrt(t.x*t.x+t.z*t.z)}function Jt(t,e){return e.sequence=t.sequence,e.clientTime=t.clientTime,e.dt=t.dt,e.inputX=t.inputX,e.inputY=t.inputY,e.yaw=t.yaw,e.flags=t.flags,e.clientX=t.clientX,e.clientY=t.clientY,e.clientZ=t.clientZ,e.rootMotionX=t.rootMotionX,e.rootMotionY=t.rootMotionY,e.rootMotionZ=t.rootMotionZ,e}function Kt(t,e){const i=t.position.x-e.clientX,o=t.position.y-e.clientY,s=t.position.z-e.clientZ;return i*i+o*o+s*s}class _t{constructor(t){this.scene=t,this.serverPosition=new h,this.direction=new h}update(t,e){const i=this.serverPosition.set(e.x,e.y,e.z),o=i.distanceTo(t);o>.1&&console.log(`Correction applied. Error: ${o.toFixed(3)}. Server position: (${i.x.toFixed(2)}, ${i.y.toFixed(2)}, ${i.z.toFixed(2)}), Client position: (${t.x.toFixed(2)}, ${t.y.toFixed(2)}, ${t.z.toFixed(2)})`);const s=this.direction.copy(i).sub(t).normalize();te.position.copy(t),te.setDirection(s),te.setLength(o);const n=ee(this.scene);n.add(te),setTimeout(()=>{n.remove(te)},1e3)}}const te=new n(new h(0,1,0),new h,1,16711680);function ee(t){return null==t.parent?t:ee(t.parent)}const ie={immediate:!1,savedRootMotion:!0};/*
|
|
1
|
+
import{__decorate as t,__metadata as e}from"tslib";import i from"@dimforge/rapier3d-simd-compat";import{takeUntil as o}from"rxjs";import*as s from"three";import{ArrowHelper as n,MathUtils as r,Quaternion as a,Vector3 as h}from"three";import{RootMotionClip as l}from"../../../../animation/root-motion.js";import{inject as c}from"../../../../inject.js";import{ActionInput as d,AxisInput as u,RotationInput as p}from"../../../../input/index.js";import{NetRole as m}from"../../../../net/service/net-actor-role.js";import{RunOnClient as y,RunOnNotOwner as v,RunOnServer as g}from"../../../../net/service/rpc-decorator.js";import{NetService as S}from"../../../../net/service/net-service.js";import{PhysicsSystem as M,RayTestResult as f}from"../../../../services/physics/physics-system.js";import{CapsuleCollisionShape as x}from"../../../../../scene/collision/collision-shape.js";import{Parameter as T}from"../../../../../shader/parameter.js";import{PhysicsBodyType as C}from"../../../../services/physics/physics-system.js";import{ActorComponent as w,Component as R}from"../../../component.js";import{CharacterMovementMode as A}from"./modes.js";import{calculateRootMotionTargetApproachCorrection as D,constrainRootMotionTargetApproach as q,warpRootMotionTargetApproach as b}from"./root-motion-constraint.js";import{DEFAULT_MAX_CLIENT_TIME_AHEAD as z,DEFAULT_LARGE_CORRECTION_THRESHOLD as k,DEFAULT_MAX_COMBINED_MOVE_DELTA_TIME as P,DEFAULT_MAX_MOVE_DELTA_TIME as I,DEFAULT_MAX_MOVES_PER_PACKET as G,DEFAULT_MAX_REPLAYED_MOVES as O,DEFAULT_MAX_SERVER_MOVES_PER_TICK as j,DEFAULT_MAX_SAVED_MOVES as Y,DEFAULT_MAX_SERVER_MOVE_DELTA_TIME_SCALAR as N,DEFAULT_MAX_SERVER_MOVE_TIME_BUDGET as F,DEFAULT_MAX_SUBSTEP_DELTA_TIME as V,DEFAULT_MOVE_SEND_INTERVAL as H,DEFAULT_SIMULATED_INTERPOLATION_DELAY as U,DEFAULT_SIMULATED_STATE_INTERVAL as X,DEFAULT_SMALL_CORRECTION_TOLERANCE as E,DEFAULT_TELEPORT_SNAP_THRESHOLD as L,MOVE_FLAG_CROUCH as B,MOVE_FLAG_JUMP as Z,MOVE_FLAG_ROOT_MOTION as W,MOVE_FLAG_SLIDE_START as $,MOVE_FLAG_SPRINT as Q,MOVE_FLAG_SPRINT_START as J,SNAPSHOT_FLAG_CROUCH_SUPPRESSED as K,SNAPSHOT_FLAG_CROUCHING as _,NetCharacterProxySmoother as tt,NetCharacterSavedMoveBuffer as et,accrueNetCharacterServerMoveTime as it,createNetCharacterMovementSnapshot as ot,createNetCharacterServerMoveTimingState as st,consumeNetCharacterServerMoveTime as nt,decodeNetCharacterMoveBatch as rt,decodeNetCharacterOwnerAck as at,decodeNetCharacterOwnerCorrection as ht,decodeNetCharacterSimulatedState as lt,encodeNetCharacterMoveBatch as ct,encodeNetCharacterOwnerAck as dt,encodeNetCharacterOwnerCorrection as ut,encodeNetCharacterSimulatedState as pt,validateNetCharacterMove as mt}from"./net-character-movement-protocol.js";import{NetMode as yt}from"../../../../net/net-session.js";import{setSlideJumpVelocity as vt,shouldContinueSlide as gt,shouldStartSlide as St,updateSlideVelocity as Mt,updateSlideVelocityFromDisplacement as ft}from"./character-slide.js";export*from"./net-character-movement-protocol.js";const xt=131070,Tt=new h(0,1,0),Ct=new h,wt=new h,Rt=new h,At=new h,Dt=new h,qt=new h,bt=new h,zt=new h,kt=new h,Pt=new h,It=new h,Gt=new h,Ot=new h,jt=new h,Yt=new h(0,1,0),Nt=new h(0,-.05,0),Ft=new h,Vt=new a,Ht=new s.Euler,Ut=new h,Xt=new h,Et=new h,Lt=new h,Bt=new i.CharacterCollision;let Zt=class extends w{get autoStepMinWidth(){return this.cc.autostepMinWidth()}set autoStepMinWidth(t){this.cc.enableAutostep(this.cc.autostepMaxHeight(),t,this.cc.autostepIncludesDynamicBodies())}get autoStepDynamicObjects(){return this.cc.autostepIncludesDynamicBodies()}set autoStepDynamicObjects(t){this.cc.enableAutostep(this.cc.autostepMaxHeight(),this.cc.autostepMinWidth(),t)}get autoStepMaxHeight(){return this.cc.autostepMaxHeight()}set autoStepMaxHeight(t){this.cc.enableAutostep(t,this.cc.autostepMinWidth(),this.cc.autostepIncludesDynamicBodies())}get snapToGround(){return this.cc.snapToGroundDistance()}set snapToGround(t){this.cc.enableSnapToGround(t)}set offset(t){this.cc.setOffset(t)}get offset(){return this.cc.offset()}set normalNudgeFactor(t){this.cc.setNormalNudgeFactor(t)}get normalNudgeFactor(){return this.cc.normalNudgeFactor()}teleport(t,e=this.actor.rotation.y){this.state.position.copy(t),this.state.velocity.set(0,0,0),this.state.yaw=e,this.actor.position.copy(t),this.actor.rotation.y=e,this.rotationInput.rotation.y=e,this.physicsSystem.setPosition(this.actor,t),this.physicsSystem.setRotation(this.actor,this.actor.quaternion),this.physicsSystem.flushModifiedBodyPositionsToColliders()}constructor(){super(),this.isDefaultCharacterMovementComponent=!0,this.directionInput=new u,this.jumpInput=new d,this.sprintInput=new d,this.crouchInput=new d,this.rotationInput=new p,this.horizontalSpeed=0,this.maxSpeed=8,this.maxSpeedBackwards=8,this.maxSpeedSprint=12,this.maxSpeedCrouch=4,this.slideEnabled=!1,this.slideMinStartSpeed=8,this.slideMinExitSpeed=4,this.slideDeceleration=3,this.slideGravityScale=1,this.slideSteeringRate=2,this.sprintStartBoostSpeed=0,this.sprintStartBoostDamping=6,this.jumpVelocity=7,this.fallingMovementControl=.5,this.fallingReorientation=!1,this.groundAccelerationRate=4,this.gravityOverride=null,this.colliderHeight=2,this.colliderRadius=.5,this.crouchColliderHeight=1,this.crouchColliderRadius=null,this.jumpInAir=!1,this.mass=50,this.allowSliding=!0,this.minSlopeSlideAngle=r.degToRad(70),this.maxSlopeClimbAngle=r.degToRad(70),this.applyImpulsesToDynamicBodies=!0,this.normalizedDirection=!0,this.characterCollision=!1,this.allowRootMotionJumpCancel=!1,this.enabled=!0,this.rotateToMovementDirection=!1,this.smoothRotation=!0,this.rotationSpeed=40,this.maxRotationSpeed=100,this.impulseDamping=2,this.moveSendInterval=H,this.simulatedStateInterval=X,this.maxSavedMoves=Y,this.maxReplayedMoves=O,this.maxServerMovesPerTick=j,this.maxMovesPerPacket=G,this.maxMoveDeltaTime=I,this.maxCombinedMoveDeltaTime=P,this.maxServerMoveDeltaTimeScalar=N,this.maxClientTimeAhead=z,this.maxServerMoveTimeBudget=F,this.maxSubstepDeltaTime=V,this.fallGraceTime=.1,this.correctionToleranceSq=E*E,this.largeCorrectionThreshold=k,this.teleportSnapThreshold=L,this.listenServerSmoothing=!0,this.listenServerSmoothLocationTime=.04,this.listenServerMaxSmoothUpdateDistance=1,this.listenServerNoSmoothUpdateDistance=2.5,this.debugCorrections=!0,this.suppressUncrouchLift=!1,this.velocity=new h,this.visualSmoothingOffset=new h,this.slideDirection=new h,this.slideSurfaceNormal=new h(0,1,0),this.mode=A.walking,this.isSprinting=!1,this.isCrouching=!1,this.isSliding=!1,this.pressedJump=!1,this.rayTestResult=new f,this.groundRayTestOptions={debug:!1,excludeTriggers:!0,excludeActor:void 0,resolveActor:!1},this.physicsSystem=c(M),this.netService=c(S),this.state={sequence:0,position:new h,velocity:new h,yaw:0,lookYaw:0,mode:A.walking,grounded:!1,currentSpeed:0,fallGraceTime:0,prevInputDirection:new h,prevRotateToMovementDirection:!1},this.movementModes=new Map,this.savedMoves=new et(Y),this.serverMoveTiming=st(),this.serverMoveQueue=[],this.inputMoveScratch={sequence:0,clientTime:0,dt:0,inputX:0,inputY:0,yaw:0,flags:0,clientX:0,clientY:0,clientZ:0,rootMotionX:0,rootMotionY:0,rootMotionZ:0},this.authoritativeMoveScratch={sequence:0,clientTime:0,dt:0,inputX:0,inputY:0,yaw:0,flags:0,clientX:0,clientY:0,clientZ:0,rootMotionX:0,rootMotionY:0,rootMotionZ:0},this.movementModeContext={component:this,state:this.state,move:this.inputMoveScratch,deltaTime:0,rotatedDirection:wt,platformVelocity:At,outVelocity:Rt,rootMotionActive:!1,startedSprinting:!1},this.proxySmoother=new tt(U,L),this.proxySample=ot(),this.pendingSnapshot=ot(),this.impulse=new h,this.prevRootMotionPos=new h,this.rootMotionOptions={},this.rootMotionTargetConstraints=new Set,this.resetRootMotion=!1,this.nextMoveSequence=0,this.clientMoveTime=0,this.lastSentClientMoveTime=0,this.moveSendAccumulator=0,this.simulatedStateAccumulator=0,this.proxyTime=0,this.lastServerProcessedSequence=0,this.lastServerReceivedSequence=0,this.lastAutonomousMoveFlags=0,this.previousSprintInput=!1,this.previousCrouchInput=!1,this.crouchSuppressedUntilRelease=!1,this.slideOnLandingRequested=!1,this.initialized=!1,this.cc=this.physicsSystem.getCharacterController(.1),this.cc.enableSnapToGround(.1),this.cc.enableAutostep(0,.1,!1),this.registerMovementMode(Wt),this.registerMovementMode($t),this.registerMovementMode({mode:A.sliding,update:t=>this.updateSlidingMode(t)})}onInit(){this.groundRayTestOptions.excludeActor=this.actor,this.configureCharacterController(),this.physicsSystem.addActor(this.actor,[this.createCollisionShape()],{mass:0,type:C.kinematic,continousCollisionDetection:!1,friction:0,restitution:.5,ignoreForNavMesh:!0}),this.rotationInput.rotation.copy(this.actor.rotation),this.initializeStateFromActor(),this.initialized=!0,this.physicsSystem.beforeStep.pipe(o(this.disposed)).subscribe(t=>this.tick(t))}onLateUpdate(t){this.updateVisualSmoothing(t)}registerMovementMode(t){this.movementModes.set(t.mode,t)}applyImpulse(t){this.impulse.add(t)}getEffectiveGravity(){return this.gravityOverride??this.physicsSystem.getGravity().y}get isGrounded(){return this.rayTestResult.hasHit||this.cc.computedGrounded()}setRootMotionAction(t,e={}){const i=t?.getClip();if(i instanceof l){this.rootMotionAction=t,this.rootMotionOptions=e,this.resetRootMotion=!0;const o=[];this.rootMotionInterpolant=i.motionTrack.InterpolantFactoryMethodSmooth(o);const n=s.PropertyBinding.parseTrackName(i.motionTrack.name);this.rootMotionRootBone=this.actor.object.getObjectByName(n.nodeName)}}addRootMotionTargetConstraint(t,e,i=0){const o={targetPosition:t,minimumHorizontalDistance:Math.max(0,e),maximumAddedHorizontalDistance:Math.max(0,i),initialHorizontalDistance:Math.hypot(t.x-this.actor.position.x,t.z-this.actor.position.z),addedHorizontalDistance:0,requestedProgress:0};this.rootMotionTargetConstraints.add(o);let s=!1;return{setProgress:t=>{o.requestedProgress=Math.max(o.requestedProgress,Math.min(1,Math.max(0,t)))},dispose:()=>{s||(s=!0,this.rootMotionTargetConstraints.delete(o))}}}serverMove(t){const e=rt(t);if(e.ok)for(const t of e.value.moves)mt(t,this.lastServerReceivedSequence,this.maxMoveDeltaTime)&&(this.lastServerReceivedSequence=t.sequence,this.serverMoveQueue.push(t))}clientMoveAck(t){const e=at(t);e.ok&&this.actor.netRole===m.autonomousProxy&&(this.savedMoves.ack(e.value.sequence),this.sprintPolicy?.ack?.(e.value.sequence,!1))}clientMoveCorrection(t){const e=ht(t);e.ok&&this.actor.netRole===m.autonomousProxy&&(this.debugCorrections&&(null==this.correctionDebug&&(this.correctionDebug=new _t(ee(this.actor.object))),this.correctionDebug.update(this.state.position,e.value.snapshot)),this.applyCorrection(e.value.snapshot))}simulatedState(t){const e=lt(t);e.ok&&this.actor.netRole===m.simulatedProxy&&this.proxySmoother.push(e.value,this.proxyTime)}tick(t){if(!this.initialized)return;t=Math.min(.1,t);const e=this.actor.netRole,i=this.netService.mode;e!==m.simulatedProxy?this.enabled&&(e!==m.autonomousProxy?this.isRemoteControlledAuthority(i,e)?this.tickRemoteAuthority(t):this.tickAuthority(t,i):this.tickAutonomousProxy(t)):this.tickSimulatedProxy(t)}tickAutonomousProxy(t){const e=this.state.mode,i=this.lastAutonomousMoveFlags,o=this.createMoveFromInput(t,++this.nextMoveSequence,{quantizeClientTime:!0});this.performMove(o,ie),this.resetToggleCrouchAfterSlide(e);const s=this.state.mode!==e||o.flags!==i;this.lastAutonomousMoveFlags=o.flags,this.savedMoves.capacity=this.maxSavedMoves,this.savedMoves.maxCombinedDeltaTime=Math.min(this.maxCombinedMoveDeltaTime,this.maxMoveDeltaTime),this.savedMoves.push(o),this.sendSavedMoves(t,s)}tickAuthority(t,e){const i=this.state.mode,o=this.createMoveFromInput(t,++this.nextMoveSequence);this.performMove(o,ie),this.resetToggleCrouchAfterSlide(i),e!==yt.none&&this.sendSimulatedState(t,!1)}resetToggleCrouchAfterSlide(t){t===A.sliding&&this.state.mode!==A.sliding&&"toggle"===this.crouchInput.mode&&this.crouchInput.activated&&this.crouchInput.setActivated(!1)}tickRemoteAuthority(t){it(this.serverMoveTiming,t,this.maxServerMoveTimeBudget);let e=!1;const i=Number.isFinite(this.maxServerMovesPerTick)?Math.max(1,Math.floor(this.maxServerMovesPerTick)):this.serverMoveQueue.length,o=Math.min(this.serverMoveQueue.length,i),s=Et.copy(this.actor.position);let n=!1;for(let t=0;t<o;t++){const t=this.serverMoveQueue.shift();if(null==t)break;if(!mt(t,this.lastServerProcessedSequence,this.maxMoveDeltaTime)){console.log("Net: Invalid move",t),this.sendOwnerCorrection(!0),e=!0;continue}const i=nt(this.serverMoveTiming,t,{maxMoveDeltaTime:this.maxMoveDeltaTime,maxServerMoveDeltaTimeScalar:this.maxServerMoveDeltaTimeScalar,maxClientTimeAhead:this.maxClientTimeAhead});if(i<0){this.sendOwnerCorrection(!0),e=!0;continue}const o=Jt(t,this.authoritativeMoveScratch);o.dt=i,o.rootMotionX=0,o.rootMotionY=0,o.rootMotionZ=0,o.flags&=~W,this.performMove(o,{immediate:!0,savedRootMotion:!1}),n=!0,this.lastServerProcessedSequence=o.sequence;const s=Kt(this.state,t);if(s>this.correctionToleranceSq){console.log(`Net server: Correction error: ${Math.sqrt(s)}`);const o=this.state.position.x-t.clientX,n=this.state.position.y-t.clientY,r=this.state.position.z-t.clientZ;console.log(`diff x: ${o}, y: ${n}, z: ${r}`),console.log({sequence:t.sequence,moveDt:t.dt,serverMoveDeltaTime:i,flags:t.flags,mode:this.state.mode,grounded:this.state.grounded,fallGraceTime:this.state.fallGraceTime}),this.sendOwnerCorrection(s>this.largeCorrectionThreshold*this.largeCorrectionThreshold),e=!0}}0===this.lastServerProcessedSequence||e||(this.sprintPolicy?.ack?.(this.lastServerProcessedSequence,!1),this.clientMoveAck(dt(this.lastServerProcessedSequence))),n&&this.accumulateListenServerVisualSmoothing(s,this.actor.position),this.sendSimulatedState(t,!1)}tickSimulatedProxy(t){this.proxyTime+=t,this.proxySmoother.sample(this.proxyTime,this.proxySample)&&this.applySnapshot(this.proxySample,!0)}accumulateListenServerVisualSmoothing(t,e){if(!this.shouldSmoothListenServerRemoteAuthority())return void this.visualSmoothingOffset.set(0,0,0);Lt.subVectors(t,e);const i=Lt.lengthSq();i>1e-8&&(i>this.listenServerNoSmoothUpdateDistance*this.listenServerNoSmoothUpdateDistance?this.visualSmoothingOffset.set(0,0,0):(i>this.listenServerMaxSmoothUpdateDistance*this.listenServerMaxSmoothUpdateDistance&&Lt.setLength(this.listenServerMaxSmoothUpdateDistance),this.visualSmoothingOffset.add(Lt)))}updateVisualSmoothing(t){if(this.visualSmoothingOffset.lengthSq()<=1e-8)return void this.visualSmoothingOffset.set(0,0,0);if(!this.shouldSmoothListenServerRemoteAuthority())return void this.visualSmoothingOffset.set(0,0,0);const e=Math.max(.001,this.listenServerSmoothLocationTime);t<e?this.visualSmoothingOffset.multiplyScalar(1-t/e):this.visualSmoothingOffset.set(0,0,0),this.visualSmoothingOffset.lengthSq()<=1e-4&&this.visualSmoothingOffset.set(0,0,0)}shouldSmoothListenServerRemoteAuthority(){return this.listenServerSmoothing&&this.actor.netRole===m.authority&&this.netService.isServer&&!this.netService.isDedicatedServer&&this.isRemoteControlledAuthority()}createMoveFromInput(t,e,i={}){const o=r.clamp(t,.001,this.maxMoveDeltaTime);this.clientMoveTime+=o;let s=this.clientMoveTime,n=o;!0===i.quantizeClientTime&&(s=Math.max(this.lastSentClientMoveTime+.001,Math.round(1e3*this.clientMoveTime)/1e3),n=r.clamp(s-this.lastSentClientMoveTime,.001,this.maxMoveDeltaTime),this.lastSentClientMoveTime=s),this.sampleRootMotion(bt);const a=this.crouchInput.activated,h=a&&!this.previousCrouchInput;this.previousCrouchInput=a;const l=!a&&this.sprintInput.activated,c=l&&!this.previousSprintInput;this.previousSprintInput=l;const d=(this.jumpInput.activated?Z:0)|(l?Q:0)|(a?B:0)|(h&&this.slideEnabled?$:0)|(c?J:0)|(bt.lengthSq()>0?W:0),u=function(t,e){e.set(t.x,0,t.y),e.lengthSq()>1&&e.normalize();return e}(this.directionInput.vector,Ut),p=this.inputMoveScratch;return p.sequence=e>>>0,p.clientTime=s,p.dt=n,p.inputX=u.x,p.inputY=u.z,p.yaw=this.rotationInput.rotation.y,p.flags=d,p.clientX=this.state.position.x,p.clientY=this.state.position.y,p.clientZ=this.state.position.z,p.rootMotionX=bt.x,p.rootMotionY=bt.y,p.rootMotionZ=bt.z,p}performMove(t,e){const i=r.clamp(t.dt,.001,this.maxMoveDeltaTime),o=Math.max(1,Math.ceil(i/this.maxSubstepDeltaTime)),s=0!==(t.flags&Q)&&0===(t.flags&B);this.sprintPolicy?.update?.(i,t.sequence,s),Gt.set(e.savedRootMotion?t.rootMotionX:0,e.savedRootMotion?t.rootMotionY:0,e.savedRootMotion?t.rootMotionZ:0),e.savedRootMotion||this.sampleRootMotion(Gt);let n=i;const a=i/o;for(let s=0;s<o;s++){const o=i>0?a/i:1;this.performSubstep(t,Math.min(a,n),Gt,o,e.immediate,0===s),n-=a}t.clientX=this.state.position.x,t.clientY=this.state.position.y,t.clientZ=this.state.position.z,t.flags=Gt.lengthSq()>0?t.flags|W:t.flags&~W,this.state.sequence=t.sequence,this.syncPublicState()}performSubstep(t,e,i,o,s,n){if(e<.001)return;this.checkGrounded(this.state.position),this.state.grounded=this.isGrounded;const r=null!=this.rootMotionAction&&this.rootMotionAction.enabled,a=i.lengthSq()>0,h=!r&&0!==(t.flags&Z),l=r||a;this.updateCrouchState(t,n,l,h),this.pressedJump=h,this.isSprinting=!1,this.updateRotation(t,e,r,a),this.resolvePlatformVelocity(At);const c=this.movementModeContext,d=this.movementModes.get(this.state.mode);if(null!=d){c.move=t,c.deltaTime=e,c.rootMotionActive=l,c.startedSprinting=n&&!l&&0===(t.flags&B)&&0!==(t.flags&J);const i=d.update(c);"number"==typeof i&&(this.state.mode=i)}else Rt.set(0,0,0);if(a){zt.copy(i).multiplyScalar(o).applyQuaternion(this.actor.quaternion),kt.copy(zt).divideScalar(e),this.state.mode===A.walking?(Rt.x=kt.x+At.x,Rt.z=kt.z+At.z):(Rt.x=kt.x,Rt.z=kt.z);const t=Math.sqrt(kt.x*kt.x+kt.z*kt.z);t>.4&&(this.state.currentSpeed=t)}if(this.applyImpulseToVelocity(Rt,e),h&&this.isSprinting){const t=Math.max(0,this.maxSpeedSprint),e=Qt(Rt);if(e>t){const i=t/e;Rt.x*=i,Rt.z*=i,this.state.velocity.x*=i,this.state.velocity.z*=i}}this.state.mode===A.walking&&this.state.velocity.copy(Rt),this.applyMovement(Rt,e,s),this.state.mode===A.sliding&&(ft(this.state.velocity,qt,At,this.slideSurfaceNormal,e),this.state.currentSpeed=Qt(this.state.velocity)),this.checkGrounded(this.state.position),this.state.grounded=this.isGrounded,this.updateModeAfterMove(Rt,e),this.state.mode===A.sliding&&this.state.velocity.length()<Math.max(0,this.slideMinExitSpeed)&&(this.state.mode=A.walking)}updateRotation(t,e,i,o){Ct.set(-t.inputX,0,t.inputY),Ct.lengthSq()>1&&Ct.normalize();const s=this.state.prevRotateToMovementDirection!==this.rotateToMovementDirection;let n=r.euclideanModulo(t.yaw-this.state.lookYaw+Math.PI,2*Math.PI)-Math.PI;if(s&&(this.actor.object.quaternion.setFromEuler(Ht.set(0,t.yaw,0)),n=0,this.state.lookYaw=t.yaw,this.state.prevRotateToMovementDirection=this.rotateToMovementDirection),wt.set(0,0,0),i)this.state.lookYaw=t.yaw;else if(this.rotateToMovementDirection){if(Ct.lengthSq()>0&&this.state.mode!==A.falling){const i=Ht.setFromQuaternion(this.actor.object.quaternion,"YXZ").y,o=Math.atan2(Ct.x,Ct.z),s=.99*t.yaw+o;if(this.smoothRotation){const t=r.euclideanModulo(s-i+Math.PI,2*Math.PI)-Math.PI;let o=r.clamp(t*this.rotationSpeed*e,-this.maxRotationSpeed*e,this.maxRotationSpeed*e);Math.abs(o)>Math.abs(t)&&(o=t),this.actor.object.quaternion.setFromEuler(Ht.set(0,i+o,0))}else this.actor.object.quaternion.setFromEuler(Ht.set(0,s,0));this.state.prevInputDirection.lerp(Ct,e*this.rotationSpeed),this.state.lookYaw+=n,wt.copy(this.actor.object.getWorldDirection(Ut).normalize())}}else o||(this.state.lookYaw=t.yaw,this.actor.object.quaternion.setFromEuler(Ht.set(0,t.yaw,0)),Ct.lengthSq()>0&&wt.copy(Ct).applyAxisAngle(Tt,t.yaw).normalize())}applyMovement(t,e,i){if(t.lengthSq()>0||!this.state.grounded)if(Dt.copy(t).multiplyScalar(e),this.state.grounded&&((o=this.state.mode)===A.walking||o===A.sliding)){if(this.state.mode===A.walking&&this.rayTestResult.distance<=this.cc.offset()&&(Dt.y=Math.max(0,At.y*e)),qt.copy(this.physicsSystem.getActorComputedMovement(this.actor,this.cc,Dt,this.getCollisionGroup())),this.cc.computedCollision(0,Bt),null!=Bt.normal1){const t=Ut.copy(Bt.normal1);if(Math.acos(r.clamp(t.dot(Tt),-1,1))>this.cc.maxSlopeClimbAngle()){const t=.016*this.getEffectiveGravity()*.5;Dt.y!==t&&(Dt.y=t,qt.copy(this.physicsSystem.getActorComputedMovement(this.actor,this.cc,Dt,this.getCollisionGroup())))}}}else qt.copy(this.physicsSystem.getActorComputedMovement(this.actor,this.cc,Dt,this.getCollisionGroup()));else qt.set(0,0,0);var o;this.suppressUncrouchLift&&(this.suppressUncrouchLift=!1,this.state.grounded&&this.state.mode===A.walking&&Dt.y<=1e-6&&qt.y>0&&(qt.y=0)),this.state.position.add(qt),i?(this.actor.position.copy(this.state.position),this.physicsSystem.updateActorTransform(this.actor),this.physicsSystem.flushModifiedBodyPositionsToColliders()):this.physicsSystem.setNextKinematicPosition(this.actor,this.state.position)}updateModeAfterMove(t,e){const i=function(t){if(0===t.numComputedCollisions())return!1;const e=t.computedCollision(0);Ft.set(e.normal2.x,e.normal2.y,e.normal2.z);const i=Ft.angleTo(Tt);Ft.set(e.normal1.x,e.normal1.y,e.normal1.z);const o=Ft.angleTo(Tt);if(i<100)return!1;return o>t.minSlopeSlideAngle()}(this.cc);!this.state.grounded||i?this.state.mode!==A.falling&&(this.state.fallGraceTime+=e,this.state.fallGraceTime>this.fallGraceTime+1e-6&&(this.state.mode=A.falling,this.state.velocity.copy(t))):this.state.velocity.y<=0&&(this.state.mode===A.falling&&(this.state.mode=A.walking),this.state.mode!==A.sliding&&(this.state.velocity.y=0),this.state.fallGraceTime=0)}applyImpulseToVelocity(t,e){if(this.impulse.lengthSq()<=.1)return void this.impulse.set(0,0,0);const i=Math.min(1,this.impulse.length()/5);t.x=r.lerp(t.x,this.impulse.x,i),t.z=r.lerp(t.z,this.impulse.z,i),t.y+=this.impulse.y;const o=Math.exp(-this.impulseDamping*e);this.impulse.x*=o,this.impulse.y>0?this.impulse.y+=e*this.getEffectiveGravity():this.impulse.y*=o,this.impulse.z*=o}sendSavedMoves(t,e=!1){if(this.moveSendAccumulator+=t,!e&&this.moveSendAccumulator<this.moveSendInterval)return;this.moveSendAccumulator=0;const i=this.savedMoves.getMovesForSend(this.maxMovesPerPacket);0!==i.length&&this.serverMove(ct(i))}sendOwnerCorrection(t){const e=this.toSnapshot(this.pendingSnapshot);t&&(e.horizontalSpeed=this.horizontalSpeed),this.sprintPolicy?.ack?.(e.sequence,!0),this.clientMoveCorrection(ut(e))}sendSimulatedState(t,e){this.simulatedStateAccumulator+=t,!e&&this.simulatedStateAccumulator<this.simulatedStateInterval||(this.simulatedStateAccumulator=0,this.simulatedState(pt(this.toSnapshot(this.pendingSnapshot))))}applyCorrection(t){this.sprintPolicy?.restore?.(t.sequence),this.savedMoves.ack(t.sequence),this.sprintPolicy?.ack?.(t.sequence,!0),this.applySnapshot(t,!0),this.savedMoves.replayAfter(t.sequence,t=>(this.performMove(t,{immediate:!0,savedRootMotion:!0}),null),this.maxReplayedMoves)}applySnapshot(t,e){this.state.sequence=t.sequence,this.state.position.set(t.x,t.y,t.z),this.state.velocity.set(t.velocityX,t.velocityY,t.velocityZ),this.state.yaw=t.yaw,this.state.lookYaw=t.yaw,this.state.mode=t.mode,this.state.currentSpeed=t.horizontalSpeed,this.state.fallGraceTime=0,this.actor.position.copy(this.state.position),this.actor.object.quaternion.setFromEuler(Ht.set(0,t.yaw,0)),this.crouchSuppressedUntilRelease=0!==(t.flags&K),this.setCrouching(0!==(t.flags&_)||t.mode===A.sliding),e&&(this.physicsSystem.updateActorTransform(this.actor),this.physicsSystem.flushModifiedBodyPositionsToColliders()),this.checkGrounded(this.state.position),this.state.grounded=this.isGrounded,this.syncPublicState()}toSnapshot(t){return t.sequence=this.state.sequence,t.x=this.state.position.x,t.y=this.state.position.y,t.z=this.state.position.z,t.velocityX=this.state.velocity.x,t.velocityY=this.state.velocity.y,t.velocityZ=this.state.velocity.z,t.yaw=Ht.setFromQuaternion(this.actor.object.quaternion,"YXZ").y,t.mode=this.state.mode,t.flags=(this.isCrouching?_:0)|(this.crouchSuppressedUntilRelease?K:0),t.horizontalSpeed=this.state.currentSpeed,t}syncPublicState(){this.velocity.copy(this.state.velocity),this.mode=this.state.mode,this.horizontalSpeed=this.state.currentSpeed,this.isSliding=this.state.mode===A.sliding,this.isSliding?(this.resolveGroundNormal(this.slideSurfaceNormal),this.slideDirection.copy(this.state.velocity).projectOnPlane(this.slideSurfaceNormal),this.slideDirection.lengthSq()>1e-8?this.slideDirection.normalize():this.slideDirection.set(0,0,0)):(this.slideDirection.set(0,0,0),this.slideSurfaceNormal.copy(Tt))}sampleRootMotion(t){if(t.set(0,0,0),null==this.rootMotionAction||this.rootMotionAction.isRunning()||this.clearRootMotionAction(this.rootMotionAction),null!=this.rootMotionAction&&this.rootMotionOptions.cancelWithJump&&this.jumpInput.activated&&(this.isGrounded||this.jumpInAir)){const e=this.rootMotionAction,i=this.rootMotionOptions.onJumpCancel;return this.clearRootMotionAction(e),e.stop(),i?.(),t}if(null==this.rootMotionAction)return t;if(!(this.rootMotionAction.getClip()instanceof l))return t;const e=this.rootMotionInterpolant;this.resetRootMotion&&(this.prevRootMotionPos.fromArray(e.evaluate(0)),this.resetRootMotion=!1),It.fromArray(e.evaluate(this.rootMotionAction.time)),t.subVectors(It,this.prevRootMotionPos),this.prevRootMotionPos.copy(It);if((this.rootMotionRootBone??this.rootMotionAction.getRoot()).getWorldScale(Pt),t.multiply(Pt),this.rootMotionTargetConstraints.size>0){zt.copy(t).applyQuaternion(this.actor.quaternion);for(const t of this.rootMotionTargetConstraints)if(t.requestedProgress>0&&t.addedHorizontalDistance<t.maximumAddedHorizontalDistance){const e=r.lerp(t.initialHorizontalDistance,t.minimumHorizontalDistance,t.requestedProgress),i=Math.min(D(zt,this.actor.position,t.targetPosition,e),t.maximumAddedHorizontalDistance-t.addedHorizontalDistance);t.addedHorizontalDistance+=b(zt,this.actor.position,t.targetPosition,t.minimumHorizontalDistance,i)}else q(zt,this.actor.position,t.targetPosition,t.minimumHorizontalDistance);t.copy(zt).applyQuaternion(Vt.copy(this.actor.quaternion).invert())}return t}clearRootMotionAction(t){null!=t&&this.rootMotionAction!==t||(this.rootMotionAction=null,this.rootMotionOptions={},this.resetRootMotion=!1)}isRemoteControlledAuthority(t=this.netService.mode,e=this.actor.netRole){if(t===yt.none||e!==m.authority||t>=yt.client)return!1;return null!=this.netService.resolveActorConnection(this.actor)&&!this.netService.isLocalOwner(this.actor)}configureCharacterController(){const t=this.cc;t.setApplyImpulsesToDynamicBodies(this.applyImpulsesToDynamicBodies),t.setMinSlopeSlideAngle(this.minSlopeSlideAngle),t.setMaxSlopeClimbAngle(this.maxSlopeClimbAngle),t.setCharacterMass(this.mass),t.setSlideEnabled(this.allowSliding)}initializeStateFromActor(){this.state.position.copy(this.actor.position),this.state.velocity.set(0,0,0),this.state.yaw=this.actor.rotation.y,this.state.lookYaw=this.rotationInput.rotation.y,this.state.mode=this.mode,this.state.currentSpeed=this.horizontalSpeed,this.state.prevRotateToMovementDirection=this.rotateToMovementDirection}createCollisionShape(){const t=this.getColliderHeight(!1),e=this.getColliderRadius(!1),i=new x(t,e);return i.offset.y=this.getCapsuleOffsetY(t,e),i.collisionGroup=xt,i}updateCrouchState(t,e,i,o){if(!(0!==(t.flags&B)))return this.crouchSuppressedUntilRelease=!1,this.slideOnLandingRequested=!1,this.state.mode===A.sliding&&(this.state.mode=A.walking),void this.tryUncrouch();if(this.state.mode===A.sliding&&(!this.slideEnabled||i))return this.slideOnLandingRequested=!1,this.state.mode=A.walking,void(this.crouchSuppressedUntilRelease||this.setCrouching(!0));if(this.state.mode===A.sliding&&o)return this.crouchSuppressedUntilRelease=!0,this.slideOnLandingRequested=!1,void this.tryUncrouch();if(this.crouchSuppressedUntilRelease)return void(this.slideOnLandingRequested=!1);if(this.setCrouching(!0),!this.slideEnabled||i)return void(this.slideOnLandingRequested=!1);if(this.state.mode===A.falling)return void(this.slideOnLandingRequested=!0);this.resolveGroundNormal(Xt);const s=this.slideOnLandingRequested&&this.state.mode===A.walking&&this.state.grounded,n=e&&0!==(t.flags&$)||s;s&&(this.slideOnLandingRequested=!1);const r=Qt(this.state.velocity);this.state.mode===A.walking&&St(this.slideEnabled,n,this.state.grounded,this.isWalkableSlideSurface(Xt),i,r,this.slideMinStartSpeed)&&(this.state.mode=A.sliding,this.slideSurfaceNormal.copy(Xt),this.state.velocity.projectOnPlane(this.slideSurfaceNormal),this.state.currentSpeed=Qt(this.state.velocity))}updateSlidingMode(t){const e=t.state,i=t.move;this.resolveGroundNormal(this.slideSurfaceNormal);const o=this.isWalkableSlideSurface(this.slideSurfaceNormal),s=e.velocity.length();if(!e.grounded||!o)return e.mode=A.falling,$t.update(t),A.falling;if(this.slideEnabled&&0!==(i.flags&B)&&!t.rootMotionActive&&0!==(i.flags&Z))return vt(t.outVelocity,e.velocity,this.jumpVelocity),e.velocity.copy(t.outVelocity),e.currentSpeed=Qt(e.velocity),A.falling;if(!gt(this.slideEnabled,0!==(i.flags&B),e.grounded,o,t.rootMotionActive,s,this.slideMinExitSpeed))return e.mode=A.walking,Wt.update(t),A.walking;const n=Mt(e.velocity,e.velocity,this.slideSurfaceNormal,t.rotatedDirection,this.getEffectiveGravity(),t.deltaTime,this.slideDeceleration,this.slideGravityScale,this.slideSteeringRate);if(e.currentSpeed=Qt(e.velocity),n<Math.max(0,this.slideMinExitSpeed))return e.mode=A.walking,Wt.update(t),A.walking;t.outVelocity.copy(e.velocity).add(t.platformVelocity)}tryUncrouch(){this.isCrouching&&this.canUncrouch()&&this.setCrouching(!1)&&(this.suppressUncrouchLift=!0,this.state.fallGraceTime=0)}resolveGroundNormal(t){return!this.rayTestResult.hasHit||this.rayTestResult.hitNormal.lengthSq()<=1e-8?t.copy(Tt):(t.copy(this.rayTestResult.hitNormal).normalize(),t.dot(Tt)<0&&t.multiplyScalar(-1),t)}isWalkableSlideSurface(t){return t.dot(Tt)>=Math.cos(this.cc.maxSlopeClimbAngle())}setCrouching(t){return this.isCrouching!==t&&(this.isCrouching=t,this.syncColliderShape(),!0)}canUncrouch(){const t=this.getColliderHeight(!1),e=this.getColliderRadius(!1);return!this.physicsSystem.hasActorCapsuleIntersection(this.actor,this.state.position,t,e,this.getCapsuleOffsetY(t,e),this.getCollisionGroup())}syncColliderShape(){const t=this.getColliderHeight(this.isCrouching),e=this.getColliderRadius(this.isCrouching);this.physicsSystem.setActorCapsuleCollider(this.actor,t,e,this.getCapsuleOffsetY(t,e))}getColliderHeight(t){const e=Number.isFinite(this.colliderHeight)?Math.max(0,this.colliderHeight):0;if(!t)return e;const i=Number.isFinite(this.crouchColliderHeight)?this.crouchColliderHeight:e;return r.clamp(i,0,e)}getColliderRadius(t){const e=Number.isFinite(this.colliderRadius)?Math.max(.001,this.colliderRadius):.001;if(!t)return e;const i=this.crouchColliderRadius??e,o=Number.isFinite(i)?i:e;return r.clamp(o,.001,e)}getCapsuleOffsetY(t,e){return e+t/2+this.offset}checkGrounded(t){Nt.y=-Math.max(.05,this.cc.snapToGroundDistance()),this.physicsSystem.rayTest(Ot.addVectors(t,Yt.set(0,this.offset,0)),jt.addVectors(t,Nt),this.rayTestResult,this.groundRayTestOptions)}resolvePlatformVelocity(t){return t.set(0,0,0),this.rayTestResult.hasHit&&null!=this.rayTestResult.actor&&this.physicsSystem.getLinearVelocity(this.rayTestResult.actor,t),t}getCollisionGroup(){return this.characterCollision?null:xt}};t([T(),e("design:type",Number)],Zt.prototype,"colliderHeight",void 0),t([T(),e("design:type",Number)],Zt.prototype,"colliderRadius",void 0),t([T(),e("design:type",Number)],Zt.prototype,"crouchColliderHeight",void 0),t([T({optional:!0}),e("design:type",Number)],Zt.prototype,"crouchColliderRadius",void 0),t([g(!1),e("design:type",Function),e("design:paramtypes",[Uint8Array]),e("design:returntype",void 0)],Zt.prototype,"serverMove",null),t([y(!1),e("design:type",Function),e("design:paramtypes",[Uint8Array]),e("design:returntype",void 0)],Zt.prototype,"clientMoveAck",null),t([y(!0),e("design:type",Function),e("design:paramtypes",[Uint8Array]),e("design:returntype",void 0)],Zt.prototype,"clientMoveCorrection",null),t([v(!1),e("design:type",Function),e("design:paramtypes",[Uint8Array]),e("design:returntype",void 0)],Zt.prototype,"simulatedState",null),Zt=t([R({inEditor:!1}),e("design:paramtypes",[])],Zt);export{Zt as CharacterMovementComponent};const Wt={mode:A.walking,update(t){const e=t.component,i=t.move,o=t.state,s=t.rotatedDirection,n=Math.min(1,Math.sqrt(i.inputX*i.inputX+i.inputY*i.inputY)),r=!e.rotateToMovementDirection&&i.inputY<0,a=e.isCrouching,h=!t.rootMotionActive&&!a&&0===(i.flags&B)&&0!==(i.flags&Q)&&!r&&0!==s.lengthSq()&&(e.sprintPolicy?.canSprint()??!0),l=(a?e.maxSpeedCrouch:r?e.maxSpeedBackwards:h?e.maxSpeedSprint:e.maxSpeed)*(e.normalizedDirection?1:n);if(0!==s.lengthSq()){e.isSprinting=h,h&&e.sprintPolicy?.commitSprint(t.deltaTime,i.sequence);let a=Math.min(l,o.currentSpeed),c=e.groundAccelerationRate;if(h&&t.startedSprinting&&e.sprintStartBoostSpeed>0){const t=e.normalizedDirection?1:n;a=Math.max(a,l+e.sprintStartBoostSpeed*t),c=e.sprintStartBoostDamping}else h&&!r&&e.sprintStartBoostSpeed>0&&o.currentSpeed>l&&(a=o.currentSpeed,c=e.sprintStartBoostDamping);o.currentSpeed=function(t,e,i,o){return e+(t-e)*Math.exp(-i*o)}(a,l,c,t.deltaTime),t.outVelocity.copy(s).multiplyScalar(function(t,e,i,o){const s=i*o;if(s<=1e-6)return t;return e+(t-e)*(1-Math.exp(-s))/s}(a,l,c,t.deltaTime))}else o.currentSpeed=0,t.outVelocity.set(0,0,0);if(0!==(i.flags&Z))return o.mode=A.falling,o.velocity.copy(t.outVelocity),o.velocity.y=e.jumpVelocity,A.falling;t.outVelocity.y=t.deltaTime*e.getEffectiveGravity(),t.outVelocity.add(t.platformVelocity)}},$t={mode:A.falling,update(t){const e=t.component,i=t.state,o=t.move;0!==(o.flags&Z)&&e.jumpInAir&&(i.velocity.copy(t.outVelocity),i.velocity.y=e.jumpVelocity),i.velocity.y+=t.deltaTime*e.getEffectiveGravity(),t.outVelocity.copy(i.velocity),t.outVelocity.addScaledVector(t.rotatedDirection,e.fallingMovementControl),e.fallingReorientation&&t.outVelocity.applyAxisAngle(Tt,r.euclideanModulo(o.yaw-i.lookYaw+Math.PI,2*Math.PI)-Math.PI)}};function Qt(t){return Math.sqrt(t.x*t.x+t.z*t.z)}function Jt(t,e){return e.sequence=t.sequence,e.clientTime=t.clientTime,e.dt=t.dt,e.inputX=t.inputX,e.inputY=t.inputY,e.yaw=t.yaw,e.flags=t.flags,e.clientX=t.clientX,e.clientY=t.clientY,e.clientZ=t.clientZ,e.rootMotionX=t.rootMotionX,e.rootMotionY=t.rootMotionY,e.rootMotionZ=t.rootMotionZ,e}function Kt(t,e){const i=t.position.x-e.clientX,o=t.position.y-e.clientY,s=t.position.z-e.clientZ;return i*i+o*o+s*s}class _t{constructor(t){this.scene=t,this.serverPosition=new h,this.direction=new h}update(t,e){const i=this.serverPosition.set(e.x,e.y,e.z),o=i.distanceTo(t);o>.1&&console.log(`Correction applied. Error: ${o.toFixed(3)}. Server position: (${i.x.toFixed(2)}, ${i.y.toFixed(2)}, ${i.z.toFixed(2)}), Client position: (${t.x.toFixed(2)}, ${t.y.toFixed(2)}, ${t.z.toFixed(2)})`);const s=this.direction.copy(i).sub(t).normalize();te.position.copy(t),te.setDirection(s),te.setLength(o);const n=ee(this.scene);n.add(te),setTimeout(()=>{n.remove(te)},1e3)}}const te=new n(new h(0,1,0),new h,1,16711680);function ee(t){return null==t.parent?t:ee(t.parent)}const ie={immediate:!1,savedRootMotion:!0};/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NodeState, type BehaviorTreeContext, type Node } from './bt.js';
|
|
1
|
+
import { NodeState, type BehaviorTreeContext, type Node, type StopReason } from './bt.js';
|
|
2
2
|
/** Owns one tree instance. Build the topology before construction; do not share nodes. */
|
|
3
3
|
export declare class BehaviorTree implements BehaviorTreeContext {
|
|
4
4
|
readonly root: Node;
|
|
@@ -8,12 +8,19 @@ export declare class BehaviorTree implements BehaviorTreeContext {
|
|
|
8
8
|
private elapsed;
|
|
9
9
|
private visit;
|
|
10
10
|
private updating;
|
|
11
|
+
private ticks;
|
|
11
12
|
constructor(root: Node);
|
|
12
13
|
get dt(): number;
|
|
13
14
|
/** Unordered live view, intended for inspection. */
|
|
14
15
|
get active(): readonly Node[];
|
|
15
16
|
getState(node: Node): NodeState | undefined;
|
|
16
17
|
isActive(node: Node): boolean;
|
|
18
|
+
/** Monotonic update number, including updates that throw. */
|
|
19
|
+
get tickCount(): number;
|
|
20
|
+
/** Last update that evaluated this node; zero means never evaluated. */
|
|
21
|
+
getLastTick(node: Node): number;
|
|
22
|
+
/** Last completion/interruption reason; cleared when execution starts again. */
|
|
23
|
+
getStopReason(node: Node): StopReason | undefined;
|
|
17
24
|
tick(dt: number): NodeState;
|
|
18
25
|
execute(node: Node, dt?: number): NodeState;
|
|
19
26
|
/** Abort a subtree, or the whole tree when called without an argument. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{NodeState as t}from"./bt.js";export class BehaviorTree{constructor(t){this.root=t,this.executions=new Map,this.activeNodes=[],this.elapsed=0,this.visit=0,this.updating=!1,this.register(t,void 0)}get dt(){return this.elapsed}get active(){return this.activeNodes}getState(t){return this.lookup(t).result}isActive(t){return this.lookup(t).active}tick(t){if(this.updating)throw new Error("BehaviorTree.tick cannot be reentered");this.updating=!0,this.elapsed=t;try{return this.execute(this.root)}catch(t){try{this.abort(this.root)}catch{}throw t}finally{this.updating=!1}}execute(e,i=this.elapsed){const
|
|
1
|
+
import{NodeState as t}from"./bt.js";export class BehaviorTree{constructor(t){this.root=t,this.executions=new Map,this.activeNodes=[],this.elapsed=0,this.visit=0,this.updating=!1,this.ticks=0,this.register(t,void 0)}get dt(){return this.elapsed}get active(){return this.activeNodes}getState(t){return this.lookup(t).result}isActive(t){return this.lookup(t).active}get tickCount(){return this.ticks}getLastTick(t){return this.lookup(t).lastTick}getStopReason(t){return this.lookup(t).stopReason}tick(t){if(this.updating)throw new Error("BehaviorTree.tick cannot be reentered");this.updating=!0,this.ticks++,this.elapsed=t;try{return this.execute(this.root)}catch(t){try{this.abort(this.root)}catch{}throw t}finally{this.updating=!1}}execute(e,i=this.elapsed){const s=this.lookup(e);if(!this.updating||(this.current?s.parent!==this.current:e!==this.root))throw new Error("Execute nodes through tree.tick or their parent context");if(s.executing)throw new Error("A node cannot execute recursively");const r=this.current,o=this.elapsed,n=++this.visit;s.visited=n,s.lastTick=this.ticks,s.executing=!0,this.current=s,this.elapsed=i;try{s.active||(s.active=!0,s.activeIndex=this.activeNodes.length,this.activeNodes.push(e),s.result=void 0,s.stopReason=void 0,e.start());const r=e.tick(i,this);if(r!==t.RUNNING&&r!==t.SUCCESS&&r!==t.FAILURE)throw new Error("Node returned an invalid execution result");if(s.result=r,r!==t.RUNNING)this.finish(s,r===t.SUCCESS?"success":"failure");else for(const t of s.children)t.active&&t.visited<=n&&this.finish(t,"aborted");return r}finally{s.executing=!1,this.current=r,this.elapsed=o}}abort(t=this.root){const e=this.lookup(t);if(e.executing)throw new Error("Cannot abort a node during its own execution");this.finish(e,"aborted")}retain(t){const e=this.lookup(t);if(!this.current||e.parent!==this.current)throw new Error("Only the executing parent can retain its child");e.visited=++this.visit}finish(t,e){if(!t.active)return;let i,s=!1;for(const e of t.children)try{this.finish(e,"aborted")}catch(t){s||(i=t,s=!0)}if(t.active){t.active=!1,t.stopReason=e;const r=this.activeNodes.pop();r!==t.node&&(this.activeNodes[t.activeIndex]=r,this.lookup(r).activeIndex=t.activeIndex),t.activeIndex=-1;try{t.node.stop(e)}catch(t){s||(i=t,s=!0)}}if(s)throw i}lookup(t){const e=this.executions.get(t);if(!e)throw new Error("Node is not part of this behavior tree");return e}register(t,e){if(this.executions.has(t))throw new Error("Behavior tree nodes cannot be shared or cyclic");const i={node:t,parent:e,children:[],active:!1,activeIndex:-1,executing:!1,visited:0,result:void 0,lastTick:0,stopReason:void 0};this.executions.set(t,i);for(const e of t.getChildren())i.children.push(this.register(e,i));return i}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{describe as t,expect as e,it as
|
|
1
|
+
import{describe as t,expect as e,it as o,vi as a}from"vitest";import{BehaviorTree as n}from"./behavior-tree.js";import{Node as i,NodeState as s,SequenceNode as c,GuardNode as r,ThrottleNode as l}from"./bt.js";class d extends i{constructor(t=s.RUNNING){super(),this.result=t,this.start=a.fn(),this.stop=a.fn(t=>{}),this.tick=a.fn((t,e)=>this.result)}}class h extends i{constructor(t){super(),this.child=t,this.enabled=!0}getChildren(){return[this.child]}tick(t,e){return this.enabled&&e.execute(this.child,t),s.RUNNING}}t("BehaviorTree lifecycle",()=>{o("exposes instant evaluations and distinguishes old results from current activity",()=>{const t=new d(s.SUCCESS),o=new d,a=new c;a.addChild(t),a.addChild(o);const i=new n(a);e(i.tickCount).toBe(0),e(i.getLastTick(t)).toBe(0),i.tick(.1),e(i.isActive(t)).toBe(!1),e(i.getState(t)).toBe(s.SUCCESS),e(i.getLastTick(t)).toBe(1),e(i.getStopReason(t)).toBe("success"),i.tick(.1),e(i.tickCount).toBe(2),e(i.getLastTick(t)).toBe(1),e(i.getLastTick(o)).toBe(2),i.abort(),e(i.getState(o)).toBe(s.RUNNING),e(i.isActive(o)).toBe(!1),e(i.getStopReason(o)).toBe("aborted"),i.tick(.1),e(i.getStopReason(o)).toBeUndefined(),e(i.getLastTick(t)).toBe(3)}),o("does not report retained descendants as evaluated during a skipped update",()=>{const t=new d,o=new n(new l(t,1));o.tick(.1),o.tick(.1),e(o.isActive(t)).toBe(!0),e(o.getLastTick(t)).toBe(1),e(o.tickCount).toBe(2),e(o.getStopReason(t)).toBeUndefined()}),o("starts once, stops once, and restarts after completion",()=>{const t=new d,o=new n(t);o.tick(.1),o.tick(.1),e(t.start).toHaveBeenCalledTimes(1),e(o.active).toEqual([t]),e(o.getState(t)).toBe(s.RUNNING),t.result=s.SUCCESS,o.tick(.1),e(t.stop).toHaveBeenCalledExactlyOnceWith("success"),e(o.active).toEqual([]),o.tick(.1),e(t.start).toHaveBeenCalledTimes(2),e(t.stop).toHaveBeenCalledTimes(2)}),o("pairs start and stop for immediate failure",()=>{const t=new d(s.FAILURE),o=new n(t);e(o.tick(.1)).toBe(s.FAILURE),e(t.start).toHaveBeenCalledTimes(1),e(t.stop).toHaveBeenCalledExactlyOnceWith("failure")}),o("stops descendants before their parent and makes abort idempotent",()=>{const t=[],o=new d,a=new h(o),i=new n(a);o.stop.mockImplementation(()=>{t.push("child")}),a.stop=()=>{t.push("parent")},i.tick(.1),i.abort(),i.abort(),e(t).toEqual(["child","parent"]),e(i.active).toEqual([]),i.tick(.1),e(o.start).toHaveBeenCalledTimes(2)}),o("automatically cancels an abandoned running child",()=>{const t=new d,o=new h(t),a=new n(o);a.tick(.1),o.enabled=!1,a.tick(.1),e(t.stop).toHaveBeenCalledExactlyOnceWith("aborted"),e(a.active).toEqual([o])}),o("retains a throttled subtree without restarting or ticking it",()=>{const t=new d,o=new c;o.addChild(t);const a=new l(o,1),i=new n(a);i.tick(.1),i.tick(.1),i.tick(.1),e(t.start).toHaveBeenCalledTimes(1),e(t.tick).toHaveBeenCalledTimes(1),e(t.stop).not.toHaveBeenCalled(),e(i.active).toHaveLength(3),i.abort(),e(t.stop).toHaveBeenCalledExactlyOnceWith("aborted")}),o("restores context dt after a child executes with accumulated time",()=>{const t=[],o=new d;new n(new class extends h{tick(e,o){return t.push(o.dt),o.execute(this.child,.75),t.push(o.dt),s.RUNNING}}(o)).tick(.1),e(t).toEqual([.1,.1]),e(o.tick.mock.calls[0][0]).toBe(.75)}),o("cleans up when start or tick throws and remains reusable",()=>{const t=new d,o=new n(t);t.start.mockImplementationOnce(()=>{throw new Error("start failure")}),e(()=>o.tick(.1)).toThrow("start failure"),e(t.stop).toHaveBeenCalledExactlyOnceWith("aborted"),e(o.active).toEqual([]),o.tick(.1),t.tick.mockImplementationOnce(()=>{throw new Error("tick failure")}),e(()=>o.tick(.1)).toThrow("tick failure"),e(o.active).toEqual([]),o.tick(.1),e(o.isActive(t)).toBe(!0)}),o("continues cleaning siblings and parents if a stop hook throws",()=>{const t=new d,o=new d;const c=new class extends i{constructor(){super(...arguments),this.stop=a.fn()}getChildren(){return[t,o]}tick(e,a){return a.execute(t,e),a.execute(o,e),s.RUNNING}},r=new n(c);t.stop.mockImplementationOnce(()=>{throw new Error("stop failure")}),r.tick(.1),e(()=>r.abort()).toThrow("stop failure"),e(o.stop).toHaveBeenCalledExactlyOnceWith("aborted"),e(c.stop).toHaveBeenCalledExactlyOnceWith("aborted"),e(r.active).toEqual([])}),o("rejects shared children, cycles, foreign nodes and reentrant ticks",()=>{const t=new d,o=new c;o.addChild(t),o.addChild(t),e(()=>new n(o)).toThrow("shared or cyclic");const a=new c;a.addChild(a),e(()=>new n(a)).toThrow("shared or cyclic");const i=new n(t);e(()=>i.execute(t)).toThrow("through tree.tick"),e(()=>i.abort(new d)).toThrow("not part"),t.tick.mockImplementationOnce(()=>i.tick(.1)),e(()=>i.tick(.1)).toThrow("reentered"),e(i.active).toEqual([])}),o("starts a fresh guarded execution after interruption",()=>{let t=!0;const o=new d,a=new r(o,()=>t),i=new n(a);i.tick(.1),t=!1,e(i.tick(.1)).toBe(s.FAILURE),e(o.stop).toHaveBeenCalledExactlyOnceWith("aborted"),t=!0,i.tick(.1),e(o.start).toHaveBeenCalledTimes(2)})});/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -12,6 +12,8 @@ export interface BehaviorTreeContext {
|
|
|
12
12
|
retain(node: Node): void;
|
|
13
13
|
}
|
|
14
14
|
export declare abstract class Node {
|
|
15
|
+
/** Optional human-readable label for development inspectors. */
|
|
16
|
+
debugLabel?: string;
|
|
15
17
|
/** dt is elapsed game time in seconds. */
|
|
16
18
|
abstract tick(dt: number, context: BehaviorTreeContext): NodeState;
|
|
17
19
|
/** Called once before the first update of each execution. */
|
|
@@ -76,6 +78,13 @@ export declare abstract class DecoratorNode extends Node {
|
|
|
76
78
|
constructor(child: Node);
|
|
77
79
|
getChildren(): readonly Node[];
|
|
78
80
|
}
|
|
81
|
+
/** Updates shared state before its branch on every active tick, including while tasks run.
|
|
82
|
+
* Services do not select branches or complete tasks; the supplied child owns execution.
|
|
83
|
+
* Use start/stop for service activation and cleanup. */
|
|
84
|
+
export declare abstract class ServiceNode extends DecoratorNode {
|
|
85
|
+
protected abstract update(dt: number, context: BehaviorTreeContext): void;
|
|
86
|
+
tick(dt: number, context: BehaviorTreeContext): NodeState;
|
|
87
|
+
}
|
|
79
88
|
export declare class InverterNode extends DecoratorNode {
|
|
80
89
|
tick(dt: number, context: BehaviorTreeContext): NodeState;
|
|
81
90
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export var NodeState;!function(t){t[t.SUCCESS=0]="SUCCESS",t[t.FAILURE=1]="FAILURE",t[t.RUNNING=2]="RUNNING"}(NodeState||(NodeState={}));export class Node{start(){}stop(t){}getChildren(){return[]}}export class LeafNode extends Node{}export class ActionNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action()}}export class SuccessNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action(),NodeState.SUCCESS}}export class FailNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action(),NodeState.FAILURE}}export class AsyncActionNode extends LeafNode{constructor(t){super(),this.action=t,this.execution=0}start(){this.result=void 0;const t=++this.execution;try{this.action().then(e=>this.finish(t,e),()=>this.finish(t,NodeState.FAILURE))}catch{this.result=NodeState.FAILURE}}tick(){return this.result??NodeState.RUNNING}finish(t,e){t===this.execution&&(this.result=e)}stop(){this.execution++,this.result=void 0}}export class ConditionNode extends LeafNode{constructor(t){super(),this.condition=t}tick(){return this.condition()?NodeState.SUCCESS:NodeState.FAILURE}}export class CompositeNode extends Node{constructor(){super(...arguments),this.children=[]}getChildren(){return this.children}addChild(t){this.children.push(t)}}export class SequenceNode extends CompositeNode{constructor(){super(...arguments),this.current=0}tick(t,e){for(;this.current<this.children.length;){const s=e.execute(this.children[this.current],t);if(s===NodeState.RUNNING)return s;if(s===NodeState.FAILURE)return this.current=0,s;this.current++}return this.current=0,NodeState.SUCCESS}stop(){this.current=0}}export class SelectorNode extends CompositeNode{tick(t,e){for(const s of this.children){const i=e.execute(s,t);if(i!==NodeState.FAILURE)return this.runningChild!==s&&this.runningChild&&e.abort(this.runningChild),this.runningChild=i===NodeState.RUNNING?s:void 0,i;this.runningChild===s&&(this.runningChild=void 0)}return NodeState.FAILURE}stop(){this.runningChild=void 0}}export class DecoratorNode extends Node{constructor(t){super(),this.child=t}getChildren(){return[this.child]}}export class InverterNode extends DecoratorNode{tick(t,e){const s=e.execute(this.child,t);return s===NodeState.SUCCESS?NodeState.FAILURE:s===NodeState.FAILURE?NodeState.SUCCESS:s}}export class RepeatTimesNode extends DecoratorNode{constructor(t,e=1/0){super(t),this.limit=e,this.counter=0}tick(t,e){if(this.counter>=this.limit)return this.counter=0,NodeState.SUCCESS;const s=e.execute(this.child,t);return s===NodeState.RUNNING?s:(this.counter++,this.counter>=this.limit?(this.counter=0,NodeState.SUCCESS):NodeState.RUNNING)}stop(){this.counter=0}}export class RepeatNode extends DecoratorNode{tick(t,e){return e.execute(this.child,t),NodeState.RUNNING}}export class RepeatUntilNode extends DecoratorNode{constructor(t,e){super(t),this.condition=e}tick(t,e){return this.condition()?(e.abort(this.child),NodeState.SUCCESS):e.execute(this.child,t)===NodeState.FAILURE?NodeState.FAILURE:NodeState.RUNNING}}export class RepeatUntilFailNode extends DecoratorNode{tick(t,e){return e.execute(this.child,t)===NodeState.FAILURE?NodeState.FAILURE:NodeState.RUNNING}}export class DelayNode extends DecoratorNode{constructor(t,e){super(t),this.delay=e,this.elapsed=0}tick(t,e){if(this.elapsed+=1e3*t,this.elapsed<this.delay)return NodeState.RUNNING;const s=e.execute(this.child,t);return s!==NodeState.RUNNING&&(this.elapsed=0),s}stop(){this.elapsed=0}}export class DelayAfterNode extends DecoratorNode{constructor(t,e){super(t),this.delay=e,this.elapsed=null,this.state=NodeState.RUNNING}tick(t,e){if(null===this.elapsed){if(this.state=e.execute(this.child,t),this.state===NodeState.RUNNING)return this.state;if(this.elapsed=0,this.delay>0)return NodeState.RUNNING}else this.elapsed+=1e3*t;return this.elapsed<this.delay?NodeState.RUNNING:(this.elapsed=null,this.state)}stop(){this.elapsed=null,this.state=NodeState.RUNNING}}export class WaitNode extends LeafNode{constructor(t,e){super(),this.delayMin=t,this.delayMax=e,this.elapsed=0,this.delay=t}tick(t){return this.elapsed+=1e3*t,this.elapsed<this.delay?NodeState.RUNNING:(this.elapsed=0,NodeState.SUCCESS)}reset(){this.elapsed=0,this.delay=null==this.delayMax?this.delayMin:(this.delayMax-this.delayMin)*Math.random()+this.delayMin}stop(){this.elapsed=0}start(){this.reset()}}export class CooldownNode extends DecoratorNode{constructor(t,e){super(t),this.cooldownTime=e,this.lastExecutionTime=null}tick(t,e){const s=Date.now();if(null!==this.lastExecutionTime&&s-this.lastExecutionTime<this.cooldownTime)return NodeState.FAILURE;const i=e.execute(this.child,t);return i===NodeState.SUCCESS&&(this.lastExecutionTime=s),i}}export class TimerNode extends DecoratorNode{constructor(t,e){super(t),this.duration=e,this.startTime=null}tick(t,e){const s=Date.now();if(this.startTime??(this.startTime=s),s-this.startTime>=this.duration)return e.abort(this.child),NodeState.FAILURE;const i=e.execute(this.child,t);return i!==NodeState.RUNNING&&(this.startTime=null),i}stop(){this.startTime=null}}export class WeightedRandomSelectorNode extends CompositeNode{constructor(t){if(super(),t.some(t=>!Number.isFinite(t)||t<0))throw new Error("Selector weights must be finite and non-negative");if(this.weights=t.slice(),this.totalWeight=this.weights.reduce((t,e)=>t+e,0),!Number.isFinite(this.totalWeight))throw new Error("Selector total weight must be finite")}getRandomIndex(){let t=Math.random()*this.totalWeight;for(let e=0;e<this.weights.length;e++)if(t-=this.weights[e],t<0)return e;for(let t=this.weights.length-1;t>=0;t--)if(this.weights[t]>0)return t;return-1}tick(t,e){if(this.children.length!==this.weights.length||this.totalWeight<=0)return NodeState.FAILURE;this.current??(this.current=this.getRandomIndex());const s=e.execute(this.children[this.current],t);return s!==NodeState.RUNNING&&(this.current=void 0),s}stop(){this.current=void 0}}export class GuardNode extends DecoratorNode{constructor(t,e){super(t),this.condition=e}tick(t,e){return this.condition()?e.execute(this.child,t):(e.abort(this.child),NodeState.FAILURE)}}class t extends CompositeNode{constructor(){super(...arguments),this.states=[]}tickParallel(t,e,s){let i=!1;for(let o=0;o<this.children.length;o++){const r=this.states[o],d=void 0===r||r===NodeState.RUNNING?e.execute(this.children[o],t):r;if(this.states[o]=d,d===s)return s;d===NodeState.RUNNING&&(i=!0)}return i?NodeState.RUNNING:(this.states.length=0,s===NodeState.SUCCESS?NodeState.FAILURE:NodeState.SUCCESS)}stop(){this.states.length=0}}export class ParallelSelectorNode extends t{tick(t,e){return this.tickParallel(t,e,NodeState.SUCCESS)}}export class ParallelSequenceNode extends t{tick(t,e){return this.tickParallel(t,e,NodeState.FAILURE)}}export class ThrottleNode extends DecoratorNode{constructor(t,e){super(t),this.duration=e,this.elapsed=0,this.started=!1}tick(t,e){if(this.elapsed+=t,this.started&&this.elapsed<this.duration)return e.retain(this.child),NodeState.RUNNING;this.started=!0;const s=this.elapsed;this.elapsed=0;const i=e.execute(this.child,s);return i!==NodeState.RUNNING&&(this.started=!1),i}stop(){this.elapsed=0,this.started=!1}}/*
|
|
1
|
+
export var NodeState;!function(t){t[t.SUCCESS=0]="SUCCESS",t[t.FAILURE=1]="FAILURE",t[t.RUNNING=2]="RUNNING"}(NodeState||(NodeState={}));export class Node{start(){}stop(t){}getChildren(){return[]}}export class LeafNode extends Node{}export class ActionNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action()}}export class SuccessNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action(),NodeState.SUCCESS}}export class FailNode extends LeafNode{constructor(t){super(),this.action=t}tick(){return this.action(),NodeState.FAILURE}}export class AsyncActionNode extends LeafNode{constructor(t){super(),this.action=t,this.execution=0}start(){this.result=void 0;const t=++this.execution;try{this.action().then(e=>this.finish(t,e),()=>this.finish(t,NodeState.FAILURE))}catch{this.result=NodeState.FAILURE}}tick(){return this.result??NodeState.RUNNING}finish(t,e){t===this.execution&&(this.result=e)}stop(){this.execution++,this.result=void 0}}export class ConditionNode extends LeafNode{constructor(t){super(),this.condition=t}tick(){return this.condition()?NodeState.SUCCESS:NodeState.FAILURE}}export class CompositeNode extends Node{constructor(){super(...arguments),this.children=[]}getChildren(){return this.children}addChild(t){this.children.push(t)}}export class SequenceNode extends CompositeNode{constructor(){super(...arguments),this.current=0}tick(t,e){for(;this.current<this.children.length;){const s=e.execute(this.children[this.current],t);if(s===NodeState.RUNNING)return s;if(s===NodeState.FAILURE)return this.current=0,s;this.current++}return this.current=0,NodeState.SUCCESS}stop(){this.current=0}}export class SelectorNode extends CompositeNode{tick(t,e){for(const s of this.children){const i=e.execute(s,t);if(i!==NodeState.FAILURE)return this.runningChild!==s&&this.runningChild&&e.abort(this.runningChild),this.runningChild=i===NodeState.RUNNING?s:void 0,i;this.runningChild===s&&(this.runningChild=void 0)}return NodeState.FAILURE}stop(){this.runningChild=void 0}}export class DecoratorNode extends Node{constructor(t){super(),this.child=t}getChildren(){return[this.child]}}export class ServiceNode extends DecoratorNode{tick(t,e){return this.update(t,e),e.execute(this.child,t)}}export class InverterNode extends DecoratorNode{tick(t,e){const s=e.execute(this.child,t);return s===NodeState.SUCCESS?NodeState.FAILURE:s===NodeState.FAILURE?NodeState.SUCCESS:s}}export class RepeatTimesNode extends DecoratorNode{constructor(t,e=1/0){super(t),this.limit=e,this.counter=0}tick(t,e){if(this.counter>=this.limit)return this.counter=0,NodeState.SUCCESS;const s=e.execute(this.child,t);return s===NodeState.RUNNING?s:(this.counter++,this.counter>=this.limit?(this.counter=0,NodeState.SUCCESS):NodeState.RUNNING)}stop(){this.counter=0}}export class RepeatNode extends DecoratorNode{tick(t,e){return e.execute(this.child,t),NodeState.RUNNING}}export class RepeatUntilNode extends DecoratorNode{constructor(t,e){super(t),this.condition=e}tick(t,e){return this.condition()?(e.abort(this.child),NodeState.SUCCESS):e.execute(this.child,t)===NodeState.FAILURE?NodeState.FAILURE:NodeState.RUNNING}}export class RepeatUntilFailNode extends DecoratorNode{tick(t,e){return e.execute(this.child,t)===NodeState.FAILURE?NodeState.FAILURE:NodeState.RUNNING}}export class DelayNode extends DecoratorNode{constructor(t,e){super(t),this.delay=e,this.elapsed=0}tick(t,e){if(this.elapsed+=1e3*t,this.elapsed<this.delay)return NodeState.RUNNING;const s=e.execute(this.child,t);return s!==NodeState.RUNNING&&(this.elapsed=0),s}stop(){this.elapsed=0}}export class DelayAfterNode extends DecoratorNode{constructor(t,e){super(t),this.delay=e,this.elapsed=null,this.state=NodeState.RUNNING}tick(t,e){if(null===this.elapsed){if(this.state=e.execute(this.child,t),this.state===NodeState.RUNNING)return this.state;if(this.elapsed=0,this.delay>0)return NodeState.RUNNING}else this.elapsed+=1e3*t;return this.elapsed<this.delay?NodeState.RUNNING:(this.elapsed=null,this.state)}stop(){this.elapsed=null,this.state=NodeState.RUNNING}}export class WaitNode extends LeafNode{constructor(t,e){super(),this.delayMin=t,this.delayMax=e,this.elapsed=0,this.delay=t}tick(t){return this.elapsed+=1e3*t,this.elapsed<this.delay?NodeState.RUNNING:(this.elapsed=0,NodeState.SUCCESS)}reset(){this.elapsed=0,this.delay=null==this.delayMax?this.delayMin:(this.delayMax-this.delayMin)*Math.random()+this.delayMin}stop(){this.elapsed=0}start(){this.reset()}}export class CooldownNode extends DecoratorNode{constructor(t,e){super(t),this.cooldownTime=e,this.lastExecutionTime=null}tick(t,e){const s=Date.now();if(null!==this.lastExecutionTime&&s-this.lastExecutionTime<this.cooldownTime)return NodeState.FAILURE;const i=e.execute(this.child,t);return i===NodeState.SUCCESS&&(this.lastExecutionTime=s),i}}export class TimerNode extends DecoratorNode{constructor(t,e){super(t),this.duration=e,this.startTime=null}tick(t,e){const s=Date.now();if(this.startTime??(this.startTime=s),s-this.startTime>=this.duration)return e.abort(this.child),NodeState.FAILURE;const i=e.execute(this.child,t);return i!==NodeState.RUNNING&&(this.startTime=null),i}stop(){this.startTime=null}}export class WeightedRandomSelectorNode extends CompositeNode{constructor(t){if(super(),t.some(t=>!Number.isFinite(t)||t<0))throw new Error("Selector weights must be finite and non-negative");if(this.weights=t.slice(),this.totalWeight=this.weights.reduce((t,e)=>t+e,0),!Number.isFinite(this.totalWeight))throw new Error("Selector total weight must be finite")}getRandomIndex(){let t=Math.random()*this.totalWeight;for(let e=0;e<this.weights.length;e++)if(t-=this.weights[e],t<0)return e;for(let t=this.weights.length-1;t>=0;t--)if(this.weights[t]>0)return t;return-1}tick(t,e){if(this.children.length!==this.weights.length||this.totalWeight<=0)return NodeState.FAILURE;this.current??(this.current=this.getRandomIndex());const s=e.execute(this.children[this.current],t);return s!==NodeState.RUNNING&&(this.current=void 0),s}stop(){this.current=void 0}}export class GuardNode extends DecoratorNode{constructor(t,e){super(t),this.condition=e}tick(t,e){return this.condition()?e.execute(this.child,t):(e.abort(this.child),NodeState.FAILURE)}}class t extends CompositeNode{constructor(){super(...arguments),this.states=[]}tickParallel(t,e,s){let i=!1;for(let o=0;o<this.children.length;o++){const r=this.states[o],d=void 0===r||r===NodeState.RUNNING?e.execute(this.children[o],t):r;if(this.states[o]=d,d===s)return s;d===NodeState.RUNNING&&(i=!0)}return i?NodeState.RUNNING:(this.states.length=0,s===NodeState.SUCCESS?NodeState.FAILURE:NodeState.SUCCESS)}stop(){this.states.length=0}}export class ParallelSelectorNode extends t{tick(t,e){return this.tickParallel(t,e,NodeState.SUCCESS)}}export class ParallelSequenceNode extends t{tick(t,e){return this.tickParallel(t,e,NodeState.FAILURE)}}export class ThrottleNode extends DecoratorNode{constructor(t,e){super(t),this.duration=e,this.elapsed=0,this.started=!1}tick(t,e){if(this.elapsed+=t,this.started&&this.elapsed<this.duration)return e.retain(this.child),NodeState.RUNNING;this.started=!0;const s=this.elapsed;this.elapsed=0;const i=e.execute(this.child,s);return i!==NodeState.RUNNING&&(this.started=!1),i}stop(){this.elapsed=0,this.started=!1}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{BehaviorTree as e}from"./behavior-tree.js";import{describe as t,expect as n,it as o,vi as s}from"vitest";import{AsyncActionNode as a,DelayAfterNode as
|
|
1
|
+
import{BehaviorTree as e}from"./behavior-tree.js";import{describe as t,expect as n,it as o,vi as s}from"vitest";import{AsyncActionNode as a,DelayAfterNode as i,DelayNode as r,GuardNode as c,Node as l,NodeState as d,ParallelSelectorNode as w,ParallelSequenceNode as C,RepeatTimesNode as S,RepeatUntilNode as u,SelectorNode as p,SequenceNode as m,ServiceNode as B,ThrottleNode as N,TimerNode as U,WaitNode as h,WeightedRandomSelectorNode as k}from"./bt.js";class E extends l{constructor(e=d.RUNNING){super(),this.state=e,this.tick=s.fn(e=>this.state),this.start=s.fn(),this.stop=s.fn()}}const R=new WeakMap;function v(t){let n=R.get(t);return n||(n=new e(t),R.set(t,n)),n}function I(e,t=0){return v(e).tick(t)}function f(e,...t){return t.forEach(t=>e.addChild(t)),e}t("behavior tree execution",()=>{o("resumes a sequence and only reports success after all steps complete",()=>{const e=new E(d.SUCCESS),t=new E,o=new E(d.SUCCESS),s=f(new m,e,t,o);n(I(s,.1)).toBe(d.RUNNING),n(o.tick).not.toHaveBeenCalled(),t.state=d.SUCCESS,n(I(s,.1)).toBe(d.SUCCESS),n(e.tick).toHaveBeenCalledTimes(1),n(o.tick).toHaveBeenCalledTimes(1)}),o("restarts interrupted sequences from the beginning",()=>{const e=new E(d.SUCCESS),t=new E,o=f(new m,e,t);I(o,.1),v(o).abort(),n(t.stop).toHaveBeenCalledTimes(1),I(o,.1),n(e.tick).toHaveBeenCalledTimes(2)}),o("keeps selectors reactive and aborts the displaced branch once",()=>{const e=new E(d.FAILURE),t=new E,o=f(new p,e,t);I(o,.1),e.state=d.RUNNING,I(o,.1),I(o,.1),n(t.stop).toHaveBeenCalledTimes(1),n(t.tick).toHaveBeenCalledTimes(1),v(o).abort(),n(e.stop).toHaveBeenLastCalledWith("aborted")}),o("retains random choices and rejects unusable weights safely",()=>{const e=s.spyOn(Math,"random").mockReturnValue(0);try{const t=new E,o=new E,s=f(new k([1,1]),t,o);I(s,.1),e.mockReturnValue(.9),I(s,.1),n(o.tick).not.toHaveBeenCalled(),t.state=d.SUCCESS,I(s,.1),I(s,.1),n(o.tick).toHaveBeenCalledTimes(1),n(I(new k([]),.1)).toBe(d.FAILURE),n(()=>new k([-1])).toThrow()}finally{e.mockRestore()}}),o("does not repeat terminal parallel children and cancels unfinished siblings",()=>{const e=new E(d.SUCCESS),t=new E,o=f(new C,e,t);I(o,.1),I(o,.1),n(e.tick).toHaveBeenCalledTimes(1),t.state=d.FAILURE,n(I(o,.1)).toBe(d.FAILURE),e.state=d.RUNNING,t.state=d.SUCCESS;const s=f(new w,e,t);n(I(s,.1)).toBe(d.SUCCESS),n(e.stop).toHaveBeenLastCalledWith("aborted");const a=new E(d.FAILURE),i=new E,r=f(new w,a,i);I(r,.1),I(r,.1),n(a.tick).toHaveBeenCalledTimes(1)}),o("cancels guarded and repeated tasks when their conditions change",()=>{let e=!0;const t=new E,o=new c(t,()=>e);I(o,.1),e=!1,I(o,.1),I(o,.1),n(t.stop).toHaveBeenCalledTimes(1);const s=new u(t,()=>e);I(s,.1),e=!0,n(I(s,.1)).toBe(d.SUCCESS),n(t.stop).toHaveBeenCalledTimes(2)}),o("clears interrupted waits and does not return a stale delay result",()=>{const e=new h(100);I(e,.06),v(e).abort(),n(I(e,.06)).toBe(d.RUNNING),n(I(e,.04)).toBe(d.SUCCESS);const t=new r(new E(d.SUCCESS),100);n(I(t,.1)).toBe(d.SUCCESS),n(I(t,.05)).toBe(d.RUNNING),v(t).abort(),n(I(t,.05)).toBe(d.RUNNING)}),o("delays after completion without counting time before completion",()=>{const e=new i(new E(d.FAILURE),100);n(I(e,1)).toBe(d.RUNNING),n(I(e,.05)).toBe(d.RUNNING),n(I(e,.05)).toBe(d.FAILURE),n(I(e,1)).toBe(d.RUNNING)}),o("bounds repeat work even with the default infinite limit",()=>{const e=new E(d.SUCCESS);n(I(new S(e),.1)).toBe(d.RUNNING),n(e.tick).toHaveBeenCalledTimes(1);const t=new S(e,2);n(I(t,.1)).toBe(d.RUNNING),n(I(t,.1)).toBe(d.SUCCESS)}),o("passes accumulated elapsed time to throttled running tasks",()=>{const e=new E,t=new N(e,.3);I(t,.1),I(t,.1),I(t,.1),I(t,.1),n(e.tick).toHaveBeenCalledTimes(2),n(e.tick.mock.calls[1][0]).toBeCloseTo(.3),v(t).abort(),n(e.stop).toHaveBeenCalledTimes(1)}),o("resets timer deadlines after completion and aborts on timeout",()=>{const e=s.spyOn(Date,"now").mockReturnValue(0);try{const t=new E(d.SUCCESS),o=new U(t,100);n(I(o,.1)).toBe(d.SUCCESS),e.mockReturnValue(200),t.state=d.RUNNING,n(I(o,.1)).toBe(d.RUNNING),e.mockReturnValue(300),n(I(o,.1)).toBe(d.FAILURE),n(t.stop).toHaveBeenLastCalledWith("aborted")}finally{e.mockRestore()}}),o("starts async work once and ignores completion from an aborted execution",async()=>{let e;const t=s.fn(()=>new Promise(t=>{e=t})),o=new a(t);I(o),I(o),n(t).toHaveBeenCalledTimes(1);const i=e;v(o).abort(),I(o),i(d.SUCCESS),await Promise.resolve(),n(I(o)).toBe(d.RUNNING),e(d.FAILURE),await Promise.resolve(),n(I(o)).toBe(d.FAILURE),I(o),n(t).toHaveBeenCalledTimes(3)}),o("reports async rejections and synchronous throws as failure",async()=>{const e=new a(()=>Promise.reject(new Error("failed")));I(e),await Promise.resolve(),n(I(e)).toBe(d.FAILURE),n(I(new a(()=>{throw new Error("failed")}))).toBe(d.FAILURE)})}),t("branch services",()=>{o("updates before a running child and cleans up on interruption",()=>{const t=[];const o=new class extends B{start(){t.push("activate")}update(e){t.push("update:"+e)}stop(){t.push("deactivate")}}(new class extends l{tick(){return t.push("task"),d.RUNNING}stop(){t.push("task stopped")}}),s=new e(o);s.tick(.1),s.tick(.2),s.abort(),n(t).toEqual(["activate","update:0.1","task","update:0.2","task","task stopped","deactivate"])}),o("runs while its branch is selected and propagates completion",()=>{let t=!0,o=0;const s=new E(d.SUCCESS),a=new e(new c(new class extends B{update(){o++}}(s),()=>t));n(a.tick(.1)).toBe(d.SUCCESS),t=!1,n(a.tick(.1)).toBe(d.FAILURE),n(o).toBe(1),n(s.tick).toHaveBeenCalledTimes(1)})});/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AnimationClip } from 'three';
|
|
2
|
+
import type { Object3D, VectorKeyframeTrack } from 'three';
|
|
3
|
+
import type { AnimationFootContacts } from './foot-contact.js';
|
|
4
|
+
export type FootContactBakeOptions = {
|
|
5
|
+
samplesPerSecond?: number;
|
|
6
|
+
/** Height above each foot's lowest sampled position, in presentation-space units. */
|
|
7
|
+
heightThreshold?: number;
|
|
8
|
+
maxVerticalSpeed?: number;
|
|
9
|
+
minimumContactTime?: number;
|
|
10
|
+
/** Restore authored root travel while sampling a fixed-in-place RootMotionClip. */
|
|
11
|
+
rootMotionTrack?: VectorKeyframeTrack;
|
|
12
|
+
};
|
|
13
|
+
/** Clip preprocessing: uses an isolated rig, never seeks the live character mixer. */
|
|
14
|
+
export declare class FootContactBaker {
|
|
15
|
+
private readonly root;
|
|
16
|
+
private readonly left;
|
|
17
|
+
private readonly right;
|
|
18
|
+
private readonly mixer;
|
|
19
|
+
private readonly point;
|
|
20
|
+
constructor(root: Object3D, leftFootName: string, rightFootName: string);
|
|
21
|
+
bake(clip: AnimationClip, options?: FootContactBakeOptions): AnimationFootContacts | undefined;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=foot-contact-baker.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{AnimationClip as t,AnimationMixer as r,LoopOnce as o,Vector3 as i}from"three";import{clone as e}from"three/addons/utils/SkeletonUtils.js";export class FootContactBaker{constructor(t,o,n){this.point=new i,this.root=e(t),this.left=this.root.getObjectByName(o),this.right=this.root.getObjectByName(n),this.mixer=new r(this.root)}bake(r,i={}){if(null==this.left||null==this.right||!(r.duration>0))return;const e=Math.max(8,Math.ceil(r.duration*(i.samplesPerSecond??60))),a=r.duration/e,s=[new Float32Array(e),new Float32Array(e)],h=null!=i.rootMotionTrack?[...r.tracks.filter(t=>t.name!==i.rootMotionTrack.name),i.rootMotionTrack]:r.tracks,l=new t(r.name,r.duration,h),m=this.mixer.clipAction(l);m.setLoop(o,1).play();try{for(let t=0;t<e;t++)this.mixer.setTime(t*a),this.root.updateMatrixWorld(!0),s[0][t]=this.point.setFromMatrixPosition(this.left.matrixWorld).y,s[1][t]=this.point.setFromMatrixPosition(this.right.matrixWorld).y;return{left:n(s[0],a,i),right:n(s[1],a,i)}}finally{m.stop(),this.mixer.uncacheClip(l)}}}function n(t,r,o){let i=1/0;for(const r of t)i=Math.min(i,r);const e=Math.max(0,o.heightThreshold??.045),n=Math.max(0,o.maxVerticalSpeed??.5),a=t.length,s=new Uint8Array(a);let h=0;for(let o=0;o<a;o++){const l=t[(o+a-1)%a],m=t[(o+1)%a],c=Math.abs(m-l)/(2*r);s[o]=t[o]<=i+e&&c<=n?1:0,h+=s[o]}if(h===a)return[{start:0,end:1}];if(0===h)return[];const l=s.indexOf(0),m=[],c=Math.max(2,Math.ceil((o.minimumContactTime??.05)/r));let u=-1;for(let t=1;t<=a;t++){const r=(l+t)%a;0!==s[r]?u<0&&(u=t):u>=0&&(t-u>=c&&m.push({start:(l+u)%a/a,end:r/a}),u=-1)}return m}/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import type { AnimationClip } from 'three';
|
|
3
|
+
/** Normalized clip phases. start > end denotes a contact crossing the loop seam. */
|
|
4
|
+
export type FootContactInterval = {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
};
|
|
8
|
+
export type AnimationFootContacts = {
|
|
9
|
+
left: readonly FootContactInterval[];
|
|
10
|
+
right: readonly FootContactInterval[];
|
|
11
|
+
};
|
|
12
|
+
export declare function getAnimationFootContacts(clip: AnimationClip | undefined): AnimationFootContacts | undefined;
|
|
13
|
+
/** Can also be used to replace generated contacts with authored phase intervals. */
|
|
14
|
+
export declare function setAnimationFootContacts(clip: AnimationClip, contacts: AnimationFootContacts | undefined): void;
|
|
15
|
+
export declare function isFootInContact(intervals: readonly FootContactInterval[], time: number, duration: number, looping: boolean): boolean;
|
|
16
|
+
/** Stateful stance anchor with bounded release. All inputs are world-space. */
|
|
17
|
+
export declare class FootContactAnchor {
|
|
18
|
+
readonly position: Vector3;
|
|
19
|
+
readonly forward: Vector3;
|
|
20
|
+
locked: boolean;
|
|
21
|
+
private blocked;
|
|
22
|
+
reset(): void;
|
|
23
|
+
/** Prevent recapture until the next swing, instead of repeatedly tugging an unreachable foot. */
|
|
24
|
+
release(): void;
|
|
25
|
+
update(contact: boolean, hasGround: boolean, animatedFoot: Vector3, groundTarget: Vector3, facing: Vector3, hip: Vector3, maxReach: number, maxPelvisDrop: number, maxDrift: number, maxTurnAngle: number): boolean;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=foot-contact.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{MathUtils as t,Vector3 as e}from"three";const o=new WeakMap;export function getAnimationFootContacts(t){return null==t?void 0:o.get(t)}export function setAnimationFootContacts(t,e){if(null!=e){for(const t of["left","right"])for(const o of e[t])if(!Number.isFinite(o.start)||!Number.isFinite(o.end)||o.start<0||o.start>1||o.end<0||o.end>1)throw new Error("Foot contact intervals must use phases between 0 and 1");o.set(t,e)}else o.delete(t)}export function isFootInContact(e,o,i,s){if(!(i>0&&Number.isFinite(o)))return!1;const r=s?t.euclideanModulo(o/i,1):t.clamp(o/i,0,1);for(const t of e){if(0===t.start&&1===t.end)return!0;if(t.start<t.end){if(r>=t.start&&r<t.end)return!0}else if(t.start>t.end&&(r>=t.start||r<t.end))return!0}return!1}export class FootContactAnchor{constructor(){this.position=new e,this.forward=new e(0,0,1),this.locked=!1,this.blocked=!1}reset(){this.locked=!1,this.blocked=!1}release(){this.locked=!1,this.blocked=!0}update(t,e,o,i,s,r,n,a,c,h){if(!t)return this.reset(),!1;if(!e)return this.release(),!1;if(this.blocked)return!1;this.locked||(this.position.copy(i),this.forward.copy(s).setY(0).normalize(),this.locked=!0);const d=o.x-this.position.x,l=o.z-this.position.z,f=r.x-this.position.x,u=r.z-this.position.z,p=Math.max(0,r.y-this.position.y-Math.max(0,a)),x=Math.sqrt(s.x*s.x+s.z*s.z),m=x>1e-6?(s.x*this.forward.x+s.z*this.forward.z)/x:1;return(d*d+l*l>c*c||f*f+p*p+u*u>n*n||m<Math.cos(h))&&this.release(),this.locked}}/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import type { Bone, Object3D } from 'three';
|
|
3
|
+
/** Visual response only: this never changes the character controller or root motion. */
|
|
4
|
+
export declare class LocomotionBodyResponse {
|
|
5
|
+
maxLean: number;
|
|
6
|
+
maxPitch: number;
|
|
7
|
+
maxTwist: number;
|
|
8
|
+
/** Reference acceleration for art-directed lean, in world units/s². */
|
|
9
|
+
referenceAcceleration: number;
|
|
10
|
+
accelerationHalfLife: number;
|
|
11
|
+
/** Approximate time to settle 95% of a procedural offset. */
|
|
12
|
+
settleTime: number;
|
|
13
|
+
turnLag: number;
|
|
14
|
+
resetDistance: number;
|
|
15
|
+
maxDeltaTime: number;
|
|
16
|
+
readonly acceleration: Vector3;
|
|
17
|
+
private readonly previousVelocity;
|
|
18
|
+
private readonly previousPosition;
|
|
19
|
+
private readonly pitchSpring;
|
|
20
|
+
private readonly rollSpring;
|
|
21
|
+
private readonly yawSpring;
|
|
22
|
+
private previousHeading;
|
|
23
|
+
private yawRate;
|
|
24
|
+
private initialized;
|
|
25
|
+
get pitch(): number;
|
|
26
|
+
get roll(): number;
|
|
27
|
+
get yaw(): number;
|
|
28
|
+
reset(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Inputs are in world space. Forward is the character's horizontal facing,
|
|
31
|
+
* independent of velocity (so this also supports strafing).
|
|
32
|
+
* Inactive states discard momentum; zero time holds it for hit stop/pause.
|
|
33
|
+
*/
|
|
34
|
+
update(deltaTime: number, position: Vector3, velocity: Vector3, forward: Vector3, active?: boolean): void;
|
|
35
|
+
}
|
|
36
|
+
export type LocomotionBodyBone = {
|
|
37
|
+
bone: Bone;
|
|
38
|
+
/** Contribution to body pitch/roll; negative values provide counter-rotation. */
|
|
39
|
+
leanWeight: number;
|
|
40
|
+
/** Contribution to yaw lag. */
|
|
41
|
+
twistWeight: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Applies offsets in a character presentation frame (+Y up, +Z forward),
|
|
45
|
+
* regardless of bone-local bind axes. Bind once, restore before mixer evaluation,
|
|
46
|
+
* then apply before IK. Only rotations are owned; animated translations survive.
|
|
47
|
+
*/
|
|
48
|
+
export declare class LocomotionBodyPose {
|
|
49
|
+
private readonly bones;
|
|
50
|
+
private readonly parentRotation;
|
|
51
|
+
private readonly inverseParentRotation;
|
|
52
|
+
private readonly offset;
|
|
53
|
+
private readonly tempRotation;
|
|
54
|
+
private readonly right;
|
|
55
|
+
private readonly up;
|
|
56
|
+
private readonly forward;
|
|
57
|
+
private applied;
|
|
58
|
+
constructor(space: Object3D, bones: readonly LocomotionBodyBone[]);
|
|
59
|
+
restore(): void;
|
|
60
|
+
apply(response: LocomotionBodyResponse, weight?: number): void;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=locomotion-body.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{MathUtils as t,Quaternion as i,Vector3 as e}from"three";export class LocomotionBodyResponse{constructor(){this.maxLean=t.degToRad(12),this.maxPitch=t.degToRad(8),this.maxTwist=t.degToRad(6),this.referenceAcceleration=12,this.accelerationHalfLife=.07,this.settleTime=.22,this.turnLag=.025,this.resetDistance=1,this.maxDeltaTime=.2,this.acceleration=new e,this.previousVelocity=new e,this.previousPosition=new e,this.pitchSpring=new s,this.rollSpring=new s,this.yawSpring=new s,this.previousHeading=0,this.yawRate=0,this.initialized=!1}get pitch(){return this.pitchSpring.value}get roll(){return this.rollSpring.value}get yaw(){return this.yawSpring.value}reset(){this.initialized=!1,this.acceleration.set(0,0,0),this.yawRate=0,this.pitchSpring.reset(),this.rollSpring.reset(),this.yawSpring.reset()}update(i,e,s,a,o=!0){if(!o||!Number.isFinite(i)||i<0)return void this.reset();if(0===i)return;const h=Math.atan2(a.x,a.z),n=Math.sqrt(s.x*s.x+s.z*s.z),r=this.resetDistance+n*i;if(!this.initialized||i>this.maxDeltaTime||this.previousPosition.distanceToSquared(e)>r*r)return this.reset(),this.previousVelocity.copy(s).setY(0),this.previousPosition.copy(e),this.previousHeading=h,void(this.initialized=!0);const l=1-Math.exp(-Math.LN2*i/Math.max(1e-4,this.accelerationHalfLife));this.acceleration.x+=((s.x-this.previousVelocity.x)/i-this.acceleration.x)*l,this.acceleration.z+=((s.z-this.previousVelocity.z)/i-this.acceleration.z)*l;const p=Math.atan2(Math.sin(h-this.previousHeading),Math.cos(h-this.previousHeading));this.yawRate+=(p/i-this.yawRate)*l,this.previousHeading=h,this.previousVelocity.copy(s).setY(0),this.previousPosition.copy(e);const c=Math.sqrt(a.x*a.x+a.z*a.z),u=c>1e-6?a.x/c:0,m=c>1e-6?a.z/c:1,y=this.acceleration.x*m-this.acceleration.z*u,v=this.acceleration.x*u+this.acceleration.z*m,x=Math.max(1e-4,this.referenceAcceleration),d=t.clamp(-Math.atan2(y,x),-this.maxLean,this.maxLean),f=t.clamp(.5*Math.atan2(v,x),-this.maxPitch,this.maxPitch),g=t.clamp(-this.yawRate*this.turnLag*Math.min(1,n),-this.maxTwist,this.maxTwist);this.rollSpring.update(d,this.settleTime,i,this.maxLean),this.pitchSpring.update(f,this.settleTime,i,this.maxPitch),this.yawSpring.update(g,this.settleTime,i,this.maxTwist)}}class s{constructor(){this.value=0,this.velocity=0}reset(){this.value=0,this.velocity=0}update(i,e,s,a){const o=4.75/Math.max(1e-4,e),h=this.value-i,n=this.velocity+h*o,r=Math.exp(-o*s);this.value=i+(h+n*s)*r,this.velocity=(this.velocity-n*o*s)*r;const l=Math.max(0,a);Math.abs(this.value)>l&&(this.value=t.clamp(this.value,-l,l),this.value*this.velocity>0&&(this.velocity=0))}}export class LocomotionBodyPose{constructor(t,s){this.parentRotation=new i,this.inverseParentRotation=new i,this.offset=new i,this.tempRotation=new i,this.right=new e(1,0,0),this.up=new e(0,1,0),this.forward=new e(0,0,1),this.applied=!1,this.bones=s.map(e=>{const s=[];let a=e.bone.parent;for(;null!=a&&a!==t;)s.push(a),a=a.parent;if(a!==t)throw new Error("Locomotion body bone must descend from its presentation frame");return{...e,baseQuaternion:new i,parentPath:s.reverse()}}),this.bones.sort((t,i)=>t.parentPath.length-i.parentPath.length)}restore(){if(this.applied){for(const t of this.bones)t.bone.quaternion.copy(t.baseQuaternion);this.applied=!1}}apply(i,e=1){this.restore();const s=t.clamp(e,0,1);if(0!==s&&(0!==i.pitch||0!==i.roll||0!==i.yaw)){for(const t of this.bones){t.baseQuaternion.copy(t.bone.quaternion),this.parentRotation.identity();for(const i of t.parentPath)this.parentRotation.multiply(i.quaternion);this.inverseParentRotation.copy(this.parentRotation).invert(),this.offset.setFromAxisAngle(this.up,i.yaw*t.twistWeight*s),this.tempRotation.setFromAxisAngle(this.right,i.pitch*t.leanWeight*s),this.offset.multiply(this.tempRotation),this.tempRotation.setFromAxisAngle(this.forward,i.roll*t.leanWeight*s),this.offset.multiply(this.tempRotation),this.offset.premultiply(this.inverseParentRotation).multiply(this.parentRotation),t.bone.quaternion.premultiply(this.offset).normalize()}this.applied=!0}}}/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AnimationClip, Bone } from 'three';
|
|
2
|
+
export type InertializationBone = {
|
|
3
|
+
bone: Bone;
|
|
4
|
+
/** Seconds until the rotation correction reaches exactly zero. */
|
|
5
|
+
duration: number;
|
|
6
|
+
/** Maximum rotation correction in radians. */
|
|
7
|
+
maxRotation: number;
|
|
8
|
+
/** Maximum outgoing angular speed to preserve, in radians/second. */
|
|
9
|
+
maxAngularSpeed?: number;
|
|
10
|
+
/** Optional translation correction, in bone-parent space units. */
|
|
11
|
+
maxPosition?: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Transition-only pose corrections. Restore before the mixer, update after it
|
|
15
|
+
* and before body response/IK. History contains this stage's output, never IK.
|
|
16
|
+
*/
|
|
17
|
+
export declare class PoseInertializer {
|
|
18
|
+
private readonly bones;
|
|
19
|
+
private readonly samplers;
|
|
20
|
+
private readonly quaternion;
|
|
21
|
+
private readonly targetQuaternion;
|
|
22
|
+
private readonly targetPosition;
|
|
23
|
+
private readonly vector;
|
|
24
|
+
private readonly cross;
|
|
25
|
+
private readonly crossCross;
|
|
26
|
+
private elapsed;
|
|
27
|
+
private transitionDuration;
|
|
28
|
+
private samples;
|
|
29
|
+
private applied;
|
|
30
|
+
constructor(bones: readonly InertializationBone[]);
|
|
31
|
+
get isTransitioning(): boolean;
|
|
32
|
+
get hasHistory(): boolean;
|
|
33
|
+
/** Clears history/corrections, retaining the pending before-mixer restoration. */
|
|
34
|
+
reset(): void;
|
|
35
|
+
restore(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Prepare before the mixer switches actions. Incoming velocity is sampled
|
|
38
|
+
* using independent track interpolants, without ticking/seeking the live mixer.
|
|
39
|
+
*/
|
|
40
|
+
transitionTo(clip: AnimationClip, time?: number, timeScale?: number, looping?: boolean): boolean;
|
|
41
|
+
update(deltaTime: number): void;
|
|
42
|
+
private readonly sampleQuaternion;
|
|
43
|
+
private quaternionFromSample;
|
|
44
|
+
private getSamplers;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=pose-inertialization.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{MathUtils as t,PropertyBinding as o,Quaternion as i,Vector3 as e}from"three";export class PoseInertializer{constructor(t){this.samplers=new WeakMap,this.quaternion=new i,this.targetQuaternion=new i,this.targetPosition=new e,this.vector=new e,this.cross=new e,this.crossCross=new e,this.elapsed=1/0,this.transitionDuration=0,this.samples=0,this.applied=!1,this.sampleQuaternion=new i,this.bones=t.map(t=>({profile:t,bone:t.bone,baseQuaternion:new i,basePosition:new e,outputQuaternion:new i,outputPosition:new e,angularVelocity:new e,linearVelocity:new e,rotationOffset:new e,rotationVelocity:new e,positionOffset:new e,positionVelocity:new e,positionApplied:!1}))}get isTransitioning(){return this.elapsed<this.transitionDuration}get hasHistory(){return this.samples>=2}reset(){this.samples=0,this.elapsed=1/0,this.transitionDuration=0;for(const t of this.bones)t.angularVelocity.set(0,0,0),t.linearVelocity.set(0,0,0)}restore(){if(this.applied){for(const t of this.bones)t.bone.quaternion.copy(t.baseQuaternion),t.positionApplied&&t.bone.position.copy(t.basePosition);this.applied=!1}}transitionTo(t,o=0,i=1,e=!0){if(!this.hasHistory||!(t.duration>0)||!Number.isFinite(i)||i<0)return!1;const a=this.getSamplers(t),l=1/240,p=n(o,t.duration,e),u=n(o+l*i,t.duration,e);this.transitionDuration=0;for(let t=0;t<this.bones.length;t++){const o=this.bones[t],i=a[t];if(o.rotationOffset.set(0,0,0),o.rotationVelocity.set(0,0,0),o.positionOffset.set(0,0,0),o.positionVelocity.set(0,0,0),null!=i.rotation&&o.profile.duration>0){this.targetQuaternion.fromArray(i.rotation.evaluate(p)).normalize(),this.quaternion.copy(this.targetQuaternion).invert().premultiply(o.outputQuaternion),s(this.quaternion,o.rotationOffset),o.rotationOffset.clampLength(0,Math.max(0,o.profile.maxRotation)),r(o.rotationOffset,this.quaternion),this.targetQuaternion.invert().premultiply(this.quaternionFromSample(i.rotation,u)),s(this.targetQuaternion,this.vector),this.vector.multiplyScalar(240).applyQuaternion(this.quaternion),o.rotationVelocity.copy(o.angularVelocity).sub(this.vector);const t=o.rotationOffset.lengthSq(),e=Math.sqrt(t),n=t<1e-6?1/12+t/720:(1-e/2/Math.tan(e/2))/t;this.cross.crossVectors(o.rotationOffset,o.rotationVelocity),this.crossCross.crossVectors(o.rotationOffset,this.cross),o.rotationVelocity.addScaledVector(this.cross,-.5).addScaledVector(this.crossCross,n),this.transitionDuration=Math.max(this.transitionDuration,o.profile.duration)}null!=i.position&&(o.profile.maxPosition??0)>0&&o.profile.duration>0&&(this.targetPosition.fromArray(i.position.evaluate(p)),o.positionOffset.subVectors(o.outputPosition,this.targetPosition),o.positionOffset.clampLength(0,o.profile.maxPosition),this.vector.fromArray(i.position.evaluate(u)).sub(this.targetPosition).multiplyScalar(240),o.positionVelocity.copy(o.linearVelocity).sub(this.vector),this.transitionDuration=Math.max(this.transitionDuration,o.profile.duration))}return this.elapsed=0,this.transitionDuration>0}update(t){if(!Number.isFinite(t)||t<0||t>.2)return void this.reset();t>0&&(this.elapsed+=t);let o=!1;for(const i of this.bones){if(i.baseQuaternion.copy(i.bone.quaternion),i.basePosition.copy(i.bone.position),i.positionApplied=!1,this.elapsed<i.profile.duration){const t=this.elapsed/i.profile.duration,e=t*t*t,n=e*t,s=n*t,a=1-10*e+15*n-6*s,l=(t-6*e+8*n-3*s)*i.profile.duration;this.vector.copy(i.rotationOffset).multiplyScalar(a).addScaledVector(i.rotationVelocity,l),this.vector.clampLength(0,Math.max(0,i.profile.maxRotation)),r(this.vector,this.quaternion),i.bone.quaternion.premultiply(this.quaternion).normalize(),(i.profile.maxPosition??0)>0&&(this.vector.copy(i.positionOffset).multiplyScalar(a).addScaledVector(i.positionVelocity,l),this.vector.clampLength(0,i.profile.maxPosition),i.bone.position.add(this.vector),i.positionApplied=!0),o=!0}t>0&&(this.samples>0&&(this.quaternion.copy(i.outputQuaternion).invert().premultiply(i.bone.quaternion),s(this.quaternion,i.angularVelocity),i.angularVelocity.multiplyScalar(1/t).clampLength(0,i.profile.maxAngularSpeed??12),i.linearVelocity.subVectors(i.bone.position,i.outputPosition).divideScalar(t)),i.outputQuaternion.copy(i.bone.quaternion),i.outputPosition.copy(i.bone.position))}this.applied=o,t>0&&(this.samples=Math.min(2,this.samples+1))}quaternionFromSample(t,o){return this.sampleQuaternion.fromArray(t.evaluate(o)).normalize()}getSamplers(t){const i=this.samplers.get(t);if(null!=i)return i;const e=this.bones.map(()=>({})),n=new Map(this.bones.map((t,o)=>[t.bone.name,o]));for(const i of t.tracks){const t=o.parseTrackName(i.name),s="bones"===t.objectName?t.objectIndex:t.nodeName,r=null!=s?n.get(s):void 0;null!=r&&("quaternion"===t.propertyName?e[r].rotation=i.createInterpolant():"position"===t.propertyName&&(e[r].position=i.createInterpolant()))}return this.samplers.set(t,e),e}}function n(o,i,e){return e?t.euclideanModulo(o,i):t.clamp(o,0,i)}function s(t,o){const i=t.w<0?-1:1,e=Math.sqrt(t.x**2+t.y**2+t.z**2),n=e<1e-8?2*i:2*Math.atan2(e,i*t.w)*i/e;return o.set(t.x*n,t.y*n,t.z*n)}function r(t,o){const i=t.length(),e=i<1e-8?.5:Math.sin(i/2)/i;return o.set(t.x*e,t.y*e,t.z*e,Math.cos(i/2)).normalize()}/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AnimationClip as t,Vector3 as o,Quaternion as e,Matrix3 as n,Matrix4 as r}from"three";import{getAnimationClipRigId as i,setAnimationClipRigId as a}from"./retarget.js";export class RootMotionClip extends t{constructor(){super(...arguments),this.displacement=0,this.fixedInPlace=!1}static fromClip(t,
|
|
1
|
+
import{AnimationClip as t,Vector3 as o,Quaternion as e,Matrix3 as n,Matrix4 as r}from"three";import{getAnimationClipRigId as i,setAnimationClipRigId as a}from"./retarget.js";import{getAnimationFootContacts as s,setAnimationFootContacts as l}from"./foot-contact.js";export class RootMotionClip extends t{constructor(){super(...arguments),this.displacement=0,this.fixedInPlace=!1}static fromClip(t,c=!1,u,d){const m=new RootMotionClip(t.name,t.duration,t.tracks.slice(),t.blendMode);if(m.fixedInPlace=c,m.rootBone=u,m.animationRoot=d,m.source=t,m.uuid=t.uuid,a(m,i(t)),l(m,s(t)),m.motionTrack=null!=u?t.tracks.find(t=>t.name===`${u.name}.position`):t.tracks.find(t=>t.name.endsWith(".position")),m.motionTrack){m.tracks.splice(m.tracks.indexOf(m.motionTrack),1);const t=(new o).fromArray(m.motionTrack.values,0),i=(new o).fromArray(m.motionTrack.values,m.motionTrack.values.length-3),a=new o(0,1,0);if(null!=u?.parent){u.parent.updateWorldMatrix(!0,!1);const t=u.parent.getWorldQuaternion(new e);a.applyQuaternion(t.invert()).normalize()}const s=m.motionTrack.clone(),l=new o;for(let t=0;t<m.motionTrack.values.length;t+=3){l.fromArray(m.motionTrack.values,t);const o=l.dot(a);l.copy(a).multiplyScalar(o),s.values[t+0]=l.x,s.values[t+1]=l.y,s.values[t+2]=l.z}m.tracks.push(s);const c=i.sub(t);if(c.addScaledVector(a,-c.dot(a)),null!=u?.parent&&null!=d){d.updateWorldMatrix(!0,!1),u.parent.updateWorldMatrix(!0,!1);const t=(new r).copy(d.matrixWorld).invert().multiply(u.parent.matrixWorld);c.applyMatrix3((new n).setFromMatrix4(t))}m.displacement=c.length()}else console.error("Could not find root motion track",t,u);return m}static fromClipWithDistance(t,o){const e=new RootMotionClip(t.name,t.duration,t.tracks.slice(),t.blendMode);return e.fixedInPlace=!0,e.source=t,e.uuid=t.uuid,a(e,i(t)),l(e,s(t)),e.displacement=o,e}clone(){const t=RootMotionClip.fromClip(this.source.clone(),this.fixedInPlace,this.rootBone,this.animationRoot);return t.displacement=this.displacement,l(t,s(this)),t}}let c=53912381;export class BoneLayer{constructor(){this.uuid=c++,this.order=0,this.boneMask=[]}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
package/dist/gameplay/index.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export * from './services/physics/physics-system.js';
|
|
|
21
21
|
export * from './animation/anim-sm.js';
|
|
22
22
|
export * from './animation/root-motion.js';
|
|
23
23
|
export * from './animation/retarget.js';
|
|
24
|
+
export * from './animation/locomotion-body.js';
|
|
25
|
+
export * from './animation/foot-contact.js';
|
|
26
|
+
export * from './animation/foot-contact-baker.js';
|
|
27
|
+
export * from './animation/pose-inertialization.js';
|
|
24
28
|
export * from './services/asset-loader.js';
|
|
25
29
|
export * from './services/pointer-events.js';
|
|
26
30
|
export { VisualEffect } from '../effects/vfx/vfx-param.js';
|