// Grass of the biome dressing: vertex-painted tufts that sway in a light wind
// and lie down under whoever walks through them, then rise again.
//
// The tufts are a MultiMesh: MODEL_MATRIX is each instance's own transform, so
// a tuft's root is its origin in the world. A vertex moves in proportion to
// its height in the model (metres): roots stay planted, tips travel most.
//
// `pushers` are fed every frame by the 3D bridge, in world units: x, z, reach,
// strength. The hero leaves a short trail of fading points behind him, so the
// grass he crossed takes a moment to rise.
shader_type spatial;
render_mode cull_disabled, diffuse_lambert, specular_disabled;

const int MAX_PUSHERS = 16;

uniform vec4 pushers[MAX_PUSHERS];
uniform int pusher_count = 0;
// Model height (metres) at which a blade bends fully.
uniform float blade_height = 0.32;
uniform float wind_strength = 0.12;
uniform float wind_speed = 1.2;
uniform vec2 wind_direction = vec2(0.85, -0.35);

void vertex() {
	vec3 root = (MODEL_MATRIX * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
	float units = length(MODEL_MATRIX[0].xyz);
	// Displacement grows with height squared: the blade curves, its root holds.
	float weight = clamp(VERTEX.y / blade_height, 0.0, 1.0);
	// A slow gust rolling across the field, and a faster flutter on top.
	float gust = 0.5 + 0.5 * sin(TIME * wind_speed - dot(root.xz, normalize(wind_direction)) * 0.004);
	float flutter = sin(TIME * 3.4 + root.x * 0.05 + root.z * 0.037);
	vec2 sway = normalize(wind_direction) * (0.35 + 0.65 * gust) * wind_strength
		+ vec2(-wind_direction.y, wind_direction.x) * flutter * wind_strength * 0.25;
	vec2 push = vec2(0.0);
	for (int i = 0; i < MAX_PUSHERS; i++) {
		if (i >= pusher_count) {
			break;
		}
		vec2 away = root.xz - pushers[i].xy;
		float distance = length(away);
		float reach = pushers[i].z;
		if (distance < reach) {
			// Fully trodden in the inner third, fading out to the reach.
			float share = (1.0 - smoothstep(reach * 0.35, reach, distance)) * pushers[i].w;
			push += (distance > 0.5 ? away / distance : vec2(0.7, 0.7)) * share;
		}
	}
	float pressed = min(length(push), 1.0);
	if (pressed > 0.0) {
		push *= pressed / max(length(push), 0.0001);
	}
	// World-space lean, turned into the instance's own frame: the instance is
	// rotated and uniformly scaled, so its transpose divided by the scale undoes
	// the rotation alone.
	vec2 lean = sway * (1.0 - pressed) + push * 1.7;
	vec3 offset = transpose(mat3(MODEL_MATRIX)) * vec3(lean.x, 0.0, lean.y) / units;
	VERTEX += offset * VERTEX.y * weight;
	// Trodden down: the blade also loses height.
	VERTEX.y *= 1.0 - 0.85 * pressed * weight;
	// Soft, sky-lit blades: normals lean up so a tuft reads as a mass, not as
	// a fan of flat cards catching the sun one by one.
	NORMAL = normalize(mix(NORMAL, vec3(0.0, 1.0, 0.0), 0.75));
}

void fragment() {
	ALBEDO = COLOR.rgb;
	ROUGHNESS = 0.92;
	BACKLIGHT = COLOR.rgb * 0.35;
}
