#version 330 core
precision highp float;

in vec3 position; // unused: the grid is only a (u,v) parameter domain
in vec2 uv;

out vec2 ex_Uv;
out vec3 ex_Normal;
out vec3 ex_FragPos;
out vec2 ex_Ring;

uniform mat4 view;
uniform mat4 projection;
uniform float uHeadArc;       // arc position of the head tip along the path (world units)
uniform float uBodyLength;
uniform float uRadius;
uniform float uDrift;         // forward drift: x = drift*a + ampX*sin(freqX*a + phaseX)
uniform float uAmpX;
uniform float uFreqX;
uniform float uPhaseX;
uniform float uAmpY;          // y = baseY + driftY*a + ampY*sin(freqY*a + phaseY)
uniform float uDriftY;
uniform float uFreqY;
uniform float uPhaseY;
uniform float uBaseY;
uniform float uBaseZ;
uniform float uDepthWobble;
uniform float uOutlineExpand; // 0 = body pass, >0 = inverted-hull outline pass

// The snake slides along a fixed world-space curve, so every body segment follows the
// head's track — that is what makes the motion read as slithering, not swaying.
// ampX = 0: straight glide. drift < 1 with ampX > 0: long Lissajous-style meander.
vec3 pathPos(float a)
{
	return vec3(uDrift * a + uAmpX * sin(uFreqX * a + uPhaseX),
	            uBaseY + uDriftY * a + uAmpY * sin(uFreqY * a + uPhaseY),
	            uBaseZ + uDepthWobble * sin(0.63 * uFreqY * a + 1.7));
}

vec3 pathTangent(float a)
{
	return normalize(vec3(uDrift + uAmpX * uFreqX * cos(uFreqX * a + uPhaseX),
	                      uDriftY + uAmpY * uFreqY * cos(uFreqY * a + uPhaseY),
	                      uDepthWobble * 0.63 * uFreqY * cos(0.63 * uFreqY * a + 1.7)));
}

void main()
{
	float u = uv.x;                               // 0 = tail tip, 1 = head tip
	float a = uHeadArc - (1.0 - u) * uBodyLength; // arc parameter of this ring

	vec3 P = pathPos(a);
	vec3 T = pathTangent(a);
	// Screen-plane frame: Up = Z×T. Our paths never head straight into the camera
	// (|T.xy| stays large), so this is continuous through the turns — no degenerate
	// pinch or frame flip at path apexes, unlike a world-up reference.
	vec3 U = normalize(vec3(-T.y, T.x, 0.0)); // snake-local up, rides the outer curve
	vec3 S = normalize(cross(T, U));          // camera-side vector

	float theta = uv.y * 6.2831853;
	vec2 ring = vec2(cos(theta), sin(theta)); // ring.y: +1 = back, -1 = belly

	// Radius profile: tapering tail, slightly bulging head, rounded nose
	float taper = pow(smoothstep(0.0, 0.35, u), 0.6);
	float nose = sqrt(smoothstep(1.0, 0.94, u));
	float headBump = smoothstep(0.82, 0.90, u) * (1.0 - smoothstep(0.90, 0.985, u));
	float r = uRadius * taper * nose * (1.0 + 0.55 * headBump);

	vec3 N = normalize(S * ring.x + U * ring.y);
	float expand = uOutlineExpand * clamp(r / uRadius + 0.35, 0.0, 1.0);
	vec3 world = P + N * (r + expand);

	gl_Position = projection * view * vec4(world, 1.0);
	ex_Uv = uv;
	ex_Normal = N;
	ex_FragPos = world;
	ex_Ring = ring;
}
