export const grassVertexShader = /* glsl */ `
  attribute vec3 aYaw;
  attribute vec3 aBladeOrigin;
  attribute vec3 aBladeColor;

  varying vec3 vColor;

  uniform float uTime;
  uniform vec3 uPlayerPosition;
  uniform sampler2D uHeightMap;
  uniform sampler2D uDiffuseMap;
  uniform sampler2D uNoiseTexture;
  uniform vec3 uBoundingBoxMin;
  uniform vec3 uBoundingBoxMax;
  uniform float uPatchSize;
  uniform float uBladeWidth;
  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 uZoneFrequency;
  uniform float uSparseZoneThreshold;
  uniform float uTallZoneThreshold;
  uniform float uZoneSoftness;
  uniform float uSparseZoneHeight;
  uniform float uLowZoneHeight;
  uniform float uTallZoneHeight;
  uniform float uSparseZoneDensity;
  uniform float uLowZoneDensity;
  uniform float uTallZoneDensity;
  uniform float uMaxBendAngle;
  uniform float uMaxBladeHeight;
  uniform float uRandomHeightAmount;
  uniform float uSurfaceOffset;

  float random(vec2 st) {
    return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
  }

  mat3 rotate3d(in vec3 axis, const in float angle) {
    axis = normalize(axis);
    float s = sin(angle);
    float c = cos(angle);
    float oc = 1.0 - c;
    return mat3(
      oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s,
      oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s,
      oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c
    );
  }

  float mapValue(float value, float inMin, float inMax, float outMin, float outMax) {
    return mix(outMin, outMax, (value - inMin) / (inMax - inMin));
  }

  void main() {
    vec3 transformed = position;
    vec3 origin = aBladeOrigin;
    float halfPatchSize = uPatchSize * 0.5;

    origin.x = mod(origin.x - uPlayerPosition.x + halfPatchSize, uPatchSize) - halfPatchSize;
    origin.z = mod(origin.z - uPlayerPosition.z + halfPatchSize, uPatchSize) - halfPatchSize;

    vec3 worldPos = vec3(uPlayerPosition.x + origin.x, 0.0, uPlayerPosition.z + origin.z);
    transformed.x = worldPos.x;
    transformed.z = worldPos.z;

    vec2 terrainUv = vec2(
      mapValue(worldPos.x, uBoundingBoxMin.x, uBoundingBoxMax.x, 0.0, 1.0),
      mapValue(worldPos.z, uBoundingBoxMin.z, uBoundingBoxMax.z, 0.0, 1.0)
    );
    terrainUv = clamp(terrainUv, 0.0, 1.0);

    float terrainHeightRatio = texture2D(uHeightMap, terrainUv).r;
    float terrainHeight = mix(uBoundingBoxMin.y, uBoundingBoxMax.y, terrainHeightRatio);
    transformed.y = terrainHeight + uSurfaceOffset;

    vec3 heightNoise = texture2D(uNoiseTexture, terrainUv.yx * vec2(uHeightNoiseFrequency)).rgb;
    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 zoneNoise = texture2D(uNoiseTexture, worldPos.xz * uZoneFrequency).r;
    float sparseZone = 1.0 - smoothstep(uSparseZoneThreshold, uSparseZoneThreshold + uZoneSoftness, zoneNoise);
    float tallZone = smoothstep(uTallZoneThreshold, uTallZoneThreshold + uZoneSoftness, zoneNoise);
    float midZone = clamp(1.0 - sparseZone - tallZone, 0.0, 1.0);
    float zoneHeight =
      sparseZone * uSparseZoneHeight +
      midZone * uLowZoneHeight +
      tallZone * uTallZoneHeight;
    float zoneDensity =
      sparseZone * uSparseZoneDensity +
      midZone * uLowZoneDensity +
      tallZone * uTallZoneDensity;
    float bladeVisibility = step(random(worldPos.xz), zoneDensity);
    float heightModifier = uMaxBladeHeight * mix(0.35, 1.0, heightNoiseAverage) * uHeightNoiseAmplitude;
    heightModifier += random(terrainUv) * (uRandomHeightAmount * 0.1);
    heightModifier = clamp(heightModifier, 0.0, uMaxBladeHeight);
    heightModifier *= zoneHeight * bladeVisibility;

    float edgeDistanceX = abs(origin.x) / halfPatchSize;
    float edgeDistanceZ = abs(origin.z) / halfPatchSize;
    float edgeFactor = 1.0 - max(edgeDistanceX, edgeDistanceZ);
    edgeFactor = pow(clamp(edgeFactor, 0.0, 1.0), uFalloffSharpness);

    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 * 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.02, uMaxBladeHeight * 0.85, heightModifier) * uBladeWidth * bladeVisibility;
    transformed += aYaw * (width / 2.0) * sideFactor;

    vec3 textureColor = texture2D(uDiffuseMap, terrainUv * 10.0).rgb;
    vec3 colorNoise = texture2D(uNoiseTexture, terrainUv.yx * vec2(uHeightNoiseFrequency) + (uTime * 0.1)).rgb;
    vColor = mix(aBladeColor * 0.55, aBladeColor, tipFactor) * textureColor * mix(vec3(0.75), vec3(1.15), colorNoise);

    float distanceFromCenter = length(origin.xz) / halfPatchSize;
    float innerCircleFactor = clamp(smoothstep(0.0, 0.5, distanceFromCenter), 0.0, 1.0);
    heightModifier *= mix(0.25, 1.0, innerCircleFactor);

    float noiseScale = uWindNoiseScale * 0.1;
    vec2 noiseUV = vec2(origin.x * noiseScale, origin.z * noiseScale);
    mat2 rotation = mat2(
      cos(uWindDirection), -sin(uWindDirection),
      sin(uWindDirection), cos(uWindDirection)
    );
    vec2 rotatedNoiseUV = rotation * noiseUV + uTime * vec2(uWindSpeed);
    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 * uWindStrength;
    mat3 rotationMatrix = rotate3d(axis, angle);

    vec3 basePosition = vec3(transformed.x, transformed.y - heightModifier, transformed.z);
    vec3 relativePosition = transformed - basePosition;
    relativePosition = rotationMatrix * relativePosition;
    transformed = basePosition + relativePosition;
    transformed.y += heightModifier * tipFactor;

    gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
  }
`;

export const grassFragmentShader = /* glsl */ `
  varying vec3 vColor;

  void main() {
    gl_FragColor = vec4(vColor, 1.0);
  }
`;
