Compare commits
4 Commits
918ee49d7c
...
ff4ead1d24
| Author | SHA1 | Date | |
|---|---|---|---|
| ff4ead1d24 | |||
| 974f340d33 | |||
| c6283d492c | |||
| 83194df14f |
@@ -129,12 +129,6 @@ export function Ebike({
|
|||||||
|
|
||||||
// State for debug visualization (synced from refs during useFrame)
|
// State for debug visualization (synced from refs during useFrame)
|
||||||
const [showCameraPoints, setShowCameraPoints] = useState(true);
|
const [showCameraPoints, setShowCameraPoints] = useState(true);
|
||||||
const [debugRestingPosition, setDebugRestingPosition] =
|
|
||||||
useState<Vector3Tuple>([
|
|
||||||
parkedPosition[0],
|
|
||||||
parkedPosition[1],
|
|
||||||
parkedPosition[2],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Keep movementModeRef in sync — useFrame closures capture React state at
|
// Keep movementModeRef in sync — useFrame closures capture React state at
|
||||||
// render time and can become stale between renders.
|
// render time and can become stale between renders.
|
||||||
@@ -321,9 +315,7 @@ export function Ebike({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sync debug visualization state (throttled to avoid excessive re-renders)
|
// Sync debug visualization state (throttled to avoid excessive re-renders)
|
||||||
if (showCameraPoints) {
|
// Debug visualization positions are derived elsewhere when needed.
|
||||||
setDebugRestingPosition([...restingPositionRef.current]);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
updateEbikeSounds({ mounted: false, driving: false, breakdown: false });
|
updateEbikeSounds({ mounted: false, driving: false, breakdown: false });
|
||||||
groupRef.current.position.set(...restingPositionRef.current);
|
groupRef.current.position.set(...restingPositionRef.current);
|
||||||
@@ -340,17 +332,6 @@ export function Ebike({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Debug visualization positions computed from state (not refs)
|
|
||||||
const camPointPos: Vector3Tuple = [
|
|
||||||
debugRestingPosition[0] + EBIKE_CAMERA_TRANSFORM.position[0],
|
|
||||||
debugRestingPosition[1] + EBIKE_CAMERA_TRANSFORM.position[1],
|
|
||||||
debugRestingPosition[2] + EBIKE_CAMERA_TRANSFORM.position[2],
|
|
||||||
];
|
|
||||||
const dropPointPos: Vector3Tuple = [
|
|
||||||
debugRestingPosition[0] + EBIKE_DROP_PLAYER_TRANSFORM.position[0],
|
|
||||||
debugRestingPosition[1] + EBIKE_DROP_PLAYER_TRANSFORM.position[1],
|
|
||||||
debugRestingPosition[2] + EBIKE_DROP_PLAYER_TRANSFORM.position[2],
|
|
||||||
];
|
|
||||||
const interactionLabel =
|
const interactionLabel =
|
||||||
mainState === "ebike"
|
mainState === "ebike"
|
||||||
? "Lancer le repair game"
|
? "Lancer le repair game"
|
||||||
@@ -409,9 +390,15 @@ export function Ebike({
|
|||||||
EBIKE_CAMERA_TRANSFORM.rotation[2],
|
EBIKE_CAMERA_TRANSFORM.rotation[2],
|
||||||
];
|
];
|
||||||
|
|
||||||
animateCameraTransformTransition(targetCamPos, targetRotation, 1, () => {
|
animateCameraTransformTransition(
|
||||||
useGameStore.getState().setPlayerMovementMode("ebike");
|
targetCamPos,
|
||||||
});
|
targetRotation,
|
||||||
|
1,
|
||||||
|
() => {
|
||||||
|
useGameStore.getState().setPlayerMovementMode("ebike");
|
||||||
|
},
|
||||||
|
{ lockInput: false },
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const currentPos = new THREE.Vector3();
|
const currentPos = new THREE.Vector3();
|
||||||
if (groupRef.current) {
|
if (groupRef.current) {
|
||||||
@@ -437,9 +424,15 @@ export function Ebike({
|
|||||||
THREE.MathUtils.radToDeg(currentEuler.z),
|
THREE.MathUtils.radToDeg(currentEuler.z),
|
||||||
];
|
];
|
||||||
|
|
||||||
animateCameraTransformTransition(targetCamPos, targetRotation, 1, () => {
|
animateCameraTransformTransition(
|
||||||
useGameStore.getState().setPlayerMovementMode("walk");
|
targetCamPos,
|
||||||
});
|
targetRotation,
|
||||||
|
1,
|
||||||
|
() => {
|
||||||
|
useGameStore.getState().setPlayerMovementMode("walk");
|
||||||
|
},
|
||||||
|
{ lockInput: false },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [movementMode, mainState, ebikeStep, setMissionStep, camera, position]);
|
}, [movementMode, mainState, ebikeStep, setMissionStep, camera, position]);
|
||||||
|
|
||||||
|
|||||||
@@ -181,6 +181,8 @@ export const EbikeGPSMap: React.FC<EbikeGPSMapProps> = ({
|
|||||||
|
|
||||||
// Sync texture into uniform when it changes (canvas resize)
|
// Sync texture into uniform when it changes (canvas resize)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// External Three.js material uniform sync — intentional side effect.
|
||||||
|
// eslint-disable-next-line react-hooks/immutability
|
||||||
shaderMat.uniforms.map.value = texture;
|
shaderMat.uniforms.map.value = texture;
|
||||||
}, [shaderMat, texture]);
|
}, [shaderMat, texture]);
|
||||||
|
|
||||||
@@ -196,6 +198,8 @@ export const EbikeGPSMap: React.FC<EbikeGPSMapProps> = ({
|
|||||||
// Resize the canvas whenever canvasSize changes (texture declared above)
|
// Resize the canvas whenever canvasSize changes (texture declared above)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Object.assign(offscreenCanvas, { width: canvasSize, height: canvasSize });
|
Object.assign(offscreenCanvas, { width: canvasSize, height: canvasSize });
|
||||||
|
// External Three.js texture invalidation — intentional side effect.
|
||||||
|
// eslint-disable-next-line react-hooks/immutability
|
||||||
texture.needsUpdate = true;
|
texture.needsUpdate = true;
|
||||||
}, [canvasSize, offscreenCanvas, texture]);
|
}, [canvasSize, offscreenCanvas, texture]);
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ export function EbikeSpeedmeter({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ── Frame loop ──────────────────────────────────────────────────────────────
|
// ── Frame loop ──────────────────────────────────────────────────────────────
|
||||||
|
/* External Three.js canvas+texture sync — intentional side effects in useFrame. */
|
||||||
|
/* eslint-disable react-hooks/immutability */
|
||||||
useFrame((_, delta) => {
|
useFrame((_, delta) => {
|
||||||
// 1. Smooth speed factor
|
// 1. Smooth speed factor
|
||||||
const target = THREE.MathUtils.clamp(window.ebikeSpeedFactor ?? 0, 0, 1);
|
const target = THREE.MathUtils.clamp(window.ebikeSpeedFactor ?? 0, 0, 1);
|
||||||
@@ -181,6 +183,7 @@ export function EbikeSpeedmeter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
fillTexture.needsUpdate = true;
|
fillTexture.needsUpdate = true;
|
||||||
|
/* eslint-enable react-hooks/immutability */
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -30,9 +30,26 @@ export function PylonDownedPylon(): React.JSX.Element | null {
|
|||||||
const straightenStartRef = useRef<number | null>(null);
|
const straightenStartRef = useRef<number | null>(null);
|
||||||
const hasPlayedFirstAudioRef = useRef(false);
|
const hasPlayedFirstAudioRef = useRef(false);
|
||||||
|
|
||||||
|
const showUpright =
|
||||||
|
isRaised ||
|
||||||
|
mainState !== "pylon" ||
|
||||||
|
step === "waiting" ||
|
||||||
|
step === "inspected" ||
|
||||||
|
step === "fragmented" ||
|
||||||
|
step === "scanning" ||
|
||||||
|
step === "repairing" ||
|
||||||
|
step === "reassembling" ||
|
||||||
|
step === "done" ||
|
||||||
|
step === "narrator-outro";
|
||||||
|
|
||||||
|
const isPylonInteractive = step === "arrived" || step === "npc-return";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (step === "arrived") {
|
if (step === "arrived") {
|
||||||
hasPlayedFirstAudioRef.current = false;
|
hasPlayedFirstAudioRef.current = false;
|
||||||
|
// Reset the "raised" latch when a new run begins. This is derived
|
||||||
|
// resync from the step prop and runs once per step transition.
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
setIsRaised(false);
|
setIsRaised(false);
|
||||||
}
|
}
|
||||||
}, [step]);
|
}, [step]);
|
||||||
@@ -62,20 +79,6 @@ export function PylonDownedPylon(): React.JSX.Element | null {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const showUpright =
|
|
||||||
isRaised ||
|
|
||||||
mainState !== "pylon" ||
|
|
||||||
step === "waiting" ||
|
|
||||||
step === "inspected" ||
|
|
||||||
step === "fragmented" ||
|
|
||||||
step === "scanning" ||
|
|
||||||
step === "repairing" ||
|
|
||||||
step === "reassembling" ||
|
|
||||||
step === "done" ||
|
|
||||||
step === "narrator-outro";
|
|
||||||
|
|
||||||
const isPylonInteractive = step === "arrived" || step === "npc-return";
|
|
||||||
|
|
||||||
const beginStraighten = (): void => {
|
const beginStraighten = (): void => {
|
||||||
setIsStraightening(true);
|
setIsStraightening(true);
|
||||||
pylonStraighteningSignal.started = true;
|
pylonStraighteningSignal.started = true;
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ const _target = new THREE.Vector3();
|
|||||||
* Compute the Y rotation (radians) for a model whose default forward
|
* Compute the Y rotation (radians) for a model whose default forward
|
||||||
* direction is +Z, so that it faces from `from` toward `to`.
|
* direction is +Z, so that it faces from `from` toward `to`.
|
||||||
*/
|
*/
|
||||||
function faceToward(from: THREE.Vector3, to: readonly [number, number, number]): number {
|
function faceToward(
|
||||||
|
from: THREE.Vector3,
|
||||||
|
to: readonly [number, number, number],
|
||||||
|
): number {
|
||||||
const dx = to[0] - from.x;
|
const dx = to[0] - from.x;
|
||||||
const dz = to[2] - from.z;
|
const dz = to[2] - from.z;
|
||||||
return Math.atan2(dx, dz);
|
return Math.atan2(dx, dz);
|
||||||
@@ -71,6 +74,10 @@ export function PylonFarmerNPC(): React.JSX.Element | null {
|
|||||||
// ─── playAnim ─────────────────────────────────────────────────────────────
|
// ─── playAnim ─────────────────────────────────────────────────────────────
|
||||||
// NOTE: actions is intentionally in the dep array so this callback is
|
// NOTE: actions is intentionally in the dep array so this callback is
|
||||||
// recreated when drei's internal state populates the actions map.
|
// recreated when drei's internal state populates the actions map.
|
||||||
|
// External THREE.AnimationAction lifecycle methods (fadeOut/fadeIn/play +
|
||||||
|
// setLoop/clampWhenFinished mutations) are intentional side effects on
|
||||||
|
// drei-managed objects.
|
||||||
|
/* eslint-disable react-hooks/immutability */
|
||||||
const playAnim = useCallback(
|
const playAnim = useCallback(
|
||||||
(name: NPCAnimation, fade = ANIM_FADE): void => {
|
(name: NPCAnimation, fade = ANIM_FADE): void => {
|
||||||
if (currentAnimRef.current === name) return;
|
if (currentAnimRef.current === name) return;
|
||||||
@@ -89,6 +96,7 @@ export function PylonFarmerNPC(): React.JSX.Element | null {
|
|||||||
},
|
},
|
||||||
[actions],
|
[actions],
|
||||||
);
|
);
|
||||||
|
/* eslint-enable react-hooks/immutability */
|
||||||
|
|
||||||
// ─── Async audio after pylon is raised ────────────────────────────────────
|
// ─── Async audio after pylon is raised ────────────────────────────────────
|
||||||
const playPostRaiseAudioAndAdvance = useCallback(async () => {
|
const playPostRaiseAudioAndAdvance = useCallback(async () => {
|
||||||
@@ -112,6 +120,8 @@ export function PylonFarmerNPC(): React.JSX.Element | null {
|
|||||||
|
|
||||||
// ─── Step-driven animation ────────────────────────────────────────────────
|
// ─── Step-driven animation ────────────────────────────────────────────────
|
||||||
// Fires when step changes OR when playAnim changes (i.e. when actions load).
|
// Fires when step changes OR when playAnim changes (i.e. when actions load).
|
||||||
|
// playAnim mutates drei-managed AnimationAction internals (intentional).
|
||||||
|
/* eslint-disable react-hooks/immutability */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
currentAnimRef.current = null;
|
currentAnimRef.current = null;
|
||||||
if (step === "arrived") {
|
if (step === "arrived") {
|
||||||
@@ -168,7 +178,10 @@ export function PylonFarmerNPC(): React.JSX.Element | null {
|
|||||||
currentPosRef.current.lerp(_target, t);
|
currentPosRef.current.lerp(_target, t);
|
||||||
} else if (!isStraightening && currentAnimRef.current === "walk") {
|
} else if (!isStraightening && currentAnimRef.current === "walk") {
|
||||||
playAnim("idle");
|
playAnim("idle");
|
||||||
savedRotationYRef.current = faceToward(currentPosRef.current, PYLON_WORLD_POSITION);
|
savedRotationYRef.current = faceToward(
|
||||||
|
currentPosRef.current,
|
||||||
|
PYLON_WORLD_POSITION,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
group.position.copy(currentPosRef.current);
|
group.position.copy(currentPosRef.current);
|
||||||
} else if (step === "inspected") {
|
} else if (step === "inspected") {
|
||||||
@@ -180,8 +193,15 @@ export function PylonFarmerNPC(): React.JSX.Element | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Rotation ──────────────────────────────────────────────────────────
|
// ── Rotation ──────────────────────────────────────────────────────────
|
||||||
if (step === "npc-return" && !isCompleted && currentAnimRef.current === "walk") {
|
if (
|
||||||
const walkRotY = faceToward(currentPosRef.current, PYLON_FARMER_NPC_WALK_LOOK_AT);
|
step === "npc-return" &&
|
||||||
|
!isCompleted &&
|
||||||
|
currentAnimRef.current === "walk"
|
||||||
|
) {
|
||||||
|
const walkRotY = faceToward(
|
||||||
|
currentPosRef.current,
|
||||||
|
PYLON_FARMER_NPC_WALK_LOOK_AT,
|
||||||
|
);
|
||||||
group.rotation.set(0, walkRotY, 0);
|
group.rotation.set(0, walkRotY, 0);
|
||||||
} else {
|
} else {
|
||||||
group.rotation.set(0, savedRotationYRef.current, 0);
|
group.rotation.set(0, savedRotationYRef.current, 0);
|
||||||
@@ -189,6 +209,7 @@ export function PylonFarmerNPC(): React.JSX.Element | null {
|
|||||||
|
|
||||||
group.scale.setScalar(PYLON_FARMER_NPC_AFTER_SCALE);
|
group.scale.setScalar(PYLON_FARMER_NPC_AFTER_SCALE);
|
||||||
});
|
});
|
||||||
|
/* eslint-enable react-hooks/immutability */
|
||||||
|
|
||||||
if (mainState !== "pylon") return null;
|
if (mainState !== "pylon") return null;
|
||||||
if (step !== "arrived" && step !== "npc-return" && step !== "inspected")
|
if (step !== "arrived" && step !== "npc-return" && step !== "inspected")
|
||||||
|
|||||||
@@ -20,14 +20,17 @@ export function PylonLightingEffect(): null {
|
|||||||
const step = useGameStore((state) => state.pylon.currentStep);
|
const step = useGameStore((state) => state.pylon.currentStep);
|
||||||
|
|
||||||
// True from "approaching" until narrator-outro (lighting resets before the outro audio)
|
// True from "approaching" until narrator-outro (lighting resets before the outro audio)
|
||||||
const isActive = mainState === "pylon" && step !== "locked" && step !== "narrator-outro";
|
const isActive =
|
||||||
|
mainState === "pylon" && step !== "locked" && step !== "narrator-outro";
|
||||||
|
|
||||||
// Working THREE.Color instances — lerped every frame
|
// Working THREE.Color instances — lerped every frame
|
||||||
const ambientRef = useRef(new THREE.Color(LIGHTING_STATE.ambientColor));
|
const ambientRef = useRef(new THREE.Color(LIGHTING_STATE.ambientColor));
|
||||||
const sunRef = useRef(new THREE.Color(LIGHTING_STATE.sunColor));
|
const sunRef = useRef(new THREE.Color(LIGHTING_STATE.sunColor));
|
||||||
|
|
||||||
// Target colours — updated reactively when isActive changes
|
// Target colours — updated reactively when isActive changes
|
||||||
const targetAmbientRef = useRef(new THREE.Color(LIGHTING_DEFAULTS.ambientColor));
|
const targetAmbientRef = useRef(
|
||||||
|
new THREE.Color(LIGHTING_DEFAULTS.ambientColor),
|
||||||
|
);
|
||||||
const targetSunRef = useRef(new THREE.Color(LIGHTING_DEFAULTS.sunColor));
|
const targetSunRef = useRef(new THREE.Color(LIGHTING_DEFAULTS.sunColor));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ export const PYLON_DOWNED_ROTATION: Vector3Tuple = [0, 0, -0.9];
|
|||||||
|
|
||||||
export const PYLON_UPRIGHT_ROTATION: Vector3Tuple = [0, 0, 0];
|
export const PYLON_UPRIGHT_ROTATION: Vector3Tuple = [0, 0, 0];
|
||||||
|
|
||||||
export const PYLON_FARMER_NPC_POSITION: Vector3Tuple = [
|
export const PYLON_FARMER_NPC_POSITION: Vector3Tuple = [-16.13, 3.2, 52.46];
|
||||||
-16.13,
|
|
||||||
3.2,
|
|
||||||
52.46
|
|
||||||
];
|
|
||||||
|
|
||||||
export const PYLON_FARMER_NPC_AFTER_POSITION: Vector3Tuple = [
|
export const PYLON_FARMER_NPC_AFTER_POSITION: Vector3Tuple = [
|
||||||
PYLON_WORLD_POSITION[0] + 3,
|
PYLON_WORLD_POSITION[0] + 3,
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import { PYLON_WORLD_POSITION } from "@/data/gameplay/pylonConfig";
|
|||||||
// Zones qui active la coupure de courant
|
// Zones qui active la coupure de courant
|
||||||
export const PYLON_APPROACH_ZONE: ZoneConfig = {
|
export const PYLON_APPROACH_ZONE: ZoneConfig = {
|
||||||
id: "pylon-approach",
|
id: "pylon-approach",
|
||||||
position: [
|
position: [5, 4, -21.5],
|
||||||
5,
|
|
||||||
4,
|
|
||||||
-21.5
|
|
||||||
],
|
|
||||||
radius: 10,
|
radius: 10,
|
||||||
height: 18,
|
height: 18,
|
||||||
oneShot: true,
|
oneShot: true,
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export class Debug {
|
|||||||
fogEnabled: boolean;
|
fogEnabled: boolean;
|
||||||
handTrackingSource: HandTrackingSource;
|
handTrackingSource: HandTrackingSource;
|
||||||
showDebugOverlay: boolean;
|
showDebugOverlay: boolean;
|
||||||
showHandTrackingSvg: boolean;
|
showHandTrackingModel: boolean;
|
||||||
showInteractionSpheres: boolean;
|
showInteractionSpheres: boolean;
|
||||||
showPerf: boolean;
|
showPerf: boolean;
|
||||||
sceneMode: SceneMode;
|
sceneMode: SceneMode;
|
||||||
@@ -108,7 +108,7 @@ export class Debug {
|
|||||||
fogEnabled: FOG_CONFIG.enabled,
|
fogEnabled: FOG_CONFIG.enabled,
|
||||||
handTrackingSource: storedControls.handTrackingSource ?? "browser",
|
handTrackingSource: storedControls.handTrackingSource ?? "browser",
|
||||||
showDebugOverlay: true,
|
showDebugOverlay: true,
|
||||||
showHandTrackingSvg: false,
|
showHandTrackingModel: false,
|
||||||
showInteractionSpheres: false,
|
showInteractionSpheres: false,
|
||||||
showPerf: true,
|
showPerf: true,
|
||||||
sceneMode: storedControls.sceneMode ?? "game",
|
sceneMode: storedControls.sceneMode ?? "game",
|
||||||
@@ -156,10 +156,10 @@ export class Debug {
|
|||||||
const handTrackingFolder = this.createFolder("Hand Tracking");
|
const handTrackingFolder = this.createFolder("Hand Tracking");
|
||||||
|
|
||||||
handTrackingFolder
|
handTrackingFolder
|
||||||
?.add(this.controls, "showHandTrackingSvg")
|
?.add(this.controls, "showHandTrackingModel")
|
||||||
.name("Show SVG")
|
.name("Show Model")
|
||||||
.onChange((value: boolean) => {
|
.onChange((value: boolean) => {
|
||||||
this.controls.showHandTrackingSvg = value;
|
this.controls.showHandTrackingModel = value;
|
||||||
this.emit();
|
this.emit();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -281,12 +281,12 @@ export class Debug {
|
|||||||
return this.controls.showInteractionSpheres;
|
return this.controls.showInteractionSpheres;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShowHandTrackingSvg(): boolean {
|
getShowHandTrackingModel(): boolean {
|
||||||
return this.controls.showHandTrackingSvg;
|
return this.controls.showHandTrackingModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowHandTrackingSvg(value: boolean): void {
|
setShowHandTrackingModel(value: boolean): void {
|
||||||
this.controls.showHandTrackingSvg = value;
|
this.controls.showHandTrackingModel = value;
|
||||||
this.emit();
|
this.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,10 @@ export function animateCameraTransformTransition(
|
|||||||
targetRotation: Vector3Tuple,
|
targetRotation: Vector3Tuple,
|
||||||
duration: number = 1,
|
duration: number = 1,
|
||||||
onComplete?: () => void,
|
onComplete?: () => void,
|
||||||
|
options: { lockInput?: boolean } = {},
|
||||||
): void {
|
): void {
|
||||||
|
const { lockInput = true } = options;
|
||||||
|
|
||||||
if (!globalCamera) {
|
if (!globalCamera) {
|
||||||
logger.warn("GameCinematics", "Camera not found for transition");
|
logger.warn("GameCinematics", "Camera not found for transition");
|
||||||
onComplete?.();
|
onComplete?.();
|
||||||
@@ -252,7 +255,9 @@ export function animateCameraTransformTransition(
|
|||||||
const camera = globalCamera;
|
const camera = globalCamera;
|
||||||
|
|
||||||
cameraTransitionTimeline?.kill();
|
cameraTransitionTimeline?.kill();
|
||||||
useGameStore.getState().setCinematicPlaying(true);
|
if (lockInput) {
|
||||||
|
useGameStore.getState().setCinematicPlaying(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Convert target rotation in degrees to quaternion
|
// Convert target rotation in degrees to quaternion
|
||||||
const targetEuler = new THREE.Euler(
|
const targetEuler = new THREE.Euler(
|
||||||
@@ -274,7 +279,9 @@ export function animateCameraTransformTransition(
|
|||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
cameraTransitionTimeline = null;
|
cameraTransitionTimeline = null;
|
||||||
useGameStore.getState().setCinematicPlaying(false);
|
if (lockInput) {
|
||||||
|
useGameStore.getState().setCinematicPlaying(false);
|
||||||
|
}
|
||||||
onComplete?.();
|
onComplete?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+9
-3
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "@/data/player/playerConfig";
|
} from "@/data/player/playerConfig";
|
||||||
import { LA_FABRIK_INITIAL_LOOK_AT } from "@/data/world/laFabrikConfig";
|
import { LA_FABRIK_INITIAL_LOOK_AT } from "@/data/world/laFabrikConfig";
|
||||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||||
|
import { useDebugStore } from "@/hooks/debug/useDebugStore";
|
||||||
import { useEnvironmentDebug } from "@/hooks/debug/useEnvironmentDebug";
|
import { useEnvironmentDebug } from "@/hooks/debug/useEnvironmentDebug";
|
||||||
import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug";
|
import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug";
|
||||||
import { useCharacterDebug } from "@/hooks/debug/useCharacterDebug";
|
import { useCharacterDebug } from "@/hooks/debug/useCharacterDebug";
|
||||||
@@ -32,7 +33,6 @@ import { CharacterSystem } from "@/world/characters/CharacterSystem";
|
|||||||
import { Player } from "@/world/player/Player";
|
import { Player } from "@/world/player/Player";
|
||||||
import { TestMap } from "@/world/debug/TestMap";
|
import { TestMap } from "@/world/debug/TestMap";
|
||||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||||
import type { HandTrackingGloveHandedness } from "@/hooks/handTracking/useHandTrackingGloveStatus";
|
|
||||||
import type { HandTrackingHand } from "@/types/handTracking/handTracking";
|
import type { HandTrackingHand } from "@/types/handTracking/handTracking";
|
||||||
|
|
||||||
interface WorldProps {
|
interface WorldProps {
|
||||||
@@ -41,7 +41,7 @@ interface WorldProps {
|
|||||||
|
|
||||||
function hasTrackedHand(
|
function hasTrackedHand(
|
||||||
hands: HandTrackingHand[],
|
hands: HandTrackingHand[],
|
||||||
handedness: HandTrackingGloveHandedness,
|
handedness: "left" | "right",
|
||||||
): boolean {
|
): boolean {
|
||||||
return hands.some((hand) => hand.handedness.toLowerCase() === handedness);
|
return hands.some((hand) => hand.handedness.toLowerCase() === handedness);
|
||||||
}
|
}
|
||||||
@@ -60,6 +60,9 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
|||||||
(state) => state.showPlayerModel,
|
(state) => state.showPlayerModel,
|
||||||
);
|
);
|
||||||
const showDebugOctree = useDebugVisualsStore((state) => state.showOctree);
|
const showDebugOctree = useDebugVisualsStore((state) => state.showOctree);
|
||||||
|
const showHandTrackingModel = useDebugStore((debug) =>
|
||||||
|
debug.getShowHandTrackingModel(),
|
||||||
|
);
|
||||||
const { hands, status, usageStatus } = useHandTrackingSnapshot();
|
const { hands, status, usageStatus } = useHandTrackingSnapshot();
|
||||||
const {
|
const {
|
||||||
octree,
|
octree,
|
||||||
@@ -74,7 +77,10 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
|||||||
? PLAYER_SPAWN_POSITION_GAME
|
? PLAYER_SPAWN_POSITION_GAME
|
||||||
: PLAYER_SPAWN_POSITION_PHYSICS;
|
: PLAYER_SPAWN_POSITION_PHYSICS;
|
||||||
const showHandTrackingGloves =
|
const showHandTrackingGloves =
|
||||||
status === "connected" && usageStatus !== "inactive" && hands.length > 0;
|
showHandTrackingModel &&
|
||||||
|
status === "connected" &&
|
||||||
|
usageStatus !== "inactive" &&
|
||||||
|
hands.length > 0;
|
||||||
const showLeftHandTrackingGlove =
|
const showLeftHandTrackingGlove =
|
||||||
showHandTrackingGloves && hasTrackedHand(hands, "left");
|
showHandTrackingGloves && hasTrackedHand(hands, "left");
|
||||||
const showRightHandTrackingGlove =
|
const showRightHandTrackingGlove =
|
||||||
|
|||||||
Reference in New Issue
Block a user