#version 300 es layout(location = 0) in vec2 aQuadPos; // shared unit quad, centered (-0.5..+0.5), Y-up layout(location = 1) in vec2 aTexCoord; layout(location = 2) in vec2 aPos; // per-instance: world position (pivot point) layout(location = 3) in vec4 aSizeCosSin; // per-instance: w, h (world meters), cos, sin layout(location = 4) in vec4 aColor; layout(location = 5) in vec4 aUVRect; // per-instance: uvMin.xy, uvMax.zw layout(location = 6) in vec2 aPivot; // per-instance: image-space 0..1, (0.5,0.5) = center uniform mat4 u_projection; out vec2 vTexCoord; out vec4 vColor; void main() { // image-space pivot (x right, y down) -> centered quad space (Y-up) vec2 pivotQuad = vec2(aPivot.x - 0.5, 0.5 - aPivot.y); vec2 scaled = (aQuadPos - pivotQuad) * aSizeCosSin.xy; float c = aSizeCosSin.z; float s = aSizeCosSin.w; // standard CCW rotation — world space, pre-projection (no Y flip here) vec2 rotated = vec2( scaled.x * c - scaled.y * s, scaled.x * s + scaled.y * c ); vec2 worldPos = aPos + rotated; gl_Position = u_projection * vec4(worldPos, 0.0, 1.0); vTexCoord = mix(aUVRect.xy, aUVRect.zw, aTexCoord); vColor = aColor; }