#version 130
#include "commonSettings.glsl"

#define Uncube 0.2 //Uncube distortion level [0.0 0.2 0.4 0.6 0.8 1.0]
#define Camera 0.5 //Camera height Camera [0.0 0.5 1.0]
#define PAPER_TEXTURE_SPEED 2.0 // Paper texture update rate [0.1 0.5 1.0 2.0 5.0 10.0]
#define UNCUBE_DISTANCE_SCALING 1 // Enable distance-based uncube scaling [0 1]
#define UNCUBE_MIN_DISTANCE 5.0 // Distance where uncube starts scaling [5.0 10.0 20.0]
#define UNCUBE_MAX_DISTANCE 50.0 // Distance where uncube reaches full strength [50.0 100.0 150.0]
#define UNCUBE_MIN_STRENGTH 0.02 // Minimum uncube strength up close [0.0 0.1 0.2]
#define UNCUBE_TIME_SYNC 1	 // Enable time-synced uncube updates [0 1]

attribute vec2 mc_Entity;
attribute vec3 mc_midTexCoord;
attribute vec4 at_midBlock;

uniform mat4 gbufferModelView;
uniform mat4 gbufferModelViewInverse;
uniform mat4 shadowModelView;
uniform mat4 shadowProjection;
uniform vec3 cameraPosition;
uniform vec3 shadowLightPosition;
uniform float frameTimeCounter;

varying vec4 color;
varying vec3 world;
varying vec3 vert;
varying vec2 coord0;
varying vec2 coord1;
varying float id;
varying vec3 shadowPos;
varying vec3 vPos;
varying vec4 N;
varying float emissiveFlag;

// Simple hash function for time-based randomization
float hash(vec2 p) {
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

vec3 hash3(vec3 p)
{
  return fract(cos(p*mat3(-31.14,15.92,65.35,-89.79,-32.38,46.26,43.38,32.79,-02.88))*41.97);
}

vec3 value3(vec3 p)
{
    vec3 f = floor(p);
    vec3 s = p-f; s*= s*s*(3.-s-s);
    const vec2 o = vec2(0,1);
    return mix(mix(mix(hash3(f+o.xxx),hash3(f+o.yxx),s.x),
                   mix(hash3(f+o.xyx),hash3(f+o.yyx),s.x),s.y),
               mix(mix(hash3(f+o.xxy),hash3(f+o.yxy),s.x),
                   mix(hash3(f+o.xyy),hash3(f+o.yyy),s.x),s.y),s.z);
}

//Time-synced block warping function with distance scaling
vec3 off(vec3 p, float distanceFromCamera)
{
    // Time offsets for noise sampling
    vec3 timeOffset1, timeOffset2, timeOffset3;
    
    #if UNCUBE_TIME_SYNC == 1
        // Create discrete time steps matching paper texture system
        float timeStep = floor(frameTimeCounter * PAPER_TEXTURE_SPEED);
        
        // Generate different random seeds for each time step
        float seed1 = hash(vec2(timeStep, 1.0)) * 1000.0;
        float seed2 = hash(vec2(timeStep, 2.0)) * 1000.0;
        float seed3 = hash(vec2(timeStep, 3.0)) * 1000.0;
        
        // Apply the time-offset to the noise sampling
        timeOffset1 = vec3(seed1, seed1 * 1.3, seed1 * 1.7);
        timeOffset2 = vec3(seed2, seed2 * 1.1, seed2 * 1.9);
        timeOffset3 = vec3(seed3, seed3 * 0.9, seed3 * 2.1);
    #else
        // Static uncube distortion (original behavior)
        timeOffset1 = vec3(0.0);
        timeOffset2 = vec3(0.0);
        timeOffset3 = vec3(0.0);
    #endif
    
    // Sample noise with time-based offsets (same structure as original)
    vec3 noise1 = value3(p + timeOffset1) * 0.4;
    vec3 noise2 = value3(p/2. + timeOffset2) * 0.6;
    vec3 noise3 = value3(p/8. + timeOffset3);
    
    // Calculate distance-based uncube strength
    float uncubeStrength = Uncube;
    
    #if UNCUBE_DISTANCE_SCALING == 1
        // Scale uncube strength based on distance
        float distanceFactor = smoothstep(UNCUBE_MIN_DISTANCE, UNCUBE_MAX_DISTANCE, distanceFromCamera);
        uncubeStrength = mix(UNCUBE_MIN_STRENGTH, Uncube, distanceFactor);
    #endif
    
    return (noise1 + noise2 + noise3 - 1.0) * uncubeStrength;
}

void main()
{
	emissiveFlag = at_midBlock.w;
    // Calculate vPos early for compatibility
    vPos = (gl_ModelViewMatrix * gl_Vertex).xyz;
    
    vec3 pos = vPos;
    pos = mat3(gbufferModelViewInverse) * pos + gbufferModelViewInverse[3].xyz;
    vert = (gl_ModelViewMatrix * gl_Vertex).xyz-(gl_NormalMatrix * gl_Normal)/32.;
    vert = mat3(gbufferModelViewInverse) * vert + gbufferModelViewInverse[3].xyz + cameraPosition;
    
    // Calculate distance from camera for distance-based effects
    float distanceFromCamera = length(pos);
    
    float c = fract(pos.y+cameraPosition.y);
    c *= min(10.-c/.1,1.);
    pos += off(pos+cameraPosition, distanceFromCamera);
    vec3 h = pos+cameraPosition;
    
    // Camera offset calculation with optional time sync
    #if UNCUBE_TIME_SYNC == 1
        float timeStep = floor(frameTimeCounter * PAPER_TEXTURE_SPEED);
        float cameraSeed = hash(vec2(timeStep, 4.0)) * 1000.0;
        vec3 cameraTimeOffset = vec3(cameraSeed, cameraSeed * 1.4, cameraSeed * 0.8);
        pos.y -= off(cameraPosition-vec3(0,1,0) + cameraTimeOffset, 0.0).y*Camera;
    #else
        pos.y -= off(cameraPosition-vec3(0,1,0), 0.0).y*Camera;
    #endif
    
    float water = float(mc_Entity.x==1.);
    pos.y += ((cos(h.x*2.+h.y*1.+h.z*2.+frameTimeCounter*4.)*.1-.1)*c)*water*Uncube;
    
    // Calculate shadow coordinates
    vec3 sWorld = vec3(shadowModelView * vec4(pos, 1.0));
	vec3 sClip = vec3(shadowProjection * vec4(sWorld, 1.0));

	// Apply distortion BEFORE other transformations
	sClip.xyz = distortShadow(sClip.xyz);
    shadowPos = sClip * 0.5 + 0.5;
    
    // Calculate normal for directional lighting
    N.xyz = normalize(gl_NormalMatrix * gl_Normal);
    N.a = (mc_Entity.x == 1) ? 1.0 : 0.0; // Water flag
        
    gl_Position = gl_ProjectionMatrix * gbufferModelView * vec4(pos,1);
    gl_FogFragCoord = length(pos);
    
    color = gl_Color;
    world = pos; // Keep using distorted position
    coord0 = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;
    coord1 = (gl_TextureMatrix[1] * gl_MultiTexCoord1).xy;
    id = mc_Entity.x;
}