auto-git:

[change] src/runtime-three/first-person-navigation-controller.ts
This commit is contained in:
2026-05-01 16:46:46 +02:00
parent 9f4471a260
commit 6f7845999f

View File

@@ -619,6 +619,260 @@ export class FirstPersonNavigationController implements NavigationController {
};
}
private createLedgeGrabLocomotionState(options: {
inputMagnitude: number;
direction: Vec3;
}): RuntimeLocomotionState {
return {
...createIdleRuntimeLocomotionState("ledgeGrab"),
inputMagnitude: options.inputMagnitude,
contact: {
collisionCount: 1,
collidedAxes: {
x: Math.abs(options.direction.x) > 0.01,
y: false,
z: Math.abs(options.direction.z) > 0.01
},
groundNormal: null,
groundDistance: null,
slopeDegrees: null
}
};
}
private enterLedgeGrab(
target: RuntimePlayerLedgeGrabTarget,
inputMagnitude: number
): void {
if (this.context === null) {
return;
}
const volumeState = this.context.resolvePlayerVolumeState(
target.hangFeetPosition
);
this.ledgeGrabTarget = target;
this.climbSurface = null;
this.climbLatchBlocked = false;
this.feetPosition = { ...target.hangFeetPosition };
this.activePlayerShape = cloneFirstPersonPlayerShape(
this.standingPlayerShape
);
this.verticalVelocity = 0;
this.jumpBufferRemainingMs = 0;
this.coyoteTimeRemainingMs = 0;
this.jumpHoldRemainingMs = 0;
this.latestJumpStarted = false;
this.latestHeadBump = false;
this.previousPlanarDisplacement = { x: 0, y: 0, z: 0 };
this.grounded = false;
this.inWaterVolume = volumeState.inWater;
this.inFogVolume = volumeState.inFog;
this.smoothedFeetY = this.feetPosition.y;
this.locomotionState = this.createLedgeGrabLocomotionState({
inputMagnitude,
direction: target.direction
});
}
private completeLedgeGrabMantle(
target: RuntimePlayerLedgeGrabTarget,
inputMagnitude: number,
dt: number,
playerMovement: RuntimePlayerMovement,
jumpPressed: boolean
): boolean {
if (
this.context === null ||
!this.context.canOccupyPlayerShape(
target.standFeetPosition,
this.standingPlayerShape
)
) {
return false;
}
const previousFeetPosition = this.feetPosition;
const volumeState = this.context.resolvePlayerVolumeState(
target.standFeetPosition
);
const displacement = {
x: target.standFeetPosition.x - previousFeetPosition.x,
y: target.standFeetPosition.y - previousFeetPosition.y,
z: target.standFeetPosition.z - previousFeetPosition.z
};
this.ledgeGrabTarget = null;
this.feetPosition = { ...target.standFeetPosition };
this.activePlayerShape = cloneFirstPersonPlayerShape(
this.standingPlayerShape
);
this.verticalVelocity = 0;
this.jumpBufferRemainingMs = 0;
this.coyoteTimeRemainingMs = playerMovement.jump.coyoteTimeMs;
this.jumpHoldRemainingMs = 0;
this.jumpPressed = jumpPressed;
this.latestJumpStarted = false;
this.latestHeadBump = false;
this.previousPlanarDisplacement = {
x: displacement.x,
y: 0,
z: displacement.z
};
this.grounded = true;
this.inWaterVolume = volumeState.inWater;
this.inFogVolume = volumeState.inFog;
this.smoothedFeetY = this.feetPosition.y;
this.locomotionState = {
...createIdleRuntimeLocomotionState("grounded"),
gait: "walk",
inputMagnitude,
requestedPlanarSpeed: CLIMB_SPEED_METERS_PER_SECOND,
planarSpeed:
dt > 0 ? Math.hypot(displacement.x, displacement.z) / dt : 0
};
return true;
}
private releaseLedgeGrab(
inputMagnitude: number,
playerMovement: RuntimePlayerMovement,
jumpPressed: boolean
): void {
if (this.context === null) {
return;
}
const volumeState = this.context.resolvePlayerVolumeState(
this.feetPosition
);
this.ledgeGrabTarget = null;
this.climbSurface = null;
this.climbLatchBlocked = true;
this.activePlayerShape = cloneFirstPersonPlayerShape(
this.standingPlayerShape
);
this.verticalVelocity = 0;
this.jumpBufferRemainingMs = 0;
this.coyoteTimeRemainingMs = 0;
this.jumpHoldRemainingMs = 0;
this.jumpPressed = jumpPressed;
this.latestJumpStarted = false;
this.latestHeadBump = false;
this.previousPlanarDisplacement = { x: 0, y: 0, z: 0 };
this.grounded = false;
this.inWaterVolume = volumeState.inWater;
this.inFogVolume = volumeState.inFog;
this.smoothedFeetY = this.feetPosition.y;
this.locomotionState = {
...createIdleRuntimeLocomotionState("airborne"),
inputMagnitude,
requestedPlanarSpeed: playerMovement.moveSpeed * inputMagnitude,
verticalVelocity: 0
};
}
private stepLedgeGrab(
dt: number,
inputState: ReturnType<typeof resolvePlayerStartActionInputs>,
playerMovement: RuntimePlayerMovement,
movementYawRadians: number
): boolean {
if (this.context === null || this.ledgeGrabTarget === null) {
return false;
}
const target = this.ledgeGrabTarget;
const jumpPressed = inputState.jump > CLIMB_INPUT_ACTIVE_THRESHOLD;
const climbPressed = inputState.climb > CLIMB_INPUT_ACTIVE_THRESHOLD;
const crouchPressed = inputState.crouch > CLIMB_INPUT_ACTIVE_THRESHOLD;
const inputDirection = resolveClimbPlanarInputDirection(
inputState,
movementYawRadians
);
const ledgeDirectionDot =
inputDirection.direction === null
? 0
: dotPlanarVec3(inputDirection.direction, target.direction);
const movingIntoLedge =
inputDirection.inputMagnitude > 0.25 && ledgeDirectionDot > 0.35;
const movingAwayFromLedge =
inputDirection.inputMagnitude > 0.25 && ledgeDirectionDot < -0.35;
if (
!playerMovement.edgeAssist.enabled ||
!this.context.canOccupyPlayerShape(
target.hangFeetPosition,
this.standingPlayerShape
)
) {
this.releaseLedgeGrab(
inputDirection.inputMagnitude,
playerMovement,
jumpPressed
);
this.updateCameraTransform();
this.publishTelemetry();
return true;
}
if (crouchPressed || movingAwayFromLedge) {
this.releaseLedgeGrab(
inputDirection.inputMagnitude,
playerMovement,
jumpPressed
);
this.updateCameraTransform();
this.publishTelemetry();
return true;
}
if (
(climbPressed || jumpPressed || movingIntoLedge) &&
this.completeLedgeGrabMantle(
target,
inputDirection.inputMagnitude,
dt,
playerMovement,
jumpPressed
)
) {
this.updateCameraTransform();
this.publishTelemetry();
return true;
}
this.feetPosition = { ...target.hangFeetPosition };
this.activePlayerShape = cloneFirstPersonPlayerShape(
this.standingPlayerShape
);
this.verticalVelocity = 0;
this.jumpBufferRemainingMs = 0;
this.coyoteTimeRemainingMs = 0;
this.jumpHoldRemainingMs = 0;
this.jumpPressed = jumpPressed;
this.latestJumpStarted = false;
this.latestHeadBump = false;
this.previousPlanarDisplacement = { x: 0, y: 0, z: 0 };
this.grounded = false;
this.smoothedFeetY = this.feetPosition.y;
this.locomotionState = this.createLedgeGrabLocomotionState({
inputMagnitude: inputDirection.inputMagnitude,
direction: target.direction
});
this.inWaterVolume =
this.context.resolvePlayerVolumeState(this.feetPosition).inWater;
this.inFogVolume =
this.context.resolvePlayerVolumeState(this.feetPosition).inFog;
this.updateCameraTransform();
this.publishTelemetry();
return true;
}
private stepClimbing(
dt: number,
inputState: ReturnType<typeof resolvePlayerStartActionInputs>,