#ifndef MODERNPSX_FOLIAGE_WIND_GLSL
#define MODERNPSX_FOLIAGE_WIND_GLSL

float modernpsxWindHash(vec2 cell) {
    // Stable world-cell variation without a texture lookup or camera-relative
    // noise. This is evaluated only for mapped foliage vertices.
    vec3 value = fract(vec3(cell.xyx) * 0.1031);
    value += dot(value, value.yzx + 33.33);
    return fract((value.x + value.y) * value.z);
}

vec3 modernpsxFoliageWind(
    vec3 worldPosition,
    vec3 blockCenterPosition,
    float vertexDistance,
    int materialId,
    float time
) {
#ifndef FOLIAGE_WIND
    return vec3(0.0);
#else
    bool leaf = materialId == 12110;
    bool grass = materialId == 12111 || materialId == 12112;
    if (!leaf && !grass) return vec3(0.0);

    vec2 windCell = floor(blockCenterPosition.xz);
    float cellVariation = modernpsxWindHash(windCell);
    float amplitudeVariation = mix(
        0.78,
        1.22,
        modernpsxWindHash(windCell + vec2(19.7, -7.3))
    );
    float speedVariation = mix(0.84, 1.16, cellVariation);
    float localPhase = cellVariation * 6.28318530718;

    // Neighbouring plants share travelling air movement, but each block has a
    // stable phase and slightly different response. The crossing wave keeps
    // tree crowns from translating as a single slab.
    float broadWave = sin(
        dot(worldPosition.xz, vec2(0.082, 0.057)) +
        time * 0.92 * WIND_SPEED * speedVariation + localPhase
    );
    float crossingWave = sin(
        dot(worldPosition.xz, vec2(-0.041, 0.119)) -
        worldPosition.y * 0.071 +
        time * 0.57 * WIND_SPEED * mix(1.12, 0.88, cellVariation) -
        localPhase * 0.43
    );
    float sway = broadWave * 0.72 + crossingWave * 0.28;

    // A slow gust front travels across the landscape. It changes how strongly
    // a patch responds without switching motion abruptly on and off.
    float gustWave = sin(
        dot(blockCenterPosition.xz, vec2(0.021, 0.014)) -
        time * 0.24 * WIND_SPEED
    );
    float gustStrength = mix(
        0.58,
        1.16,
        smoothstep(-0.62, 0.78, gustWave)
    );

    // Cutout motion becomes sub-pixel noise in the distance, so settle it
    // before the atmospheric horizon. Grass bases remain rooted to the block.
    float distanceFade = 1.0 - smoothstep(48.0, 104.0, vertexDistance);
    float heightInBlock = fract(worldPosition.y + 0.002);
    float grassTip = smoothstep(0.08, 0.92, heightInBlock);
    float amplitude = leaf ? 0.032 : 0.085 * grassTip;
    amplitude *= WIND_STRENGTH * distanceFade *
                 amplitudeVariation * gustStrength;

    vec2 baseDirection = normalize(vec2(0.86, 0.51));
    vec2 horizontalDirection = normalize(
        baseDirection +
        vec2(-baseDirection.y, baseDirection.x) *
        ((cellVariation - 0.5) * 0.56)
    );
    vec2 horizontalOffset = horizontalDirection * sway * amplitude;
    float leafLift = leaf ? crossingWave * amplitude * 0.16 : 0.0;
    return vec3(horizontalOffset.x, leafLift, horizontalOffset.y);
#endif
}

#endif
