diff --git a/src/components/three/world/TerrainModel.tsx b/src/components/three/world/TerrainModel.tsx index 57e3d35..4525ee2 100644 --- a/src/components/three/world/TerrainModel.tsx +++ b/src/components/three/world/TerrainModel.tsx @@ -25,6 +25,23 @@ function applyTerrainMaterialSettings( scene.traverse((child) => { if (child instanceof THREE.Mesh) { child.receiveShadow = receiveShadow; + + const materials = Array.isArray(child.material) + ? child.material + : [child.material]; + + for (const material of materials) { + const materialWithAlphaMap = material as THREE.Material & { + alphaMap?: THREE.Texture | null; + }; + + material.depthTest = true; + material.depthWrite = true; + + if (material.opacity >= 1 && !materialWithAlphaMap.alphaMap) { + material.transparent = false; + } + } } }); } diff --git a/src/data/world/terrainConfig.ts b/src/data/world/terrainConfig.ts index d4bb151..14a211b 100644 --- a/src/data/world/terrainConfig.ts +++ b/src/data/world/terrainConfig.ts @@ -8,7 +8,7 @@ export const TERRAIN_SURFACE_PROJECTION = { offsetX: 0, offsetZ: 0, }; -export const TERRAIN_WATER_HEIGHT = 0; +export const TERRAIN_WATER_HEIGHT = 0.8; export const TERRAIN_TILE_SIZE = 1; export const GRASS_BASE_COLOR = "#1a3a1a"; diff --git a/src/data/world/waterConfig.ts b/src/data/world/waterConfig.ts index 12de85b..46a76f8 100644 --- a/src/data/world/waterConfig.ts +++ b/src/data/world/waterConfig.ts @@ -11,7 +11,10 @@ export interface WaterSurfaceConfig { export const WATER_SHADER_CONFIG = { enabled: true, height: TERRAIN_WATER_HEIGHT, - scale: 0.3, + depthOffset: -0.04, + borderRadius: 0.18, + borderSoftness: 0.035, + scale: 0.4, smoothness: 0.55, edgeThreshold: 0.067, edgeSoftness: 0.01, @@ -31,9 +34,9 @@ export const WATER_SHADER_CONFIG = { export const WATER_SURFACES: WaterSurfaceConfig[] = [ { - position: [62, TERRAIN_WATER_HEIGHT, -82], + position: [40, TERRAIN_WATER_HEIGHT, -102], rotation: [0, 0, 0], - size: [75, 42], + size: [75, 45], renderOrder: 0, }, ]; diff --git a/src/data/world/windConfig.ts b/src/data/world/windConfig.ts index 8ad3c7a..3646742 100644 --- a/src/data/world/windConfig.ts +++ b/src/data/world/windConfig.ts @@ -13,3 +13,12 @@ export const WIND_BOUNDS = { }; export type WindState = typeof WIND_DEFAULTS; + +export function getWindVector(wind: WindState): { x: number; z: number } { + const intensity = wind.speed * wind.strength; + + return { + x: Math.cos(wind.direction) * intensity, + z: Math.sin(wind.direction) * intensity, + }; +} diff --git a/src/hooks/debug/useEnvironmentDebug.ts b/src/hooks/debug/useEnvironmentDebug.ts new file mode 100644 index 0000000..0b4ba91 --- /dev/null +++ b/src/hooks/debug/useEnvironmentDebug.ts @@ -0,0 +1,49 @@ +import { WIND_BOUNDS } from "@/data/world/windConfig"; +import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; +import { useWorldSettingsStore } from "@/managers/stores/useWorldSettingsStore"; + +export function useEnvironmentDebug(): void { + useDebugFolder("Environment", (folder) => { + const { setWind, wind } = useWorldSettingsStore.getState(); + const controls = { ...wind }; + + folder + .add(controls, "speed", WIND_BOUNDS.speed.min, WIND_BOUNDS.speed.max) + .step(WIND_BOUNDS.speed.step) + .name("Wind speed") + .onChange((speed: number) => setWind({ speed })); + + folder + .add( + controls, + "direction", + WIND_BOUNDS.direction.min, + WIND_BOUNDS.direction.max, + ) + .step(WIND_BOUNDS.direction.step) + .name("Wind direction") + .onChange((direction: number) => setWind({ direction })); + + folder + .add( + controls, + "strength", + WIND_BOUNDS.strength.min, + WIND_BOUNDS.strength.max, + ) + .step(WIND_BOUNDS.strength.step) + .name("Wind strength") + .onChange((strength: number) => setWind({ strength })); + + folder + .add( + controls, + "noiseScale", + WIND_BOUNDS.noiseScale.min, + WIND_BOUNDS.noiseScale.max, + ) + .step(WIND_BOUNDS.noiseScale.step) + .name("Wind noise scale") + .onChange((noiseScale: number) => setWind({ noiseScale })); + }); +} diff --git a/src/utils/debug/Debug.ts b/src/utils/debug/Debug.ts index f9498a7..06047c5 100644 --- a/src/utils/debug/Debug.ts +++ b/src/utils/debug/Debug.ts @@ -18,6 +18,7 @@ interface DebugEvents { const DEBUG_FOLDER_ORDER = [ "Lighting", + "Environment", "Game", "Interaction", "Hand Tracking", diff --git a/src/world/World.tsx b/src/world/World.tsx index 7067784..67a69e1 100644 --- a/src/world/World.tsx +++ b/src/world/World.tsx @@ -5,6 +5,7 @@ import { PLAYER_SPAWN_POSITION_PHYSICS, } from "@/data/player/playerConfig"; import { useCameraMode } from "@/hooks/debug/useCameraMode"; +import { useEnvironmentDebug } from "@/hooks/debug/useEnvironmentDebug"; import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot"; @@ -36,6 +37,7 @@ interface WorldProps { } export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element { + useEnvironmentDebug(); useMapPerformanceDebug(); const cameraMode = useCameraMode(); diff --git a/src/world/water/WaterSurface.tsx b/src/world/water/WaterSurface.tsx index c166dad..c78cec5 100644 --- a/src/world/water/WaterSurface.tsx +++ b/src/world/water/WaterSurface.tsx @@ -1,8 +1,10 @@ import { useMemo, useRef } from "react"; import * as THREE from "three"; import { useFrame } from "@react-three/fiber"; +import { getWindVector } from "@/data/world/windConfig"; import { WATER_SHADER_CONFIG } from "@/data/world/waterConfig"; import type { WaterSurfaceConfig } from "@/data/world/waterConfig"; +import { useWind } from "@/hooks/world/useWind"; import { WATER_FRAGMENT_SHADER, WATER_VERTEX_SHADER, @@ -15,6 +17,7 @@ export function WaterSurface({ size, }: WaterSurfaceConfig): React.JSX.Element { const materialRef = useRef(null); + const wind = useWind(); const uniforms = useMemo( () => ({ uTime: { value: 0 }, @@ -28,6 +31,8 @@ export function WaterSurface({ uNoiseScale: { value: WATER_SHADER_CONFIG.noiseScale }, uNoiseFlowSpeed: { value: WATER_SHADER_CONFIG.noiseFlowSpeed }, uDistortAmount: { value: WATER_SHADER_CONFIG.distortAmount }, + uBorderRadius: { value: WATER_SHADER_CONFIG.borderRadius }, + uBorderSoftness: { value: WATER_SHADER_CONFIG.borderSoftness }, uDeepColor: { value: new THREE.Color(WATER_SHADER_CONFIG.deepColor) }, uMidColor: { value: new THREE.Color(WATER_SHADER_CONFIG.midColor) }, uMidPos: { value: WATER_SHADER_CONFIG.midPos }, @@ -41,15 +46,28 @@ export function WaterSurface({ ); useFrame(({ clock }) => { - const uniform = materialRef.current?.uniforms.uTime; - if (uniform) { - uniform.value = clock.getElapsedTime(); + const material = materialRef.current; + if (!material) return; + + const windVector = getWindVector(wind); + + const { uFlowX, uFlowZ, uNoiseScale, uTime } = material.uniforms; + + if (uTime) uTime.value = clock.getElapsedTime(); + if (uFlowX) uFlowX.value = WATER_SHADER_CONFIG.flowX + windVector.x; + if (uFlowZ) uFlowZ.value = WATER_SHADER_CONFIG.flowZ + windVector.z; + if (uNoiseScale) { + uNoiseScale.value = WATER_SHADER_CONFIG.noiseScale * wind.noiseScale; } }); return ( diff --git a/src/world/water/waterShaders.ts b/src/world/water/waterShaders.ts index 2035c9d..890fdd7 100644 --- a/src/world/water/waterShaders.ts +++ b/src/world/water/waterShaders.ts @@ -1,7 +1,9 @@ export const WATER_VERTEX_SHADER = /* glsl */ ` + varying vec2 vUv; varying vec2 vWorldPos; void main() { + vUv = uv; vec4 worldPosition = modelMatrix * vec4(position, 1.0); vWorldPos = worldPosition.xz; gl_Position = projectionMatrix * viewMatrix * worldPosition; @@ -20,6 +22,8 @@ export const WATER_FRAGMENT_SHADER = /* glsl */ ` uniform float uNoiseScale; uniform float uNoiseFlowSpeed; uniform float uDistortAmount; + uniform float uBorderRadius; + uniform float uBorderSoftness; uniform vec3 uDeepColor; uniform vec3 uMidColor; uniform float uMidPos; @@ -27,8 +31,18 @@ export const WATER_FRAGMENT_SHADER = /* glsl */ ` uniform float uOpacity; uniform float uDeepOpacity; + varying vec2 vUv; varying vec2 vWorldPos; + float roundedBoxMask(vec2 uv, float radius, float softness) { + vec2 centeredUv = uv * 2.0 - 1.0; + vec2 boxSize = vec2(1.0 - radius); + vec2 distanceToEdge = abs(centeredUv) - boxSize; + float outsideDistance = length(max(distanceToEdge, 0.0)) - radius; + + return 1.0 - smoothstep(-softness, softness, outsideDistance); + } + vec2 hash2(vec2 p) { p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3))); return fract(sin(p) * 43758.5453); @@ -126,6 +140,11 @@ export const WATER_FRAGMENT_SHADER = /* glsl */ ` inSecondSegment ); float alpha = mix(uDeepOpacity, 1.0, ramp) * uOpacity; + alpha *= roundedBoxMask(vUv, uBorderRadius, uBorderSoftness); + + if (alpha < 0.01) { + discard; + } gl_FragColor = vec4(color, alpha); }