#version 140

#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif

in highp vec4 var_world_pos;
in mediump vec3 var_world_normal;

out vec4 out_fragColor;

uniform mediump sampler2D tex0; // rock
uniform mediump sampler2D tex1; // grass

uniform fs_uniforms {

  // x: side texture scale (X axis projection)
  // y: side texture scale (Y component of all side UVs)
  // z: side texture scale (Z axis projection)
  // w: top texture scale (tex1 top-down projection)
  mediump vec4 texture_scale;

  // x: blend_start   — normal.y value where side→top blend begins
  // y: blend_end     — normal.y value where surface is fully top
  // z: ground_start  — world Y below which ground_color is fully applied
  // w: ground_end    — world Y above which ground_color has no influence
  mediump vec4 texture_params;

  // x: mid_center — normal.y center of the mid-band overlay
  // y: mid_width  — width of the mid-band falloff
  // z: mid_enable — 0.0 = off, 1.0 = on
  mediump vec4 mid_params;

  mediump vec4 ground_color;
  mediump vec4 mid_color;
};

vec4 biplanar_sample(vec3 wp, vec3 n) {
  vec3 an = abs(n);

  // pow(an, N) via repeated multiply — avoids exp2/log2 transcendentals.
  // Each extra multiply doubles the exponent:
  //   vec3 a2  = an * an;           // ^2  softer blend
  //   vec3 a4  = a2 * a2;           // ^4  standard (used below)
  //   vec3 a8  = a4 * a4;           // ^8  sharp
  //   vec3 a16 = a8 * a8;           // ^16 nearly hard edge
  // To change sharpness, swap the line that sets bx/bz to the desired level.
  vec3 a2 = an * an;
  float bx = a2.x * a2.x; // ^4
  float bz = a2.z * a2.z; // ^4
  float total = max(bx + bz, 0.0001);

  float sx = texture_scale.x;
  float sy = texture_scale.y;
  float sz = texture_scale.z;

  vec2 uv_x = vec2(wp.z * sz, wp.y * sy) + 0.5;
  vec2 uv_z = vec2(wp.x * sx, wp.y * sy) + 0.5;

  // Branchless sign-flip to match projection orientation across faces.
  uv_x.x *= sign(n.x);
  uv_z.x *= sign(n.z);

  vec4 tx = texture(tex0, uv_x);
  vec4 tz = texture(tex0, uv_z);

  return (tx * bx + tz * bz) / total;
}

void main() {
  vec3 n = normalize(var_world_normal);
  float s = texture_scale.w;
  float blend_start = texture_params.x;
  float blend_end = texture_params.y;

  float up = max(n.y, 0.0);
  float top_factor = smoothstep(blend_start, blend_end, up);

  vec2 top_uv = var_world_pos.xz * s + 0.5;
  vec4 top_color = texture(tex1, top_uv);

  vec4 side_color = biplanar_sample(var_world_pos.xyz, n);

  float ground_start = texture_params.z;
  float ground_end = texture_params.w;

  float ground_factor = smoothstep(ground_start, ground_end, var_world_pos.y);

  side_color = mix(ground_color, side_color, ground_factor);

  float mid_center = mid_params.x;
  float mid_width = mid_params.y;

  float mid_factor = 1.0 - smoothstep(0.0, mid_width, abs(up - mid_center));

  vec4 color = mix(side_color, top_color, top_factor);
  // Branchless mid-band overlay: mid_params.z is 0.0 (off) or 1.0 (on).
  color = mix(color, mid_color, mid_factor * mid_params.z);

  out_fragColor = color;
}