feat: he is here.

This commit is contained in:
2026-05-31 23:24:14 -03:00
parent fe686b0071
commit 339c660bcb
6 changed files with 624 additions and 473 deletions
+369
View File
@@ -0,0 +1,369 @@
import { useEffect, useRef, useState } from "react";
import { FEAR_SETTINGS, fearState } from "../state";
import { useTexture } from "@react-three/drei";
import * as THREE from "three";
import { useFrame } from "@react-three/fiber";
interface DoorProps {
position: [number, number, number];
rotation: [number, number, number];
}
function Door({ position, rotation }: DoorProps) {
return (
<group position={position} rotation={rotation}>
<mesh position={[0, 2, -0.14]}>
<boxGeometry args={[2.4, 4.0, 0.2]} />
<meshStandardMaterial color="#8a8585" roughness={0.8} metalness={0.2} />
</mesh>
<mesh position={[0, 1.95, -0.08]}>
<boxGeometry args={[2.1, 3.8, 0.1]} />
<meshStandardMaterial color="#4e4b4b" roughness={0.7} metalness={0.2} />
</mesh>
<mesh position={[0.9, 1.8, 0.08]}>
<boxGeometry args={[0.08, 0.08, 0.15]} />
<meshStandardMaterial color="#4e4b4b" roughness={0.4} metalness={0.2} />
</mesh>
</group>
);
}
export default function Hallway() {
const [width, setWidth] = useState(fearState.currentWidth);
const [floorTex, wallTex, rustTex] = useTexture([
'fear/img/concrete-floor.png',
'fear/img/concrete-wall.png',
'fear/img/rust.png'
]);
useEffect(() => {
[floorTex, wallTex, rustTex].forEach((tex) => {
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.minFilter = tex.magFilter = THREE.NearestFilter;
tex.colorSpace = THREE.SRGBColorSpace;
});
}, [floorTex, wallTex, rustTex]);
const segmentPool = [0, 1, 2, 3, 4];
const segmentCount = segmentPool.length;
const lightRefs = useRef<(THREE.PointLight | null)[]>([]);
const matRefs = useRef<(THREE.MeshStandardMaterial | null)[]>([]);
const lightState = useRef<'normal' | 'flickering' | 'dead'>('normal');
const stateEndTime = useRef<number>(0);
const nextEventTime = useRef<number>(5);
const segmentsRef = useRef<THREE.Group[]>([]);
const wallMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
const floorMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
const pipeMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
const bracketMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
wallMaterialsRef.current = [];
floorMaterialsRef.current = [];
pipeMaterialsRef.current = [];
bracketMaterialsRef.current = [];
const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);
useEffect(() => {
const unsubscribe = fearState.subscribe(() => {
setWidth(fearState.currentWidth);
setIsRustActive(fearState.isRustActive);
});
return () => unsubscribe();
}, []);
useFrame((state, delta) => {
const time = state.clock.elapsedTime;
fearState.update(delta);
if (fearState.isRustActive !== isRustActive)
setIsRustActive(fearState.isRustActive);
/*
lights
*/
let intensity1 = 0.85 + Math.sin(time * 2) * 0.03;
if (time > nextEventTime.current && lightState.current === 'normal') {
lightState.current = 'flickering';
stateEndTime.current = time + 1.5 + Math.random() * 2;
}
if (lightState.current === 'flickering') {
if (time > stateEndTime.current) {
if (Math.random() > 0.4) {
lightState.current = 'dead';
stateEndTime.current = time + 1.0 + Math.random() * 2.5;
} else {
lightState.current = 'normal';
nextEventTime.current = time + 10 + Math.random() * 20;
}
} else {
const baseWave = Math.sin(time * 45) * 0.4 + Math.sin(time * 90) * 0.3;
intensity1 = 0.5 + baseWave;
if (Math.sin(time * 150) + Math.cos(time * 220) > 1.2) intensity1 *= Math.random() > 0.5 ? 0.0 : 0.15;
}
}
if (lightState.current === 'dead') {
if (time > stateEndTime.current) {
lightState.current = 'normal';
nextEventTime.current = time + 12 + Math.random() * 15;
} else {
intensity1 = Math.random() > 0.98 ? 0.08 : 0.0;
}
}
/*
objects
*/
const length = FEAR_SETTINGS.HALLWAY_LENGTH;
const playerSegmentZ = Math.floor(state.camera.position.z / length);
const horizontalTexRepeat = width / FEAR_SETTINGS.HALLWAY_WIDTH;
floorTex.repeat.set(horizontalTexRepeat, 10);
wallTex.repeat.set(10, 1);
rustTex.repeat.set(horizontalTexRepeat, 10);
floorTex.needsUpdate = true;
wallTex.needsUpdate = true;
rustTex.needsUpdate = true;
let closestPoolIndex = 0;
let minDistance = Infinity;
segmentsRef.current.forEach((segGroup, poolIndex) => {
if (!segGroup) return;
let segmentZIndex = poolIndex - Math.floor(segmentCount / 2) + playerSegmentZ;
segGroup.position.z = segmentZIndex * length;
// Track which pool index is currently physically closest to the player's camera position
const distance = Math.abs(segGroup.position.z - state.camera.position.z);
if (distance < minDistance) {
minDistance = distance;
closestPoolIndex = poolIndex;
}
const leftWallGroup = segGroup.getObjectByName("left-wall-group");
if (leftWallGroup) leftWallGroup.position.x = -width / 2;
const rightWallGroup = segGroup.getObjectByName("right-wall-group");
if (rightWallGroup) rightWallGroup.position.x = width / 2;
const floorMesh = segGroup.getObjectByName("floor-mesh");
if (floorMesh) floorMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;
const ceilingMesh = segGroup.getObjectByName("ceiling-mesh");
if (ceilingMesh) ceilingMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;
for (let i = 0; i < 3; i++) {
const pipe = segGroup.getObjectByName(`pipe-${i}`);
if (pipe) pipe.position.x = -width / 2 + 0.4 + (i * 0.20);
}
const bracketGroup = segGroup.getObjectByName("brackets-group");
if (bracketGroup) {
bracketGroup.children.forEach(b => {
b.position.x = -width / 2 + 0.6;
});
}
});
/*
dyn light
*/
segmentPool.forEach((poolIndex) => {
const light = lightRefs.current[poolIndex];
const mat = matRefs.current[poolIndex];
if (poolIndex === closestPoolIndex) {
if (light) light.intensity = intensity1 * 1.2;
if (mat) {
mat.emissiveIntensity = intensity1 * 2.5;
if (lightState.current !== 'normal') mat.emissive.setHSL(0.07, 0.4, Math.min(intensity1, 0.7));
else mat.emissive.setHex(0xa8a1a1);
}
} else {
if (light) light.intensity = 0.9;
if (mat) {
mat.emissiveIntensity = 0.8;
mat.emissive.setHex(0xa8a1a1);
}
}
});
/*
materials
*/
const updateMaterials = (materials: THREE.MeshStandardMaterial[], defaultTex: THREE.Texture, activeColor: string, defaultColor: string, activeRough: number, defaultRough: number, activeMetal: number, defaultMetal: number) => {
materials.forEach(mat => {
if (!mat) return;
const targetTex = isRustActive ? rustTex : defaultTex;
if (mat.map !== targetTex) {
mat.map = targetTex;
mat.needsUpdate = true;
}
mat.color.set(isRustActive ? activeColor : defaultColor);
mat.roughness = isRustActive ? activeRough : defaultRough;
mat.metalness = isRustActive ? activeMetal : defaultMetal;
});
};
updateMaterials(wallMaterialsRef.current, wallTex, "#918a87", "#ffffff", 0.95, 0.7, 0.05, 0.1);
updateMaterials(floorMaterialsRef.current, floorTex, "#8b827f", "#ffffff", 0.95, 0.8, 0.05, 0.2);
pipeMaterialsRef.current.forEach(mat => {
if (!mat) return;
mat.color.set(isRustActive ? "#3d1b0f" : "#a5aca8");
mat.roughness = isRustActive ? 0.95 : 0.0;
mat.metalness = isRustActive ? 0.05 : 0.4;
});
bracketMaterialsRef.current.forEach(mat => {
if (!mat) return;
mat.color.set(isRustActive ? "#1b0b05" : "#a5aca8");
mat.roughness = isRustActive ? 0.95 : 0.0;
mat.metalness = isRustActive ? 0.05 : 0.4;
});
});
return (
<>
<ambientLight intensity={0.0225} />
{segmentPool.map((poolIndex) => (
<group
key={poolIndex}
ref={(el) => { if (el) segmentsRef.current[poolIndex] = el; }}
position={[0, 0, 0]}
>
{/* lights */}
<group position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.1, -FEAR_SETTINGS.HALLWAY_LENGTH / 4]}>
<pointLight
ref={(el) => { lightRefs.current[poolIndex] = el; }}
intensity={0.9}
distance={15}
color="#a8a1a1"
/>
<mesh position={[0, 0.09, 0]}>
<boxGeometry args={[0.3, 0.01, 0.3]} />
<meshStandardMaterial
ref={(el) => { matRefs.current[poolIndex] = el; }}
color="#111111"
emissive="#a8a1a1"
emissiveIntensity={0.8}
roughness={0.9}
/>
</mesh>
</group>
{/* floor */}
<mesh
name="floor-mesh"
rotation={[-Math.PI / 2, 0, 0]}
position={[0, 0, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
>
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH]} />
<meshStandardMaterial
ref={(el) => { if (el) floorMaterialsRef.current.push(el); }}
map={floorTex}
roughness={0.8}
metalness={0.2}
/>
</mesh>
{/* ceiling */}
<mesh
name="ceiling-mesh"
rotation={[Math.PI / 2, 0, 0]}
position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
>
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH]} />
<meshStandardMaterial
ref={(el) => { if (el) floorMaterialsRef.current.push(el); }}
map={floorTex}
roughness={0.8}
metalness={0.2}
/>
</mesh>
{/* left wall */}
<group name="left-wall-group">
<mesh rotation={[0, Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}>
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT]} />
<meshStandardMaterial
ref={(el) => { if (el) wallMaterialsRef.current.push(el); }}
map={wallTex}
roughness={0.7}
metalness={0.1}
/>
</mesh>
{!isRustActive && (
<>
<Door position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.25]} rotation={[0, Math.PI / 2, 0]} />
<Door position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.85]} rotation={[0, Math.PI / 2, 0]} />
</>
)}
</group>
{/* right wall */}
<group name="right-wall-group">
<mesh rotation={[0, -Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}>
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT]} />
<meshStandardMaterial
ref={(el) => { if (el) wallMaterialsRef.current.push(el); }}
map={wallTex}
roughness={0.7}
metalness={0.1}
/>
</mesh>
{!isRustActive && (
<Door position={[-0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.65]} rotation={[0, -Math.PI / 2, 0]} />
)}
</group>
{/* pipes */}
{Array.from({ length: 3 }).map((_, idx) => (
<mesh
key={idx}
name={`pipe-${idx}`}
rotation={[Math.PI / 2, 0, 0]}
position={[-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.4 + (idx * 0.20), FEAR_SETTINGS.HALLWAY_HEIGHT - 0.2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
>
<cylinderGeometry args={[0.06, 0.06, FEAR_SETTINGS.HALLWAY_LENGTH, 4]} />
<meshStandardMaterial
ref={(el) => el && pipeMaterialsRef.current.push(el)}
color="#a5aca8"
roughness={0.0}
metalness={0.4}
/>
</mesh>
))}
{/* brackets */}
<group name="brackets-group">
{Array.from({ length: 5 }).map((_, idx) => {
const zOffset = -(idx * 8 + 4);
return (
<mesh
key={`bracket-${idx}`}
position={[-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.6, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.15, zOffset]}
>
<boxGeometry args={[0.7, 0.3, 0.15]} />
<meshStandardMaterial
ref={(el) => el && bracketMaterialsRef.current.push(el)}
color="#a5aca8"
roughness={0.0}
metalness={0.4}
/>
</mesh>
);
})}
</group>
</group>
))}
</>
);
}