diff --git a/src/world/grass/GrassPatch.tsx b/src/world/grass/GrassPatch.tsx index 73cc78b..dd8657f 100644 --- a/src/world/grass/GrassPatch.tsx +++ b/src/world/grass/GrassPatch.tsx @@ -144,10 +144,14 @@ function createGrassMaterial( uWindDirection: { value: 0 }, uWindSpeed: { value: 0 }, uWindNoiseScale: { value: GRASS_CONFIG.windNoiseScale }, + uWindStrength: { value: GRASS_CONFIG.windStrength }, uBaldPatchModifier: { value: GRASS_CONFIG.baldPatchModifier }, uFalloffSharpness: { value: GRASS_CONFIG.falloffSharpness }, uHeightNoiseFrequency: { value: GRASS_CONFIG.heightNoiseFrequency }, uHeightNoiseAmplitude: { value: GRASS_CONFIG.heightNoiseAmplitude }, + uClumpFrequency: { value: GRASS_CONFIG.clumpFrequency }, + uClumpThreshold: { value: GRASS_CONFIG.clumpThreshold }, + uClumpSoftness: { value: GRASS_CONFIG.clumpSoftness }, uMaxBendAngle: { value: GRASS_CONFIG.maxBendAngle }, uMaxBladeHeight: { value: GRASS_CONFIG.maxBladeHeight }, uRandomHeightAmount: { value: GRASS_CONFIG.randomHeightAmount }, diff --git a/src/world/grass/grassConfig.ts b/src/world/grass/grassConfig.ts index 7434d2d..93e039e 100644 --- a/src/world/grass/grassConfig.ts +++ b/src/world/grass/grassConfig.ts @@ -1,18 +1,22 @@ export const GRASS_CONFIG = { enabled: true, patchSize: 30, - bladeCount: 18000, + bladeCount: 32000, bladeWidth: 0.08, maxBladeHeight: 0.36, randomHeightAmount: 0.25, surfaceOffset: 0.025, heightTextureSize: 128, windNoiseScale: 0.9, - baldPatchModifier: 2.5, + windStrength: 0.35, + baldPatchModifier: 1.1, falloffSharpness: 0.35, - heightNoiseFrequency: 12, - heightNoiseAmplitude: 3, - maxBendAngle: 22, + heightNoiseFrequency: 9, + heightNoiseAmplitude: 1, + clumpFrequency: 2.6, + clumpThreshold: 0.18, + clumpSoftness: 0.45, + maxBendAngle: 14, } as const; export const GRASS_COLORS = ["#84C66B", "#67B058", "#A3CA5B"] as const; diff --git a/src/world/grass/grassShaders.ts b/src/world/grass/grassShaders.ts index ebd1964..2488616 100644 --- a/src/world/grass/grassShaders.ts +++ b/src/world/grass/grassShaders.ts @@ -17,10 +17,14 @@ export const grassVertexShader = /* glsl */ ` uniform float uWindDirection; uniform float uWindSpeed; uniform float uWindNoiseScale; + uniform float uWindStrength; uniform float uBaldPatchModifier; uniform float uFalloffSharpness; uniform float uHeightNoiseFrequency; uniform float uHeightNoiseAmplitude; + uniform float uClumpFrequency; + uniform float uClumpThreshold; + uniform float uClumpSoftness; uniform float uMaxBendAngle; uniform float uMaxBladeHeight; uniform float uRandomHeightAmount; @@ -69,8 +73,13 @@ export const grassVertexShader = /* glsl */ ` transformed.y = terrainHeight + uSurfaceOffset; vec3 heightNoise = texture2D(uNoiseTexture, terrainUv.yx * vec2(uHeightNoiseFrequency)).rgb; - float heightModifier = ((heightNoise.r + heightNoise.g + heightNoise.b) * uMaxBladeHeight) * uHeightNoiseAmplitude; + float heightNoiseAverage = (heightNoise.r + heightNoise.g + heightNoise.b) / 3.0; + vec2 clumpUv = (worldPos.xz / uPatchSize) * uClumpFrequency; + float clumpNoise = texture2D(uNoiseTexture, clumpUv).r; + float clumpMask = smoothstep(uClumpThreshold, uClumpThreshold + uClumpSoftness, clumpNoise); + float heightModifier = uMaxBladeHeight * mix(0.35, 1.0, heightNoiseAverage) * uHeightNoiseAmplitude; heightModifier += random(terrainUv) * (uRandomHeightAmount * 0.1); + heightModifier = clamp(heightModifier, 0.0, uMaxBladeHeight); float edgeDistanceX = abs(origin.x) / halfPatchSize; float edgeDistanceZ = abs(origin.z) / halfPatchSize; @@ -79,17 +88,18 @@ export const grassVertexShader = /* glsl */ ` float baldPatchOffset = heightNoise.r * (uBaldPatchModifier * (1.0 - edgeFactor)); heightModifier -= baldPatchOffset; + heightModifier = max(heightModifier, 0.0); float edgeFade = smoothstep(uBoundingBoxMin.x, uBoundingBoxMin.x + 2.0, worldPos.x) * smoothstep(uBoundingBoxMax.x, uBoundingBoxMax.x - 2.0, worldPos.x) * smoothstep(uBoundingBoxMin.z, uBoundingBoxMin.z + 2.0, worldPos.z) * smoothstep(uBoundingBoxMax.z, uBoundingBoxMax.z - 2.0, worldPos.z); - heightModifier *= edgeFade; + heightModifier *= edgeFade * mix(0.45, 1.0, clumpMask); float sideFactor = (color.r == 0.1) ? 1.0 : (color.b == 0.1) ? -1.0 : 0.0; float tipFactor = color.g; - float width = smoothstep(0.5, 1.0, heightModifier * 2.0) * uBladeWidth; + float width = smoothstep(0.02, uMaxBladeHeight * 0.85, heightModifier) * uBladeWidth; transformed += aYaw * (width / 2.0) * sideFactor; vec3 textureColor = texture2D(uDiffuseMap, terrainUv * 10.0).rgb; @@ -110,7 +120,7 @@ export const grassVertexShader = /* glsl */ ` vec3 windNoise = texture2D(uNoiseTexture, rotatedNoiseUV).rgb; vec3 axis = vec3(windNoise.g, 0.0, windNoise.b); - float angle = radians(mapValue(windNoise.g + windNoise.b, 0.0, 2.0, -uMaxBendAngle, uMaxBendAngle)) * tipFactor; + float angle = radians(mapValue(windNoise.g + windNoise.b, 0.0, 2.0, -uMaxBendAngle, uMaxBendAngle)) * tipFactor * uWindStrength; mat3 rotationMatrix = rotate3d(axis, angle); vec3 basePosition = vec3(transformed.x, transformed.y - heightModifier, transformed.z); diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 6aa9d98..c0fd224 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -26,6 +26,7 @@ import { PLAYER_XZ_DAMPING_FACTOR, } from "@/data/player/playerConfig"; import { useRepairMovementLocked } from "@/hooks/gameplay/useRepairMovementLocked"; +import { useTerrainHeightSampler } from "@/hooks/three/useTerrainHeight"; import { InteractionManager } from "@/managers/InteractionManager"; import { useGameStore } from "@/managers/stores/useGameStore"; import { useSettingsStore } from "@/managers/stores/useSettingsStore"; @@ -48,7 +49,8 @@ const DEFAULT_KEYS: Keys = { }; const PLAYER_COLLISION_ITERATIONS = 3; -const PLAYER_FLOOR_NORMAL_MIN = 0.5; +const PLAYER_FLOOR_NORMAL_MIN = 0.15; +const PLAYER_GROUND_SNAP_DISTANCE = 0.22; interface PlayerControllerProps { octree: Octree | null; @@ -116,12 +118,17 @@ function setMovementKey(keys: Keys, key: string, pressed: boolean): boolean { } } +function getCapsuleFootY(capsule: Capsule): number { + return capsule.start.y - capsule.radius; +} + export function PlayerController({ octree, spawnPosition, }: PlayerControllerProps): null { const camera = useThree((state) => state.camera); const movementLocked = useRepairMovementLocked(); + const terrainHeight = useTerrainHeightSampler(); const movementLockedRef = useRef(movementLocked); const keys = useRef({ ...DEFAULT_KEYS }); const velocity = useRef(new THREE.Vector3()); @@ -319,6 +326,10 @@ export function PlayerController({ if (isFloorCollision) { velocity.current.y = Math.max(0, velocity.current.y); + capsule.current.translate( + _collisionCorrection.set(0, result.depth / result.normal.y, 0), + ); + continue; } capsule.current.translate( @@ -327,6 +338,22 @@ export function PlayerController({ } } + const groundHeight = terrainHeight.getHeight( + capsule.current.end.x, + capsule.current.end.z, + ); + if (groundHeight !== null && velocity.current.y <= 0) { + const groundOffset = getCapsuleFootY(capsule.current) - groundHeight; + + if (groundOffset <= PLAYER_GROUND_SNAP_DISTANCE) { + capsule.current.translate( + _collisionCorrection.set(0, -groundOffset, 0), + ); + velocity.current.y = 0; + onFloor.current = true; + } + } + camera.position.copy(capsule.current.end); });