precision highp float;
uniform vec2 resolution;
uniform float time;
// --- Hash & Noise Functions
float hash(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x), f.y);
float fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
for(int i = 0; i < 5; i++) {
v += a * noise(p);
p = m * p;
a *= 0.5;
return v;
// --- SDFs
float sdOctagon(vec2 p, float r) {
p = abs(p);
float d = max(p.x, p.y) - r;
d = max(d, dot(p, vec2(0.70710678)) - r * 1.12);
return d;
float sdCone(vec3 p, vec2 dim) {
vec2 q = vec2(length(p.xz), p.y);
return max(dot(q, vec2(dim.y, -dim.x)), -q.y - dim.x);
// --- Ocean (Gerstner-inspired waves)
float waveHeight(vec2 p) {
float t = time * 1.5;
float h = 0.0;
h += sin(p.x * 0.15 + t) * 3.0;
h += sin(p.y * 0.25 - t * 1.3) * 2.0;
h += sin((p.x + p.y) * 0.3 + t * 1.7) * 1.5;
h += fbm(p * 0.4 + vec2(t * 0.2, -t * 0.3)) * 5.0;
return h;
// --- Scene Map
vec2 map(vec3 p) {
float oceanBase = -1.5;
float ocean = p.y - (oceanBase + waveHeight(p.xz));
float city = 1000.0;
float cellSize = 18.0;
vec2 id = floor(p.xz / cellSize);
vec2 q = mod(p.xz, cellSize) - cellSize * 0.5;
float rnd = hash(id);
float rnd2 = hash(id + 1.0);
if (rnd > 0.15) {
float w = 2.5 + rnd * 1.5;
float h = 35.0 + rnd2 * 70.0;
float baseY = -25.0;
// Tapered Main Body
float taperY = clamp((p.y - baseY) / max(0.1, h - baseY), 0.0, 1.0);
float taper = mix(1.3, 0.5, taperY);
float dOct = sdOctagon(q / taper, w) * taper;
// Gothic Vertical Ribs
float angle = atan(q.x, q.y);
float ribs = cos(angle * 8.0) * 0.15;
dOct += ribs * smoothstep(h, h - 15.0, p.y) * smoothstep(baseY, baseY + 10.0, p.y);
float body = max(dOct, max(p.y - h, baseY - p.y));
city = min(city, body);
// Central Spire
if (p.y > h - 2.0) {
float spireH = 20.0 + rnd2 * 15.0;
float spire = sdCone(vec3(q.x, p.y - h, q.y), vec2(w * 0.8, spireH));
city = min(city, spire);
// Corner Spires (Buttresses)
vec2 cornerOffset = vec2(w + 1.5);
for(int i = 0; i < 4; i++) {
vec2 c = cornerOffset;
if(i == 1) c.x = -c.x;
if(i == 2) c.y = -c.y;
if(i == 3) c = -c;
vec2 cq = q - c;
float c_h = h * 0.75 + rnd2 * 10.0;
float c_taperY = clamp((p.y - baseY) / max(0.1, c_h - baseY), 0.0, 1.0);