diff --git a/src/App.tsx b/src/App.tsx index 8f44ccd..395c086 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,16 +2,13 @@ import "./App.css"; import { Canvas } from "@react-three/fiber"; import { KeyboardControls } from "@react-three/drei"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef } from "react"; import { PCFSoftShadowMap, Vector3 } from "three"; import { Player } from "./components/Player"; import { Bloom, EffectComposer, Vignette } from "@react-three/postprocessing"; -import { Cubes } from "./components/Cubes"; import { GameContext } from "./hooks/GameContext"; import { ChunkGenerator } from "./components/ChunkGenerator"; -const CUBES_QT = 10; - function App() { const container = useRef(null); @@ -19,15 +16,6 @@ function App() { container.current?.focus(); }); - const [cubes] = useState( - Array.from({ length: CUBES_QT }, (_, i) => - Array.from( - { length: CUBES_QT }, - (_, j) => new Vector3(i + 20 - CUBES_QT / 2, 0, j - CUBES_QT / 2) - ) - ).flat() - ); - return (
- - { - const noise = new ImprovedNoise(); - return noise.noise(x, y, props.seed); - }; - - canvas.width = canvas.height = ADJUSTED_SIZE; - const context = canvas.getContext("2d", { willReadFrequently: true }); - if (!context) throw new Error("Canvas context not found"); - - const offsetX = -OVERLAP + props.position.x; - const offsetY = -OVERLAP + props.position.y; - - const imageData = context.getImageData(0, 0, ADJUSTED_SIZE, ADJUSTED_SIZE); - const data = imageData.data; - - for (let x = 0; x < ADJUSTED_SIZE; x++) { - for (let y = 0; y < ADJUSTED_SIZE; y++) { - const n = - noise((x + offsetX) / ADJUSTED_SCALE, (y + offsetY) / ADJUSTED_SCALE) * - 127 + - 127; - const id = (x + y * ADJUSTED_SIZE) * 4; - data[id] = n; - data[id + 1] = n; - data[id + 2] = n; - data[id + 3] = 255; - } - } - context.putImageData(imageData, 0, 0); - const displacementMap = new TextureLoader().load(canvas.toDataURL()); - displacementMap.wrapS = displacementMap.wrapT = ClampToEdgeWrapping; - - useEffect(() => { - return () => { - displacementMap.dispose(); +export const Chunk = memo( + (props: { position: Vector2; seed: number }): JSX.Element => { + const noise = (x: number, y: number): number => { + const noise = new ImprovedNoise(); + return noise.noise(x, y, props.seed); }; - }, [displacementMap]); - return ( - - - - - - - ); -} + canvas.width = canvas.height = ADJUSTED_SIZE; + const context = canvas.getContext("2d", { willReadFrequently: true }); + if (!context) throw new Error("Canvas context not found"); + + const offsetX = -OVERLAP + props.position.x; + const offsetY = -OVERLAP + props.position.y; + + const imageData = context.getImageData(0, 0, ADJUSTED_SIZE, ADJUSTED_SIZE); + const data = imageData.data; + + for (let x = 0; x < ADJUSTED_SIZE; x++) { + for (let y = 0; y < ADJUSTED_SIZE; y++) { + const n = + noise( + (x + offsetX) / ADJUSTED_SCALE, + (y + offsetY) / ADJUSTED_SCALE + ) * + 127 + + 127; + const id = (x + y * ADJUSTED_SIZE) * 4; + data[id] = n; + data[id + 1] = n; + data[id + 2] = n; + data[id + 3] = 255; + } + } + context.putImageData(imageData, 0, 0); + const displacementMap = new TextureLoader().load(canvas.toDataURL()); + displacementMap.wrapS = displacementMap.wrapT = ClampToEdgeWrapping; + + useEffect(() => { + return () => { + displacementMap.dispose(); + }; + }, [displacementMap]); + + return ( + + + + + + + ); + } +); diff --git a/src/components/ChunkGenerator.tsx b/src/components/ChunkGenerator.tsx index 85b181b..d95eaed 100644 --- a/src/components/ChunkGenerator.tsx +++ b/src/components/ChunkGenerator.tsx @@ -1,53 +1,111 @@ -import { useContext, useEffect, useState } from "react"; +import { useContext, useRef, useState } from "react"; import { GameContext } from "../hooks/GameContext"; import { useFrame } from "@react-three/fiber"; import { Vector2 } from "three"; import { Chunk } from "./Chunk"; +const CHUNK_RADIUS = 20; +const CHUNK_PER_FRAME = 3; + export function ChunkGenerator(): JSX.Element { const { playerPosition } = useContext(GameContext); - const [lastPlayerPosition] = useState(new Vector2(0, 0)); + const [lastPlayerPosition] = useState(new Vector2()); + const [chunks, setChunks] = useState([] as JSX.Element[]); + const [chunkQueue, setChunkQueue] = useState([] as JSX.Element[]); + const generatedChunkKeys = useRef(new Set()); const chunkPosVec2 = new Vector2(); - const isChunkAlreadyGenerated = (x: number, y: number) => - chunks.some((chunk) => chunk.key === `${x}-${y}`); + const getChunkCoordinates = (x: number, y: number): number[] => { + return [Math.floor(x / 100 + 0.5), Math.floor(y / 100 + 0.5)]; + }; - function generateNewChunks(playerX: number, playerY: number) { - const newChunks = []; - for (let x = -2; x <= 2; x++) { - for (let y = -2; y <= 2; y++) { - if (isChunkAlreadyGenerated(playerX + x, playerY + y)) continue; + const isChunkAlreadyGenerated = (x: number, y: number, key: string) => + generatedChunkKeys.current.has(`${x}-${y}`) || + chunkQueue.some((chunk) => chunk.key === key); + + const isChunkInPlayerRadius = (chunkPos: number[], playerPos: number[]) => + chunkPos[0] < playerPos[0] - CHUNK_RADIUS || + chunkPos[0] > playerPos[0] + CHUNK_RADIUS || + chunkPos[1] < playerPos[1] - CHUNK_RADIUS || + chunkPos[1] > playerPos[1] + CHUNK_RADIUS; + + function generateNewChunks(playerX: number, playerY: number): void { + const newChunks = [] as JSX.Element[]; + for (let x = -CHUNK_RADIUS; x <= CHUNK_RADIUS; x++) { + for (let y = -CHUNK_RADIUS; y <= CHUNK_RADIUS; y++) { + const key = `${playerX + x}-${playerY + y}`; + if (isChunkAlreadyGenerated(playerX + x, playerY + y, key)) continue; newChunks.push( ); + generatedChunkKeys.current.add(key); } } - setChunks([...chunks, ...newChunks]); + setChunkQueue((prevQueue) => [...prevQueue, ...newChunks]); + } + + function getFilteredChunks(prevChunks: JSX.Element[], playerPos: number[]) { + const filteredPrevChunks = [] as JSX.Element[]; + + let qtDeleted = 0; + prevChunks.forEach((chunk) => { + const chunkPos = getChunkCoordinates( + chunk.props.position.x, + chunk.props.position.y + ); + + if ( + qtDeleted < CHUNK_PER_FRAME && + isChunkInPlayerRadius(chunkPos, playerPos) + ) { + generatedChunkKeys.current.delete(`${chunkPos[0]}-${chunkPos[1]}`); + qtDeleted++; + } else { + filteredPrevChunks.push(chunk); + } + }); + return filteredPrevChunks; + } + + function setNewChunks(queuedChunks: JSX.Element[], playerPos: number[]) { + setChunks((prevChunks) => { + const newChunks = queuedChunks + .splice(-CHUNK_PER_FRAME) + .filter( + (chunk) => + !prevChunks.some((prevChunk) => prevChunk.key === chunk.key) + ); + const filteredPrevChunks = getFilteredChunks(prevChunks, playerPos); + return [...filteredPrevChunks, ...newChunks]; + }); } useFrame(() => { - const playerX = Math.floor(playerPosition.x / 100 + 0.5); - const playerY = Math.floor(playerPosition.z / 100 + 0.5); + const playerPos = getChunkCoordinates(playerPosition.x, playerPosition.z); - if (lastPlayerPosition.x === playerX && lastPlayerPosition.y === playerY) - return; + if (chunkQueue.length > 0) { + setChunkQueue((prevQueue) => { + setNewChunks(prevQueue, playerPos); + return prevQueue; + }); + } - generateNewChunks(playerX, playerY); - - lastPlayerPosition.set(playerX, playerY); + if ( + lastPlayerPosition.x !== playerPos[0] || + lastPlayerPosition.y !== playerPos[1] + ) { + generateNewChunks(playerPos[0], playerPos[1]); + lastPlayerPosition.set(playerPos[0], playerPos[1]); + } }); - useEffect(() => { - generateNewChunks(0, 0); - }, []); - return <>{chunks}; } diff --git a/src/components/Player.tsx b/src/components/Player.tsx index f73660b..2bf3bc6 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -21,8 +21,7 @@ export interface KeyPressed { sprint: boolean; } -const PLAYER_SPEED = 4.75 * 10; -const DRAW_DISTANCE = 128; +const PLAYER_SPEED = 4.5 * 100; const SHADOW_RESOLUTION = 2048; const MOUSE_X_SENSITIVITY = 0.002; const MOUSE_Y_SENSITIVITY = 0.001; @@ -30,7 +29,7 @@ const ANIM_SPEED = 0.2; const CAMERA_RADIUS = 2; const CAMERA_HEIGHT = 1.5; -const SHADOW_DRAW_DISTANCE = DRAW_DISTANCE * 1.2; +const SHADOW_DRAW_DISTANCE = 128; export function Player(): JSX.Element { const model = useGLTF("/Adventurer.glb"); @@ -181,7 +180,7 @@ export function Player(): JSX.Element { function setDirLightPosition(): void { dirLightRef.current?.position.set( model.scene.position.x + 100, - 70, + model.scene.position.y + 100, model.scene.position.z + 100 ); } @@ -203,7 +202,7 @@ export function Player(): JSX.Element { child.receiveShadow = true; } }); - model.scene.position.y = 10; + model.scene.position.y = 70; if (dirLightRef.current) { dirLightRef.current.shadow.bias = -0.0001; } @@ -234,10 +233,7 @@ export function Player(): JSX.Element { ]} /> - + ); }