#version 460
layout(push_constant) uniform Scene {
    // xy = input pixels; zw = raster jitter in pixels (Y down).
    vec4 sizeJitter;
    // xy = current/previous scene seconds; zw = horizontal shifts in pixels.
    vec4 times;
    // x = appearance stress flag; y = camera orbit flag; zw unused.
    vec4 settings;
} pc;
layout(location=0) out vec3 world;
layout(location=1) out vec3 normal;
layout(location=2) out vec4 nowClip;
layout(location=3) out vec4 oldClip;
layout(location=4) flat out int objectId;
layout(location=5) flat out vec3 cameraEye;
const int indices[36]=int[](0,2,1,1,2,3,4,5,6,5,7,6,0,1,4,1,5,4,2,6,3,3,6,7,0,4,2,2,4,6,1,3,5,3,7,5);
vec3 corner(int i) {
    return vec3((i&1)==0?-1:1,(i&2)==0?-1:1,(i&4)==0?-1:1);
}
// Object IDs: floor 0, boxes 1/2, thin bars 3..38, particles 39 onward.
vec3 objectPoint(int id,vec3 p,float time) {
    if(id>=39) {
        float k=float(id-39);
        return p*(pc.settings.x==1?.035:0)+vec3(sin(k*2.4+time)*3,1.4+sin(k*.8+time*.7),cos(k*1.7+time)*2);
    }
    if(id==0)return p*vec3(5,.06,4)+vec3(0,-.08,0);
    if(id==1) {
        float a=time*.55;
        p.xz=mat2(cos(a),sin(a),-sin(a),cos(a))*p.xz;
        return p*vec3(.85,.7,.85)+vec3(-1.5,.7,-.5);
    }
    if(id==2) {
        float a=-time*.4;
        p.xz=mat2(cos(a),sin(a),-sin(a),cos(a))*p.xz;
        return p*vec3(.7,.9,.7)+vec3(1.4+sin(time)*.6,.9,-.4);
    }
    int n=id-3;
    if(n<18) {
        p*=vec3(.015,1.2,.015);
        p.xy=mat2(.8,.6,-.6,.8)*p.xy;
        return p+vec3(float(n)*.19-1.7,1.7,1.4);
    }
    p*=vec3(.015,1.2,.015);
    p.xy=mat2(.8,-.6,.6,.8)*p.xy;
    return p+vec3(float(n-18)*.19-1.7,1.7,1.45);
}
// Orbit in world space; share the current eye with fragment lighting.
vec3 cameraPosition(float time) {
    float angle = pc.settings.y == 1 ? time * .18 : 0;
    return vec3(5 * sin(angle) + 5 * cos(angle), 3.7, 5 * cos(angle) - 5 * sin(angle));
}

// Positive view depth maps the near plane to Z=0 and the far plane to Z=W.
vec4 project(vec3 worldPosition, float time) {
    vec3 eye = cameraPosition(time);
    vec3 forward = normalize(vec3(0, .8, 0) - eye);
    vec3 right = normalize(cross(forward, vec3(0, 1, 0)));
    vec3 up = cross(right, forward);
    vec3 viewOffset = worldPosition - eye;
    float depth = dot(viewOffset, forward);
    float near = .1;
    float far = 100;
    return vec4(
        dot(viewOffset, right) * 1.9 * pc.sizeJitter.y / pc.sizeJitter.x,
        dot(viewOffset, up) * 1.9,
        far / (far - near) * depth - far * near / (far - near),
        depth
    );
}
void main() {
    cameraEye = cameraPosition(pc.times.x);
    // First triangle fills the background; remaining triangles form the objects.
    if(gl_VertexIndex<3) {
        vec2 xy=vec2((gl_VertexIndex<<1)&2,gl_VertexIndex&2)*2-1;
        gl_Position=vec4(xy,.999999,1);
        objectId=-1;
        world=vec3(0);
        normal=vec3(0,1,0);
        nowClip=oldClip=gl_Position;
        return;
    }
    // Recover object/face IDs, then transform the triangle into world space.
    objectId=(gl_VertexIndex-3)/36;
    int v=(gl_VertexIndex-3)%36;
    float t=pc.times.x,old=pc.times.y;
    vec3 p=corner(indices[v]);
    world=objectPoint(objectId,p,t);
    int tri=(v/3)*3;
    vec3 a = objectPoint(objectId, corner(indices[tri]), t);
    vec3 b = objectPoint(objectId, corner(indices[tri + 1]), t);
    vec3 c = objectPoint(objectId, corner(indices[tri + 2]), t);
    normal=normalize(cross(b-a,c-a));
    // Keep correspondence in unjittered clip space for both scene times.
    nowClip=project(world,t);
    oldClip=project(objectPoint(objectId,p,old),old);
    nowClip.x+=2*pc.times.z/pc.sizeJitter.x*nowClip.w;
    oldClip.x+=2*pc.times.w/pc.sizeJitter.x*oldClip.w;
    // Jitter moves only the raster position; it must not become motion.
    gl_Position=nowClip;
    gl_Position.xy+=vec2(2*pc.sizeJitter.z/pc.sizeJitter.x,-2*pc.sizeJitter.w/pc.sizeJitter.y)*gl_Position.w;
}
