From deaafc9e22eff50f0dc75fa787d463147a1f35c9 Mon Sep 17 00:00:00 2001 From: neru <152752583+neeeruuu@users.noreply.github.com> Date: Sat, 3 Jan 2026 06:42:19 -0300 Subject: [PATCH] feat: move seal page to its own file --- src/app/page.tsx | 677 +----------------------- src/app/pages/seal.tsx | 684 +++++++++++++++++++++++++ src/app/{ => pages}/shaders/grass.frag | 0 src/app/{ => pages}/shaders/grass.vert | 0 4 files changed, 687 insertions(+), 674 deletions(-) create mode 100644 src/app/pages/seal.tsx rename src/app/{ => pages}/shaders/grass.frag (100%) rename src/app/{ => pages}/shaders/grass.vert (100%) diff --git a/src/app/page.tsx b/src/app/page.tsx index 68b62bb..3f935bf 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,683 +1,12 @@ 'use client'; -import { Environment, OrbitControls, useProgress } from '@react-three/drei'; -import { Canvas, useLoader, useFrame } from '@react-three/fiber'; -import { - forwardRef, - useImperativeHandle, - useLayoutEffect, - useMemo, - useRef, - useState -} from 'react'; - -import { - BufferAttribute, - BufferGeometry, - Mesh, - Object3D, - InstancedMesh, - DoubleSide, - TextureLoader, - Color, - MeshStandardMaterial -} from 'three'; - import './page.css'; -import { createNoise2D } from 'simplex-noise'; - -import grassVert from './shaders/grass.vert'; -import grassFrag from './shaders/grass.frag'; -import { - Bloom, - EffectComposer, - Noise, - Vignette, - DepthOfField, - SMAA, - HueSaturation, - BrightnessContrast, - LUT -} from '@react-three/postprocessing'; -import { LUTCubeLoader } from 'three/examples/jsm/loaders/LUTCubeLoader.js'; - -interface Shader { - uniforms: { [key: string]: { value: unknown } }; - vertexShader: string; - fragmentShader: string; -} - -function getTerrainHeight( - localX: number, - localZ: number, - worldXBase: number, - worldZBase: number, - scale: number, - hillScale: number, - hillHeight: number, - detailScale: number, - detailHeight: number, - noise2D: (x: number, y: number) => number -) { - const worldX = (worldXBase + localX) * 0.1; - const worldZ = (worldZBase + localZ) * 0.1; - - const noiseHill = - noise2D(worldX * hillScale, worldZ * hillScale) * hillHeight; - const noiseDetail = - noise2D(worldX * detailScale, worldZ * detailScale) * detailHeight; - - return (noiseHill + noiseDetail) * scale; -} - -interface GrassProps { - x: number; - y: number; - size: number; - count: number; - grassSize: number; - scale: number; - hillScale: number; - hillHeight: number; - detailScale: number; - detailHeight: number; - noise2D: (x: number, y: number) => number; - grassLOD: number; - enableShadows?: boolean; -} - -function Grass({ - x, - y, - size, - count, - grassSize, - scale, - hillScale, - hillHeight, - detailScale, - detailHeight, - noise2D, - grassLOD -}: GrassProps) { - const meshRef = useRef(null); - const dummyRef = useRef(new Object3D()); - - const [alphaMap, normalMap] = useLoader(TextureLoader, [ - '/img/grass_alpha.png', - '/img/grass_normal.png' - ]); - - const materialRef = useRef< - MeshStandardMaterial & { userData: { shader: Shader } } - >(null); - - useFrame((state) => { - if ( - materialRef.current && - materialRef.current.userData && - materialRef.current.userData.shader - ) { - (materialRef.current.userData.shader as Shader).uniforms.uTime.value = - state.clock.getElapsedTime(); - } - }); - - const geometry = useMemo(() => { - const geo = new BufferGeometry(); - - const w = 0.5; - const h = 2; - const segments = 4; - - const positions: number[] = []; - const uvs: number[] = []; - const indices: number[] = []; - - for (let i = 0; i < 3; i++) { - const angle = (Math.PI / 3) * i; - const sinA = Math.sin(angle); - const cosA = Math.cos(angle); - - const offset = positions.length / 3; - - for (let y = 0; y <= segments; y++) { - const v = y / segments; - const yPos = v * h; - - positions.push(-w * cosA, yPos, -w * sinA); - uvs.push(0, v); - - positions.push(w * cosA, yPos, w * sinA); - uvs.push(1, v); - } - - for (let y = 0; y < segments; y++) { - const row1 = offset + y * 2; - const row2 = offset + (y + 1) * 2; - - indices.push(row1, row1 + 1, row2); - indices.push(row1 + 1, row2 + 1, row2); - } - } - - geo.setAttribute( - 'position', - new BufferAttribute(new Float32Array(positions), 3) - ); - geo.setAttribute('uv', new BufferAttribute(new Float32Array(uvs), 2)); - geo.setIndex(indices); - geo.computeVertexNormals(); - - return geo; - }, []); - - useLayoutEffect(() => { - if (!meshRef.current) return; - const dummy = dummyRef.current; - - const worldXBase = x * size; - const worldZBase = y * size; - - const color = new Color(); - const lushColor = new Color('#348a34'); - const dryColor = new Color('#556b19'); - - let instanceIndex = 0; - - for (let i = 0; i < count; i++) { - const localX = (Math.random() - 0.5) * size; - const localZ = (Math.random() - 0.5) * size; - - const globalX = worldXBase + localX; - const globalZ = worldZBase + localZ; - - const dist = Math.sqrt(globalX * globalX + globalZ * globalZ); - - const maxDist = grassLOD; - - const falloffStart = maxDist * 0.1; - const falloffEnd = maxDist; - - let fallofFactor = (dist - falloffStart) / (falloffEnd - falloffStart); - fallofFactor = Math.max(0, Math.min(1, fallofFactor)); - - const density = (1.0 - fallofFactor) * (1.0 - fallofFactor); - - if (Math.random() > density) continue; - - const localY = getTerrainHeight( - localX, - localZ, - worldXBase, - worldZBase, - scale, - hillScale, - hillHeight, - detailScale, - detailHeight, - noise2D - ); - - dummy.position.set(localX, localY, localZ); - - dummy.rotation.y = Math.random() * Math.PI * 2; - dummy.rotation.x = (Math.random() - 0.5) * 0.2; - dummy.rotation.z = (Math.random() - 0.5) * 0.2; - - const baseScale = grassSize + Math.random() * grassSize * 0.5; - const heightMult = 0.5 + Math.random() * 1.0; - dummy.scale.set(baseScale, baseScale * heightMult, baseScale); - - dummy.updateMatrix(); - meshRef.current.setMatrixAt(instanceIndex, dummy.matrix); - - const noiseVal = noise2D(globalX * 0.02, globalZ * 0.02); - - const t = (noiseVal + 1) / 2; - - const randomInternal = (Math.random() - 0.5) * 0.2; - const finalT = Math.max(0, Math.min(1, t + randomInternal)); - - color.lerpColors(dryColor, lushColor, finalT); - - meshRef.current.setColorAt(instanceIndex, color); - - instanceIndex++; - } - meshRef.current.count = instanceIndex; - - meshRef.current.instanceMatrix.needsUpdate = true; - if (meshRef.current.instanceColor) - meshRef.current.instanceColor.needsUpdate = true; - }, [ - x, - y, - size, - count, - grassSize, - scale, - hillScale, - hillHeight, - detailScale, - detailHeight, - noise2D, - grassLOD - ]); - - const onBeforeCompile = useMemo( - () => (shader: Shader) => { - shader.uniforms.uTime = { value: 0 }; - - shader.vertexShader = ` - uniform float uTime; - varying vec2 vGrassUv; - - float hash(vec2 p) { - return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); - } - - float noise(vec2 p) { - vec2 i = floor(p); - vec2 f = fract(p); - f = f * f * (3.0 - 2.0 * f); - - float a = hash(i); - float b = hash(i + vec2(1.0, 0.0)); - float c = hash(i + vec2(0.0, 1.0)); - float d = hash(i + vec2(1.0, 1.0)); - - return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); - } - - float fbm(vec2 p) { - float value = 0.0; - float amplitude = 0.5; - float frequency = 1.0; - - for(int i = 0; i < 4; i++) { - value += amplitude * noise(p * frequency); - frequency *= 2.0; - amplitude *= 0.5; - } - - return value; - } - - ${shader.vertexShader} - `; - shader.vertexShader = shader.vertexShader.replace( - '#include ', - ` - #include - vGrassUv = uv; - ${grassVert} - ` - ); - - shader.fragmentShader = ` - uniform float uTime; - varying vec2 vGrassUv; - ${shader.fragmentShader} - `; - shader.fragmentShader = shader.fragmentShader.replace( - '#include ', - ` - #include - ${grassFrag} - ` - ); - - if (materialRef.current) { - materialRef.current.userData.shader = shader; - } - }, - [] - ); - - return ( - - - - ); -} - -interface TerrainChunkProps { - x: number; - y: number; - size: number; - resolution: number; - scale: number; - hillScale: number; - hillHeight: number; - detailScale: number; - detailHeight: number; - noise2D: (x: number, y: number) => number; - grassCount: number; - grassSize: number; - grassLOD: number; -} -function TerrainChunk({ - x, - y, - size, - resolution, - scale, - hillScale, - hillHeight, - detailScale, - detailHeight, - noise2D, - grassCount, - grassSize, - grassLOD -}: TerrainChunkProps) { - const chunkDist = Math.sqrt((x * size) ** 2 + (y * size) ** 2); - const shouldRenderGrass = chunkDist < grassLOD + size; - - const meshRef = useRef(null); - - const geometry = useMemo(() => { - const geo = new BufferGeometry(); - - const vertices: Array = []; - const indices: Array = []; - - const step = size / (resolution - 1); - const halfSize = size / 2; - const worldXBase = x * size; - const worldZBase = y * size; - - /* - vtx gen - */ - for (let iz = 0; iz < resolution; iz++) { - for (let ix = 0; ix < resolution; ix++) { - const localX = ix * step - halfSize; - const localZ = iz * step - halfSize; - - const localY = getTerrainHeight( - localX, - localZ, - worldXBase, - worldZBase, - scale, - hillScale, - hillHeight, - detailScale, - detailHeight, - noise2D - ); - - vertices.push(localX, localY, localZ); - } - } - - /* - idx gen - */ - for (let iz = 0; iz < resolution - 1; iz++) { - for (let ix = 0; ix < resolution - 1; ix++) { - const topLeft = iz * resolution + ix; - const topRight = topLeft + 1; - const bottomLeft = (iz + 1) * resolution + ix; - const bottomRight = bottomLeft + 1; - - indices.push(topLeft, bottomLeft, topRight); - indices.push(topRight, bottomLeft, bottomRight); - } - } - geo.setAttribute( - 'position', - new BufferAttribute(new Float32Array(vertices), 3) - ); - - const colors: Array = []; - for (let iz = 0; iz < resolution; iz++) { - for (let ix = 0; ix < resolution; ix++) { - const localX = ix * step - halfSize; - const localZ = iz * step - halfSize; - const globalX = worldXBase + localX; - const globalZ = worldZBase + localZ; - const colorNoise = noise2D(globalX * 0.02, globalZ * 0.02); - const t = (colorNoise + 1) / 2; - - const dryGreen = { r: 0.01, g: 0.01, b: 0.0 }; - const lushGreen = { r: 0.0, g: 0.05, b: 0.0 }; - - const r = dryGreen.r + (lushGreen.r - dryGreen.r) * t; - const g = dryGreen.g + (lushGreen.g - dryGreen.g) * t; - const b = dryGreen.b + (lushGreen.b - dryGreen.b) * t; - - colors.push(r, g, b); - } - } - geo.setAttribute('color', new BufferAttribute(new Float32Array(colors), 3)); - - geo.setIndex(indices); - geo.computeVertexNormals(); - - return geo; - }, [ - x, - y, - size, - resolution, - scale, - hillScale, - hillHeight, - detailScale, - detailHeight, - noise2D - ]); - - return ( - - - - - {shouldRenderGrass && ( - - )} - - ); -} - -interface TerrainProps { - chunks?: number; - chunkSize?: number; - resolution?: number; - scale?: number; - hillScale: number; - hillHeight: number; - detailScale: number; - detailHeight: number; - grassCount?: number; - grassSize?: number; - grassLOD?: number; -} -function Terrain({ - chunks = 5, - chunkSize = 10, - resolution = 8, - scale = 1, - hillScale = 0.2, - hillHeight = 3, - detailScale = 3, - detailHeight = 0.1, - grassCount = 6000, - grassSize = 0.6, - grassLOD = 40 -}: TerrainProps) { - const noise2D = useMemo(() => createNoise2D(), []); - const offset = -Math.floor(chunks / 2); - - const chunkPositions = useMemo(() => { - const positions: [number, number][] = []; - for (let x = 0; x < chunks; x++) - for (let y = 0; y < chunks; y++) positions.push([x + offset, y + offset]); - - return positions; - }, [chunks, offset]); - - return ( - - {chunkPositions.map(([x, y], index) => ( - - ))} - - ); -} - -const SealCube = forwardRef((props, ref) => { - const texture = useLoader(TextureLoader, '/img/niko.jpg'); - const meshRef = useRef(null); - - useImperativeHandle(ref, () => meshRef.current!, []); - - useFrame((state, delta) => { - if (meshRef.current) { - meshRef.current.rotation.x += delta * 0.5; - meshRef.current.rotation.y += delta * 0.5; - } - }); - - return ( - - - - - ); -}); -SealCube.displayName = 'SealCube'; - -function Loader() { - const { progress, active } = useProgress(); - const [visible, setVisible] = useState(true); - - useLayoutEffect(() => { - if (!active && progress === 100) { - const timeout = setTimeout(() => setVisible(false), 500); - return () => clearTimeout(timeout); - } - }, [progress, active]); - - return ( -
- - niko!! - -
- ); -} - -function LutEffect() { - const lutTexture = useLoader(LUTCubeLoader, '/lut/Landscape6.cube'); - return ; -} +import SealHome from './pages/seal'; export default function Home() { return ( <> - - - - - - - - - - - - - - - - - - - - - + - ); + ) } diff --git a/src/app/pages/seal.tsx b/src/app/pages/seal.tsx new file mode 100644 index 0000000..4dfec2f --- /dev/null +++ b/src/app/pages/seal.tsx @@ -0,0 +1,684 @@ +'use client'; + +import { + forwardRef, + useImperativeHandle, + useLayoutEffect, + useMemo, + useRef, + useState +} from 'react'; + +import { + Bloom, + EffectComposer, + Noise, + Vignette, + DepthOfField, + SMAA, + HueSaturation, + BrightnessContrast, + LUT +} from '@react-three/postprocessing'; + +import { Environment, OrbitControls, useProgress } from '@react-three/drei'; +import { Canvas, useLoader, useFrame } from '@react-three/fiber'; +import { + BufferAttribute, + BufferGeometry, + Mesh, + Object3D, + InstancedMesh, + DoubleSide, + TextureLoader, + Color, + MeshStandardMaterial +} from 'three'; + +import { LUTCubeLoader } from 'three/examples/jsm/loaders/LUTCubeLoader.js'; + +import { createNoise2D } from 'simplex-noise'; + +import grassVert from './shaders/grass.vert'; +import grassFrag from './shaders/grass.frag'; + +interface Shader { + uniforms: { [key: string]: { value: unknown } }; + vertexShader: string; + fragmentShader: string; +} + +function getTerrainHeight( + localX: number, + localZ: number, + worldXBase: number, + worldZBase: number, + scale: number, + hillScale: number, + hillHeight: number, + detailScale: number, + detailHeight: number, + noise2D: (x: number, y: number) => number +) { + const worldX = (worldXBase + localX) * 0.1; + const worldZ = (worldZBase + localZ) * 0.1; + + const noiseHill = + noise2D(worldX * hillScale, worldZ * hillScale) * hillHeight; + const noiseDetail = + noise2D(worldX * detailScale, worldZ * detailScale) * detailHeight; + + return (noiseHill + noiseDetail) * scale; +} + +interface GrassProps { + x: number; + y: number; + size: number; + count: number; + grassSize: number; + scale: number; + hillScale: number; + hillHeight: number; + detailScale: number; + detailHeight: number; + noise2D: (x: number, y: number) => number; + grassLOD: number; + enableShadows?: boolean; +} + +function Grass({ + x, + y, + size, + count, + grassSize, + scale, + hillScale, + hillHeight, + detailScale, + detailHeight, + noise2D, + grassLOD +}: GrassProps) { + const meshRef = useRef(null); + const dummyRef = useRef(new Object3D()); + + const [alphaMap, normalMap] = useLoader(TextureLoader, [ + '/img/grass_alpha.png', + '/img/grass_normal.png' + ]); + + const materialRef = useRef< + MeshStandardMaterial & { userData: { shader: Shader } } + >(null); + + useFrame((state) => { + if ( + materialRef.current && + materialRef.current.userData && + materialRef.current.userData.shader + ) { + (materialRef.current.userData.shader as Shader).uniforms.uTime.value = + state.clock.getElapsedTime(); + } + }); + + const geometry = useMemo(() => { + const geo = new BufferGeometry(); + + const w = 0.5; + const h = 2; + const segments = 4; + + const positions: number[] = []; + const uvs: number[] = []; + const indices: number[] = []; + + for (let i = 0; i < 3; i++) { + const angle = (Math.PI / 3) * i; + const sinA = Math.sin(angle); + const cosA = Math.cos(angle); + + const offset = positions.length / 3; + + for (let y = 0; y <= segments; y++) { + const v = y / segments; + const yPos = v * h; + + positions.push(-w * cosA, yPos, -w * sinA); + uvs.push(0, v); + + positions.push(w * cosA, yPos, w * sinA); + uvs.push(1, v); + } + + for (let y = 0; y < segments; y++) { + const row1 = offset + y * 2; + const row2 = offset + (y + 1) * 2; + + indices.push(row1, row1 + 1, row2); + indices.push(row1 + 1, row2 + 1, row2); + } + } + + geo.setAttribute( + 'position', + new BufferAttribute(new Float32Array(positions), 3) + ); + geo.setAttribute('uv', new BufferAttribute(new Float32Array(uvs), 2)); + geo.setIndex(indices); + geo.computeVertexNormals(); + + return geo; + }, []); + + useLayoutEffect(() => { + if (!meshRef.current) return; + const dummy = dummyRef.current; + + const worldXBase = x * size; + const worldZBase = y * size; + + const color = new Color(); + const lushColor = new Color('#348a34'); + const dryColor = new Color('#556b19'); + + let instanceIndex = 0; + + for (let i = 0; i < count; i++) { + const localX = (Math.random() - 0.5) * size; + const localZ = (Math.random() - 0.5) * size; + + const globalX = worldXBase + localX; + const globalZ = worldZBase + localZ; + + const dist = Math.sqrt(globalX * globalX + globalZ * globalZ); + + const maxDist = grassLOD; + + const falloffStart = maxDist * 0.1; + const falloffEnd = maxDist; + + let fallofFactor = (dist - falloffStart) / (falloffEnd - falloffStart); + fallofFactor = Math.max(0, Math.min(1, fallofFactor)); + + const density = (1.0 - fallofFactor) * (1.0 - fallofFactor); + + if (Math.random() > density) continue; + + const localY = getTerrainHeight( + localX, + localZ, + worldXBase, + worldZBase, + scale, + hillScale, + hillHeight, + detailScale, + detailHeight, + noise2D + ); + + dummy.position.set(localX, localY, localZ); + + dummy.rotation.y = Math.random() * Math.PI * 2; + dummy.rotation.x = (Math.random() - 0.5) * 0.2; + dummy.rotation.z = (Math.random() - 0.5) * 0.2; + + const baseScale = grassSize + Math.random() * grassSize * 0.5; + const heightMult = 0.5 + Math.random() * 1.0; + dummy.scale.set(baseScale, baseScale * heightMult, baseScale); + + dummy.updateMatrix(); + meshRef.current.setMatrixAt(instanceIndex, dummy.matrix); + + const noiseVal = noise2D(globalX * 0.02, globalZ * 0.02); + + const t = (noiseVal + 1) / 2; + + const randomInternal = (Math.random() - 0.5) * 0.2; + const finalT = Math.max(0, Math.min(1, t + randomInternal)); + + color.lerpColors(dryColor, lushColor, finalT); + + meshRef.current.setColorAt(instanceIndex, color); + + instanceIndex++; + } + meshRef.current.count = instanceIndex; + + meshRef.current.instanceMatrix.needsUpdate = true; + if (meshRef.current.instanceColor) + meshRef.current.instanceColor.needsUpdate = true; + }, [ + x, + y, + size, + count, + grassSize, + scale, + hillScale, + hillHeight, + detailScale, + detailHeight, + noise2D, + grassLOD + ]); + + const onBeforeCompile = useMemo( + () => (shader: Shader) => { + shader.uniforms.uTime = { value: 0 }; + + shader.vertexShader = ` + uniform float uTime; + varying vec2 vGrassUv; + + float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); + } + + float noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + + return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); + } + + float fbm(vec2 p) { + float value = 0.0; + float amplitude = 0.5; + float frequency = 1.0; + + for(int i = 0; i < 4; i++) { + value += amplitude * noise(p * frequency); + frequency *= 2.0; + amplitude *= 0.5; + } + + return value; + } + + ${shader.vertexShader} + `; + shader.vertexShader = shader.vertexShader.replace( + '#include ', + ` + #include + vGrassUv = uv; + ${grassVert} + ` + ); + + shader.fragmentShader = ` + uniform float uTime; + varying vec2 vGrassUv; + ${shader.fragmentShader} + `; + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + ` + #include + ${grassFrag} + ` + ); + + if (materialRef.current) { + materialRef.current.userData.shader = shader; + } + }, + [] + ); + + return ( + + + + ); +} + +interface TerrainChunkProps { + x: number; + y: number; + size: number; + resolution: number; + scale: number; + hillScale: number; + hillHeight: number; + detailScale: number; + detailHeight: number; + noise2D: (x: number, y: number) => number; + grassCount: number; + grassSize: number; + grassLOD: number; +} +function TerrainChunk({ + x, + y, + size, + resolution, + scale, + hillScale, + hillHeight, + detailScale, + detailHeight, + noise2D, + grassCount, + grassSize, + grassLOD +}: TerrainChunkProps) { + const chunkDist = Math.sqrt((x * size) ** 2 + (y * size) ** 2); + const shouldRenderGrass = chunkDist < grassLOD + size; + + const meshRef = useRef(null); + + const geometry = useMemo(() => { + const geo = new BufferGeometry(); + + const vertices: Array = []; + const indices: Array = []; + + const step = size / (resolution - 1); + const halfSize = size / 2; + const worldXBase = x * size; + const worldZBase = y * size; + + /* + vtx gen + */ + for (let iz = 0; iz < resolution; iz++) { + for (let ix = 0; ix < resolution; ix++) { + const localX = ix * step - halfSize; + const localZ = iz * step - halfSize; + + const localY = getTerrainHeight( + localX, + localZ, + worldXBase, + worldZBase, + scale, + hillScale, + hillHeight, + detailScale, + detailHeight, + noise2D + ); + + vertices.push(localX, localY, localZ); + } + } + + /* + idx gen + */ + for (let iz = 0; iz < resolution - 1; iz++) { + for (let ix = 0; ix < resolution - 1; ix++) { + const topLeft = iz * resolution + ix; + const topRight = topLeft + 1; + const bottomLeft = (iz + 1) * resolution + ix; + const bottomRight = bottomLeft + 1; + + indices.push(topLeft, bottomLeft, topRight); + indices.push(topRight, bottomLeft, bottomRight); + } + } + geo.setAttribute( + 'position', + new BufferAttribute(new Float32Array(vertices), 3) + ); + + const colors: Array = []; + for (let iz = 0; iz < resolution; iz++) { + for (let ix = 0; ix < resolution; ix++) { + const localX = ix * step - halfSize; + const localZ = iz * step - halfSize; + const globalX = worldXBase + localX; + const globalZ = worldZBase + localZ; + const colorNoise = noise2D(globalX * 0.02, globalZ * 0.02); + const t = (colorNoise + 1) / 2; + + const dryGreen = { r: 0.01, g: 0.01, b: 0.0 }; + const lushGreen = { r: 0.0, g: 0.05, b: 0.0 }; + + const r = dryGreen.r + (lushGreen.r - dryGreen.r) * t; + const g = dryGreen.g + (lushGreen.g - dryGreen.g) * t; + const b = dryGreen.b + (lushGreen.b - dryGreen.b) * t; + + colors.push(r, g, b); + } + } + geo.setAttribute('color', new BufferAttribute(new Float32Array(colors), 3)); + + geo.setIndex(indices); + geo.computeVertexNormals(); + + return geo; + }, [ + x, + y, + size, + resolution, + scale, + hillScale, + hillHeight, + detailScale, + detailHeight, + noise2D + ]); + + return ( + + + + + {shouldRenderGrass && ( + + )} + + ); +} + +interface TerrainProps { + chunks?: number; + chunkSize?: number; + resolution?: number; + scale?: number; + hillScale: number; + hillHeight: number; + detailScale: number; + detailHeight: number; + grassCount?: number; + grassSize?: number; + grassLOD?: number; +} +function Terrain({ + chunks = 5, + chunkSize = 10, + resolution = 8, + scale = 1, + hillScale = 0.2, + hillHeight = 3, + detailScale = 3, + detailHeight = 0.1, + grassCount = 6000, + grassSize = 0.6, + grassLOD = 40 +}: TerrainProps) { + const noise2D = useMemo(() => createNoise2D(), []); + const offset = -Math.floor(chunks / 2); + + const chunkPositions = useMemo(() => { + const positions: [number, number][] = []; + for (let x = 0; x < chunks; x++) + for (let y = 0; y < chunks; y++) positions.push([x + offset, y + offset]); + + return positions; + }, [chunks, offset]); + + return ( + + {chunkPositions.map(([x, y], index) => ( + + ))} + + ); +} + +const SealCube = forwardRef((props, ref) => { + const texture = useLoader(TextureLoader, '/img/niko.jpg'); + const meshRef = useRef(null); + + useImperativeHandle(ref, () => meshRef.current!, []); + + useFrame((state, delta) => { + if (meshRef.current) { + meshRef.current.rotation.x += delta * 0.5; + meshRef.current.rotation.y += delta * 0.5; + } + }); + + return ( + + + + + ); +}); +SealCube.displayName = 'SealCube'; + +function Loader() { + const { progress, active } = useProgress(); + const [visible, setVisible] = useState(true); + + useLayoutEffect(() => { + if (!active && progress === 100) { + const timeout = setTimeout(() => setVisible(false), 500); + return () => clearTimeout(timeout); + } + }, [progress, active]); + + return ( +
+ + niko!! + +
+ ); +} + +function LutEffect() { + const lutTexture = useLoader(LUTCubeLoader, '/lut/Landscape6.cube'); + return ; +} + +export default function SealHome() { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/app/shaders/grass.frag b/src/app/pages/shaders/grass.frag similarity index 100% rename from src/app/shaders/grass.frag rename to src/app/pages/shaders/grass.frag diff --git a/src/app/shaders/grass.vert b/src/app/pages/shaders/grass.vert similarity index 100% rename from src/app/shaders/grass.vert rename to src/app/pages/shaders/grass.vert