Files
La-Fabrik/src/world/RepairGameScene.tsx
T
2026-06-02 00:23:43 +02:00

109 lines
3.5 KiB
TypeScript

import { Suspense, useCallback, useEffect } from "react";
import { Canvas, useThree } from "@react-three/fiber";
import { Physics } from "@react-three/rapier";
import * as THREE from "three";
import { DebugPerf } from "@/components/debug/DebugPerf";
import { RepairGame } from "@/components/three/gameplay/RepairGame";
import { logger } from "@/utils/core/Logger";
import type { RepairMissionId } from "@/types/gameplay/repairMission";
import type { Vector3Tuple } from "@/types/three/three";
// Isolated scene — no world offset, no terrain. The repair game runs
// fully centred in its own context so the heavy map never loads here.
const REPAIR_SCENE_POSITION: Vector3Tuple = [0, 0, 0];
// Background: very dark blue-grey to match Altera's night-time mood
const REPAIR_SCENE_BG = "#0b0d14";
// Lighting tuned to match the main world defaults from lightingConfig.ts
const AMBIENT_COLOR = "#dfe7d8";
const AMBIENT_INTENSITY = 0.9;
const SUN_COLOR = "#ffe2bf";
const SUN_INTENSITY = 2.2;
const SUN_POSITION: Vector3Tuple = [5, 8, 4];
// Mimic the first-person view from the main world:
// - PLAYER_EYE_HEIGHT = 1.75 → camera Y
// - Case floats at [0, 0.4, 1.8] (inspected) → [0, 1.05, 2.05] (repairing)
// - Look-at target averaged between those two states
const CAMERA_POSITION: Vector3Tuple = [5, 2, 2];
const CAMERA_LOOK_AT: Vector3Tuple = [0, 0.7, 1.9];
function RepairSceneCamera(): null {
const { camera } = useThree();
useEffect(() => {
camera.lookAt(...CAMERA_LOOK_AT);
}, [camera]);
return null;
}
interface RepairGameSceneProps {
mission: RepairMissionId;
}
export function RepairGameScene({
mission,
}: RepairGameSceneProps): React.JSX.Element {
const handleCreated = useCallback(({ gl }: { gl: THREE.WebGLRenderer }) => {
const canvas = gl.domElement;
const loseContextExt = gl.getContext().getExtension("WEBGL_lose_context");
const handleContextLost = (event: Event) => {
event.preventDefault();
logger.error("WebGL", "Repair scene context lost — attempting restore");
window.setTimeout(() => loseContextExt?.restoreContext(), 500);
};
const handleContextRestored = () => {
logger.info("WebGL", "Repair scene context restored");
};
canvas.addEventListener("webglcontextlost", handleContextLost);
canvas.addEventListener("webglcontextrestored", handleContextRestored);
}, []);
return (
<Canvas
camera={{ position: CAMERA_POSITION, fov: 42 }}
shadows={{ type: THREE.PCFShadowMap }}
gl={{
powerPreference: "high-performance",
antialias: true,
stencil: false,
}}
onCreated={handleCreated}
>
<color attach="background" args={[REPAIR_SCENE_BG]} />
<RepairSceneCamera />
{/* Lighting — mirrors the game world defaults */}
<ambientLight intensity={AMBIENT_INTENSITY} color={AMBIENT_COLOR} />
<directionalLight
position={SUN_POSITION}
intensity={SUN_INTENSITY}
color={SUN_COLOR}
castShadow
shadow-mapSize-width={1024}
shadow-mapSize-height={1024}
/>
<Suspense fallback={null}>
{/* Physics is required: TriggerObject and GrabbableObject both use
RigidBody. The world is minimal — no octree, no character bodies. */}
<Physics>
<RepairGame
mission={mission}
position={REPAIR_SCENE_POSITION}
snapToTerrain={false}
/>
</Physics>
</Suspense>
<DebugPerf />
</Canvas>
);
}