diff --git a/public/fear/img/concrete-floor.jpg b/public/fear/img/concrete-floor.jpg new file mode 100644 index 0000000..0c5d14f Binary files /dev/null and b/public/fear/img/concrete-floor.jpg differ diff --git a/public/fear/img/concrete-wall.jpg b/public/fear/img/concrete-wall.jpg new file mode 100644 index 0000000..1ca6561 Binary files /dev/null and b/public/fear/img/concrete-wall.jpg differ diff --git a/public/fear/snd/ambience.mp3 b/public/fear/snd/ambience.mp3 new file mode 100644 index 0000000..4f6be61 Binary files /dev/null and b/public/fear/snd/ambience.mp3 differ diff --git a/public/img/boom.gif b/public/img/boom.gif deleted file mode 100644 index 3778b75..0000000 Binary files a/public/img/boom.gif and /dev/null differ diff --git a/src/app/fear/page.css b/src/app/fear/page.css new file mode 100644 index 0000000..5f58ba7 --- /dev/null +++ b/src/app/fear/page.css @@ -0,0 +1,4 @@ +.canvas { + width: 100vw !important; + height: 100vh !important; +} \ No newline at end of file diff --git a/src/app/fear/page.tsx b/src/app/fear/page.tsx new file mode 100644 index 0000000..bcfd841 --- /dev/null +++ b/src/app/fear/page.tsx @@ -0,0 +1,291 @@ +'use client'; + +import './page.css'; + +import { PointerLockControls, useTexture } from "@react-three/drei"; +import { Canvas, useFrame, useThree } from "@react-three/fiber"; +import { BrightnessContrast, EffectComposer, Noise, Pixelation, Vignette } from "@react-three/postprocessing"; +import { Suspense, useEffect, useRef } from "react"; + +import * as THREE from "three"; +import { AmbientSound } from './scene-components/ambient-sound'; +import { MeshStandardNodeMaterial } from 'three/webgpu'; + +const HALLWAY_LENGTH = 40; +const HALLWAY_WIDTH = 6; +const HALLWAY_HEIGHT = 5; + +const PLAYER_HEIGHT = 3; + +function Player() { + const { camera } = useThree(); + const controls = usePlayerControls(); + + const flashlightRef = useRef(null); + const movementCounter = useRef(0); + + const frontVector = new THREE.Vector3(); + const sideVector = new THREE.Vector3(); + const direction = new THREE.Vector3(); + + const viewDirection = new THREE.Vector3(); + + useFrame((state, delta) => { + frontVector.set(0, 0, Number(controls.Backward) - Number(controls.Forward)); + sideVector.set(Number(controls.Left) - Number(controls.Right), 0, 0); + + direction + .subVectors(frontVector, sideVector) + .normalize() + .multiplyScalar(4 * delta) + .applyEuler(camera.rotation); + + camera.position.x += direction.x; + camera.position.z += direction.z; + + const isMoving = controls.Forward || controls.Backward || controls.Left || controls.Right; + if (isMoving) { + movementCounter.current += delta * 10; + camera.position.y = PLAYER_HEIGHT + Math.sin(movementCounter.current) * 0.08; + camera.position.x += Math.cos(movementCounter.current / 2) * 0.006; + } else + camera.position.y = THREE.MathUtils.lerp(camera.position.y, PLAYER_HEIGHT, 5 * delta); + + + if (flashlightRef.current) { + flashlightRef.current.position.copy(camera.position); + + camera.getWorldDirection(viewDirection); + + flashlightRef.current.target.position + .copy(camera.position) + .addScaledVector(viewDirection, 10); + flashlightRef.current.target.updateMatrixWorld(); + flashlightRef.current.intensity = 5 + Math.sin(state.clock.elapsedTime * 30) * 0.3; + } + + const wallBuffer = 0.6; + const HALLWAY_WIDTH = 6; + const HALLWAY_LENGTH = 40; + + if (camera.position.x < -HALLWAY_WIDTH / 2 + wallBuffer) camera.position.x = -HALLWAY_WIDTH / 2 + wallBuffer; + if (camera.position.x > HALLWAY_WIDTH / 2 - wallBuffer) camera.position.x = HALLWAY_WIDTH / 2 - wallBuffer; + + if (camera.position.z < -HALLWAY_LENGTH) camera.position.z += HALLWAY_LENGTH; + if (camera.position.z > 0) camera.position.z -= HALLWAY_LENGTH; + }); + + return ( + <> + + + + ); +} + +function usePlayerControls() { + const keys = useRef({ Forward: false, Backward: false, Left: false, Right: false }); + + useEffect(() => { + const handleKeyDown = (e) => { + if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = true; + if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = true; + if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = true; + if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = true; + }; + + const handleKeyUp = (e) => { + if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = false; + if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = false; + if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = false; + if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = false; + }; + + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + }; + }, []); + + return keys.current; +} + +interface DoorProps { + position: [number, number, number]; + rotation: [number, number, number]; +} +function Door({ position, rotation }: DoorProps) { + return ( + + + + + + + + + + + + + + + + + ); +} + +function Hallway() { + const [floorTex, wallTex] = useTexture([ + 'fear/img/concrete-floor.jpg', + 'fear/img/concrete-wall.jpg' + ]); + + floorTex.wrapS = floorTex.wrapT = THREE.RepeatWrapping; + floorTex.repeat.set(1, 10); + floorTex.minFilter = THREE.NearestFilter; + floorTex.magFilter = THREE.NearestFilter; + + + wallTex.wrapS = wallTex.wrapT = THREE.RepeatWrapping; + wallTex.repeat.set(10, 1); + wallTex.minFilter = THREE.NearestFilter; + wallTex.magFilter = THREE.NearestFilter; + + const segments = [0, -1, 1, -2, 2]; + + const lightRef1 = useRef(null); + const lightRef2 = useRef(null); + + useFrame((state) => { + const time = state.clock.elapsedTime; + const flicker = Math.sin(time * 10) * Math.cos(time * 3) > 0.4 ? 0 : 0.8; + if (lightRef1.current) lightRef1.current.intensity = flicker; + if (lightRef2.current) lightRef2.current.intensity = flicker * 0.5; + }); + + return ( + <> + {/* */} + + + {segments.map((i) => ( + + + + + {/* floor */} + + + + + + {/* roof */} + + + + + + {/* l wall */} + + + + + + {/* r wall */} + + + + + + {/* doors */} + + + + + + + {/* pipes */} + + {Array.from({ length: 3 }).map((_, idx) => ( + + + + + ))} + + {Array.from({ length: 5 }).map((_, idx) => { + const zOffset = -(idx * 8 + 4); + + return ( + + + + + ); + })} + + ))} + + ); +} + +function PostProcessing() { + return ( + + + + + ) +} + +export default function Fear() { + return (<> + + + + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/app/fear/scene-components/ambient-sound.tsx b/src/app/fear/scene-components/ambient-sound.tsx new file mode 100644 index 0000000..27e9b2f --- /dev/null +++ b/src/app/fear/scene-components/ambient-sound.tsx @@ -0,0 +1,43 @@ +import { useEffect, useRef } from 'react' + +interface AmbientSoundProps { + url: string + volume?: number +} + +export function AmbientSound({ url, volume = 0.5 }: AmbientSoundProps) { + const audioRef = useRef(null) + + useEffect(() => { + const audio = new Audio(url) + audio.loop = true + audio.volume = volume + audioRef.current = audio + + const startAudio = () => { + audio.play().catch((err) => { + console.warn('Autoplay blocked. Waiting for user interaction.', err) + }) + } + + startAudio() + + window.addEventListener('click', startAudio, { once: true }) + window.addEventListener('keydown', startAudio, { once: true }) + + return () => { + window.removeEventListener('click', startAudio) + window.removeEventListener('keydown', startAudio) + audio.pause() + audioRef.current = null + } + }, [url]) + + useEffect(() => { + if (audioRef.current) { + audioRef.current.volume = volume + } + }, [volume]) + + return null +} \ No newline at end of file diff --git a/src/app/niko/scene-components/shaders/grass.vert b/src/app/niko/scene-components/shaders/grass.vert index dcfd202..fab2125 100644 --- a/src/app/niko/scene-components/shaders/grass.vert +++ b/src/app/niko/scene-components/shaders/grass.vert @@ -8,6 +8,7 @@ float windSpeed = 1.5; float windTime = uTime * windSpeed; vec2 windSamplePos = (worldPos.xz * 0.05) - (mainWindDir * windTime * 0.2); + float windBase = fbm(windSamplePos * 0.8) * 0.4 + 0.2; float gustNoise = fbm(windSamplePos * 0.4); float gust = pow(gustNoise, 3.0) * 1.8; diff --git a/src/app/page.tsx b/src/app/page.tsx index 5fd35ed..5742f1a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -29,7 +29,7 @@ function Content() { <>
- +

neru