#include "app.h"
#include "chunk_mesh.h"
#include "debug_draw.h"
#include "texture_atlas.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <stdexcept>

#include "third_party/stb_image.h"

void App::createMobBuffers() {
    // Pre-allocate for 60 mobs × 8 boxes × 24 verts × 9 floats
    struct V { float pos[3]; float col[3]; float uv[2]; float ti; };
    constexpr int MAX_QUADS   = 60 * 8 * 6;   // 60 mobs × 8 boxes × 6 faces
    constexpr int MAX_VERTS   = MAX_QUADS * 4;
    constexpr int MAX_INDICES = MAX_QUADS * 6;

    createBuffer(static_cast<VkDeviceSize>(MAX_VERTS * sizeof(V)),
        VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        mobVertexBuffer_, mobVertexMemory_);

    createBuffer(static_cast<VkDeviceSize>(MAX_INDICES * sizeof(uint32_t)),
        VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        mobIndexBuffer_, mobIndexMemory_);

    // Static index buffer: quads [0,1,2,0,2,3] [4,5,6,4,6,7] ...
    std::vector<uint32_t> idx;
    idx.reserve(MAX_INDICES);
    for (int q = 0; q < MAX_VERTS / 4; ++q) {
        const uint32_t b = static_cast<uint32_t>(q * 4);
        idx.insert(idx.end(), {b, b+1, b+2, b, b+2, b+3});
    }
    void* idata = nullptr;
    vkMapMemory(device_, mobIndexMemory_,
        0, static_cast<VkDeviceSize>(MAX_INDICES * sizeof(uint32_t)), 0, &idata);
    std::memcpy(idata, idx.data(), idx.size() * sizeof(uint32_t));
    vkUnmapMemory(device_, mobIndexMemory_);
}

void App::destroyMobBuffers() {
    destroyBuffer(mobVertexBuffer_, mobVertexMemory_);
    destroyBuffer(mobIndexBuffer_,  mobIndexMemory_);
}

void App::updateMobMesh() {
    mobDrawIndexCount_ = 0;
    if (mobs_.empty() || mobVertexBuffer_ == VK_NULL_HANDLE) return;
    if (gameState_ != GameState::Playing) return;

    constexpr float MOB_TI = -6.0f;
    struct V { float pos[3]; float col[3]; float uv[2]; float ti; };

    // Stack-allocated vertex buffer (avoid heap alloc each frame)
    constexpr int MAX_QUADS = 60 * 8 * 6;
    constexpr int MAX_VERTS = MAX_QUADS * 4;
    static V vbuf[MAX_VERTS];
    int vcount = 0;

    float curFlash = 0.0f; // set before each mob's box calls

    // emit one quad face (4 vertices)
    auto face = [&](
        float c0x, float c0y, float c0z,
        float c1x, float c1y, float c1z,
        float c2x, float c2y, float c2z,
        float c3x, float c3y, float c3z,
        float r, float g, float b, float bright)
    {
        if (vcount + 4 > MAX_VERTS) return;
        // Blend color toward red based on hitFlash (0=normal, 1=full red)
        float flash = std::min(1.0f, curFlash / 0.35f);
        float fr = r + (1.0f - r) * flash * 0.9f;
        float fg = g * (1.0f - flash * 0.85f);
        float fb = b * (1.0f - flash * 0.85f);
        float cols[3] = {fr, fg, fb};
        float ps[4][3] = {{c0x,c0y,c0z},{c1x,c1y,c1z},{c2x,c2y,c2z},{c3x,c3y,c3z}};
        for (int k = 0; k < 4; ++k) {
            V& v = vbuf[vcount++];
            v.pos[0]=ps[k][0]; v.pos[1]=ps[k][1]; v.pos[2]=ps[k][2];
            v.col[0]=cols[0];  v.col[1]=cols[1];  v.col[2]=cols[2];
            v.uv[0]=bright; v.uv[1]=0.0f;
            v.ti = MOB_TI;
        }
    };

    // emit rotated box at (wx,wy,wz), half-extents (hw,hh,hd), yaw around up axis
    auto box = [&](float wx, float wy, float wz,
                    float hw, float hh, float hd,
                    float yaw,
                    float upX, float upY, float upZ,
                    float r, float g, float b)
    {
        // Build gravity-relative basis: fwd_base and right_base in horizontal plane
        // ref = (0,0,1) unless up is near Z, then (0,1,0)
        float refY = (std::abs(upZ) > 0.9f) ? 1.0f : 0.0f;
        float refZ = (std::abs(upZ) > 0.9f) ? 0.0f : 1.0f;
        // fwd_base = normalize(ref projected onto horizontal plane)
        float fdot = refY*upY + refZ*upZ;
        float fbx = -upX*fdot, fby = refY - upY*fdot, fbz = refZ - upZ*fdot;
        float fbl = std::sqrt(fbx*fbx + fby*fby + fbz*fbz);
        if (fbl > 1e-6f) { fbx/=fbl; fby/=fbl; fbz/=fbl; }
        // right_base = cross(up, fwd_base)
        float rbx = upY*fbz - upZ*fby, rby = upZ*fbx - upX*fbz, rbz = upX*fby - upY*fbx;
        // Apply yaw: right = cos*right_base - sin*fwd_base, fwd = sin*right_base + cos*fwd_base
        float cy = std::cos(yaw), sy2 = std::sin(yaw);
        float rx = cy*rbx - sy2*fbx, ry = cy*rby - sy2*fby, rz = cy*rbz - sy2*fbz;
        float fx = sy2*rbx + cy*fbx, fy2 = sy2*rby + cy*fby, fz = sy2*rbz + cy*fbz;
        // Local→World: lx→right, ly→up, lz→fwd
        auto P = [&](float lx, float ly, float lz, float out[3]) {
            out[0] = wx + lx*rx + ly*upX + lz*fx;
            out[1] = wy + lx*ry + ly*upY + lz*fy2;
            out[2] = wz + lx*rz + ly*upZ + lz*fz;
        };
        float pts[2][2][2][3]; // [xi][yi][zi]
        for (int xi=0;xi<2;xi++) for (int yi=0;yi<2;yi++) for (int zi=0;zi<2;zi++)
            P(xi?hw:-hw, yi?hh:-hh, zi?hd:-hd, pts[xi][yi][zi]);
        auto& c = pts;
        // Top +Y
        face(c[0][1][0][0],c[0][1][0][1],c[0][1][0][2], c[1][1][0][0],c[1][1][0][1],c[1][1][0][2],
             c[1][1][1][0],c[1][1][1][1],c[1][1][1][2], c[0][1][1][0],c[0][1][1][1],c[0][1][1][2], r,g,b, 1.00f);
        // Bottom -Y
        face(c[0][0][0][0],c[0][0][0][1],c[0][0][0][2], c[0][0][1][0],c[0][0][1][1],c[0][0][1][2],
             c[1][0][1][0],c[1][0][1][1],c[1][0][1][2], c[1][0][0][0],c[1][0][0][1],c[1][0][0][2], r,g,b, 0.40f);
        // Front -Z local
        face(c[0][0][0][0],c[0][0][0][1],c[0][0][0][2], c[1][0][0][0],c[1][0][0][1],c[1][0][0][2],
             c[1][1][0][0],c[1][1][0][1],c[1][1][0][2], c[0][1][0][0],c[0][1][0][1],c[0][1][0][2], r,g,b, 0.75f);
        // Back +Z local
        face(c[1][0][1][0],c[1][0][1][1],c[1][0][1][2], c[0][0][1][0],c[0][0][1][1],c[0][0][1][2],
             c[0][1][1][0],c[0][1][1][1],c[0][1][1][2], c[1][1][1][0],c[1][1][1][1],c[1][1][1][2], r,g,b, 0.60f);
        // Right +X local
        face(c[1][0][0][0],c[1][0][0][1],c[1][0][0][2], c[1][0][1][0],c[1][0][1][1],c[1][0][1][2],
             c[1][1][1][0],c[1][1][1][1],c[1][1][1][2], c[1][1][0][0],c[1][1][0][1],c[1][1][0][2], r,g,b, 0.70f);
        // Left -X local
        face(c[0][0][1][0],c[0][0][1][1],c[0][0][1][2], c[0][0][0][0],c[0][0][0][1],c[0][0][0][2],
             c[0][1][0][0],c[0][1][0][1],c[0][1][0][2], c[0][1][1][0],c[0][1][1][1],c[0][1][1][2], r,g,b, 0.55f);
    };

    constexpr double MOB_RENDER_DIST_SQ = 200.0 * 200.0;
    for (const auto& mob : mobs_) {
        if (mob.dead) continue;
        const double dxc = mob.x - cameraX_;
        const double dyc = mob.y - cameraY_;
        const double dzc = mob.z - cameraZ_;
        if (dxc*dxc + dyc*dyc + dzc*dzc > MOB_RENDER_DIST_SQ) continue;
        curFlash = mob.hitFlash;
        const float wx  = static_cast<float>(mob.x);
        const float wy  = static_cast<float>(mob.y);
        const float wz  = static_cast<float>(mob.z);
        const float yaw = mob.yaw;
        const float sw  = std::sin(mob.animPhase) * 0.28f;  // leg swing

        // Gravity-relative basis for this mob
        const float ux = mob.upX, uy = mob.upY, uz = mob.upZ;
        float refY2 = (std::abs(uz) > 0.9f) ? 1.0f : 0.0f;
        float refZ2 = (std::abs(uz) > 0.9f) ? 0.0f : 1.0f;
        float fdt = refY2*uy + refZ2*uz;
        float fb0x=-ux*fdt, fb0y=refY2-uy*fdt, fb0z=refZ2-uz*fdt;
        float fbl=std::sqrt(fb0x*fb0x+fb0y*fb0y+fb0z*fb0z);
        if(fbl>1e-6f){fb0x/=fbl;fb0y/=fbl;fb0z/=fbl;}
        float rb0x=uy*fb0z-uz*fb0y, rb0y=uz*fb0x-ux*fb0z, rb0z=ux*fb0y-uy*fb0x;
        const float cya=std::cos(yaw), sya=std::sin(yaw);
        // forward = sin*right_base + cos*fwd_base
        const float fX=sya*rb0x+cya*fb0x, fY=sya*rb0y+cya*fb0y, fZ=sya*rb0z+cya*fb0z;
        // right   = cos*right_base - sin*fwd_base
        const float rX=cya*rb0x-sya*fb0x, rY=cya*rb0y-sya*fb0y, rZ=cya*rb0z-sya*fb0z;
        // Helper: world pos = mob base + up*h + fwd*f + right*r
        auto P3 = [&](float h, float f, float r, float& ox, float& oy, float& oz){
            ox = wx+ux*h+fX*f+rX*r;
            oy = wy+uy*h+fY*f+rY*r;
            oz = wz+uz*h+fZ*f+rZ*r;
        };

        switch (mob.type) {
        case MobType::Sheep: {
            // Body center at height 0.50
            float bx,by,bz; P3(0.50f,0,0,bx,by,bz);
            box(bx,by,bz, 0.30f,0.22f,0.38f, yaw, ux,uy,uz, 0.93f,0.93f,0.92f);
            // Head at height 0.76, forward 0.32
            float hx,hy,hz; P3(0.76f,0.32f,0,hx,hy,hz);
            box(hx,hy,hz, 0.17f,0.17f,0.19f, yaw, ux,uy,uz, 0.86f,0.66f,0.58f);
            // 4 legs
            for (int lxi=0;lxi<2;lxi++) for (int lzi=0;lzi<2;lzi++) {
                float ll=(lxi?1.f:-1.f)*0.18f, lf=(lzi?1.f:-1.f)*0.22f;
                float lgx,lgy,lgz; P3(0.17f,lf,ll,lgx,lgy,lgz);
                float ls=(lzi==0?sw:-sw)*(lxi?-1.f:1.f);
                box(lgx,lgy,lgz, 0.08f,0.17f,0.08f, yaw+ls, ux,uy,uz, 0.25f,0.22f,0.20f);
            }
            break;
        }
        case MobType::Wolf: {
            float bx,by,bz; P3(0.45f,0,0,bx,by,bz);
            box(bx,by,bz, 0.22f,0.22f,0.35f, yaw, ux,uy,uz, 0.28f,0.28f,0.30f);
            float hx,hy,hz; P3(0.62f,0.38f,0,hx,hy,hz);
            box(hx,hy,hz, 0.18f,0.16f,0.22f, yaw, ux,uy,uz, 0.25f,0.25f,0.27f);
            // Tail (behind)
            float tx,ty,tz; P3(0.65f,-0.42f,0,tx,ty,tz);
            box(tx,ty,tz, 0.05f,0.06f,0.18f, yaw+0.55f, ux,uy,uz, 0.30f,0.30f,0.33f);
            for (int lxi=0;lxi<2;lxi++) for (int lzi=0;lzi<2;lzi++) {
                float ll=(lxi?1.f:-1.f)*0.15f, lf=(lzi?1.f:-1.f)*0.22f;
                float lgx,lgy,lgz; P3(0.18f,lf,ll,lgx,lgy,lgz);
                float ls=(lzi==0?sw:-sw)*(lxi?-1.f:1.f)*1.2f;
                box(lgx,lgy,lgz, 0.07f,0.18f,0.07f, yaw+ls, ux,uy,uz, 0.25f,0.25f,0.28f);
            }
            break;
        }
        case MobType::Human: {
            // Torso
            float bx,by,bz; P3(0.88f,0,0,bx,by,bz);
            box(bx,by,bz, 0.20f,0.28f,0.13f, yaw, ux,uy,uz, 0.22f,0.28f,0.52f);
            // Head
            float hx,hy,hz; P3(1.42f,0,0,hx,hy,hz);
            box(hx,hy,hz, 0.17f,0.18f,0.17f, yaw, ux,uy,uz, 0.90f,0.72f,0.55f);
            // Arms (right/left)
            float ax=0.29f;
            float arx,ary,arz; P3(0.86f,0,ax,arx,ary,arz);
            box(arx,ary,arz, 0.08f,0.27f,0.08f, yaw+sw*1.3f, ux,uy,uz, 0.22f,0.28f,0.52f);
            float alx,aly,alz; P3(0.86f,0,-ax,alx,aly,alz);
            box(alx,aly,alz, 0.08f,0.27f,0.08f, yaw-sw*1.3f, ux,uy,uz, 0.22f,0.28f,0.52f);
            // Legs
            float loff=0.10f;
            float l1x,l1y,l1z; P3(0.28f,0, loff,l1x,l1y,l1z);
            box(l1x,l1y,l1z, 0.09f,0.28f,0.09f, yaw+sw, ux,uy,uz, 0.18f,0.18f,0.32f);
            float l2x,l2y,l2z; P3(0.28f,0,-loff,l2x,l2y,l2z);
            box(l2x,l2y,l2z, 0.09f,0.28f,0.09f, yaw-sw, ux,uy,uz, 0.18f,0.18f,0.32f);
            break;
        }
        }
    }

    if (vcount == 0) return;

    const VkDeviceSize vsize = static_cast<VkDeviceSize>(vcount * sizeof(V));
    void* vdata = nullptr;
    vkMapMemory(device_, mobVertexMemory_, 0, vsize, 0, &vdata);
    std::memcpy(vdata, vbuf, static_cast<size_t>(vsize));
    vkUnmapMemory(device_, mobVertexMemory_);

    mobDrawIndexCount_ = (vcount / 4) * 6;
}

// ── Drop GPU buffers ──────────────────────────────────────────────────────────

void App::createDropBuffers() {
    struct V { float pos[3]; float col[3]; float uv[2]; float ti; };
    // 3D cube: 6 faces × 4 verts = 24 verts per drop, 6 faces × 6 indices = 36 per drop
    constexpr int MAX_DROPS   = 256;
    constexpr int MAX_VERTS   = MAX_DROPS * 24;
    constexpr int MAX_INDICES = MAX_DROPS * 36;

    createBuffer(static_cast<VkDeviceSize>(MAX_VERTS * sizeof(V)),
        VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        dropVertexBuffer_, dropVertexMemory_);

    createBuffer(static_cast<VkDeviceSize>(MAX_INDICES * sizeof(uint32_t)),
        VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        dropIndexBuffer_, dropIndexMemory_);

    // Static index buffer: 6 quads per drop, each quad = 2 tris
    std::vector<uint32_t> idx;
    idx.reserve(MAX_INDICES);
    for (int drop = 0; drop < MAX_DROPS; ++drop) {
        for (int face = 0; face < 6; ++face) {
            const uint32_t b = static_cast<uint32_t>(drop * 24 + face * 4);
            idx.insert(idx.end(), {b, b+1, b+2, b, b+2, b+3});
        }
    }
    void* idata = nullptr;
    vkMapMemory(device_, dropIndexMemory_,
        0, static_cast<VkDeviceSize>(MAX_INDICES * sizeof(uint32_t)), 0, &idata);
    std::memcpy(idata, idx.data(), idx.size() * sizeof(uint32_t));
    vkUnmapMemory(device_, dropIndexMemory_);
}

void App::destroyDropBuffers() {
    destroyBuffer(dropVertexBuffer_, dropVertexMemory_);
    destroyBuffer(dropIndexBuffer_,  dropIndexMemory_);
}

void App::updateDropMesh() {
    dropIndexCount_ = 0;
    if (drops_.empty() || dropVertexBuffer_ == VK_NULL_HANDLE) return;
    if (gameState_ != GameState::Playing) return;

    struct V { float pos[3]; float col[3]; float uv[2]; float ti; };
    constexpr int MAX_DROPS = 256;
    constexpr int MAX_VERTS = MAX_DROPS * 24; // 6 faces × 4 verts
    static V vbuf[MAX_VERTS];
    int vcount = 0;

    // Gravity up axis (unit vector)
    const float ux = gravityUp_.x, uy = gravityUp_.y, uz = gravityUp_.z;

    // Face brightness: top / bottom / +right / -right / +fwd / -fwd
    static constexpr float BRIGHT[6] = { 1.0f, 0.55f, 0.75f, 0.75f, 0.70f, 0.70f };

    // CubeNetFace order matching atlasTileIndex: Top=0,Left=1,Front=2,Right=3,Back=4,Bottom=5
    // Map cube face idx → CubeNetFace: top=Top, bottom=Bottom, +r=Right, -r=Left, +f=Front, -f=Back
    static constexpr CubeNetFace FACE_NET[6] = {
        CubeNetFace::Top, CubeNetFace::Bottom,
        CubeNetFace::Right, CubeNetFace::Left,
        CubeNetFace::Front, CubeNetFace::Back
    };

    for (const auto& d : drops_) {
        if (vcount + 24 > MAX_VERTS) break;

        float ox = static_cast<float>(d.x);
        float oy = static_cast<float>(d.y);
        float oz = static_cast<float>(d.z);

        // Bob up and down along gravity axis
        float bob = 0.05f * std::sin(d.animTime * 3.0f);
        ox += ux * bob;  oy += uy * bob;  oz += uz * bob;

        // Compute a stable base-right vector perpendicular to gravity up
        float bRX, bRY, bRZ;
        if (std::abs(uy) < 0.99f) {
            // up × world-Y = (-uz, 0, ux)
            bRX = -uz; bRY = 0.0f; bRZ = ux;
        } else {
            // up × world-Z = (uy, -ux, 0)
            bRX = uy; bRY = -ux; bRZ = 0.0f;
        }
        float bRLen = std::sqrt(bRX*bRX + bRY*bRY + bRZ*bRZ);
        if (bRLen > 1e-6f) { bRX/=bRLen; bRY/=bRLen; bRZ/=bRLen; }

        // Rotate base-right around up axis by animTime (Rodrigues, up·bR=0 so last term drops)
        float ang  = d.animTime * 1.8f;
        float cosA = std::cos(ang), sinA = std::sin(ang);
        float crX = uy*bRZ - uz*bRY, crY = uz*bRX - ux*bRZ, crZ = ux*bRY - uy*bRX;
        float rX = bRX*cosA + crX*sinA;
        float rY = bRY*cosA + crY*sinA;
        float rZ = bRZ*cosA + crZ*sinA;

        // Forward = up × right
        float fX = uy*rZ - uz*rY;
        float fY = uz*rX - ux*rZ;
        float fZ = ux*rY - uy*rX;

        constexpr float H = 0.15f;

        // Get tile index per face
        float tiles[6];
        const int typeInt = static_cast<int>(d.type);
        if (typeInt >= 12) {
            // Item: same icon tile for all faces
            float itemTile = 66.0f + static_cast<float>(typeInt - 12);
            for (int fi = 0; fi < 6; fi++) tiles[fi] = itemTile;
        } else {
            for (int fi = 0; fi < 6; fi++)
                tiles[fi] = static_cast<float>(atlasTileIndex(d.type, FACE_NET[fi]));
        }

        // Emit one quad: v0(0,0) v1(1,0) v2(1,1) v3(0,1)
        auto quad = [&](
            float ax, float ay, float az,
            float bx2, float by2, float bz2,
            float cx, float cy, float cz,
            float dx, float dy, float dz,
            float br, float ti)
        {
            V& v0 = vbuf[vcount++];
            v0.pos[0]=ax; v0.pos[1]=ay; v0.pos[2]=az;
            v0.col[0]=br; v0.col[1]=br; v0.col[2]=br; v0.uv[0]=0; v0.uv[1]=0; v0.ti=ti;
            V& v1 = vbuf[vcount++];
            v1.pos[0]=bx2; v1.pos[1]=by2; v1.pos[2]=bz2;
            v1.col[0]=br; v1.col[1]=br; v1.col[2]=br; v1.uv[0]=1; v1.uv[1]=0; v1.ti=ti;
            V& v2 = vbuf[vcount++];
            v2.pos[0]=cx; v2.pos[1]=cy; v2.pos[2]=cz;
            v2.col[0]=br; v2.col[1]=br; v2.col[2]=br; v2.uv[0]=1; v2.uv[1]=1; v2.ti=ti;
            V& v3 = vbuf[vcount++];
            v3.pos[0]=dx; v3.pos[1]=dy; v3.pos[2]=dz;
            v3.col[0]=br; v3.col[1]=br; v3.col[2]=br; v3.uv[0]=0; v3.uv[1]=1; v3.ti=ti;
        };

        // Top face (+up): looking down → r goes right, f goes forward
        quad(ox+ux*H-rX*H-fX*H, oy+uy*H-rY*H-fY*H, oz+uz*H-rZ*H-fZ*H,
             ox+ux*H+rX*H-fX*H, oy+uy*H+rY*H-fY*H, oz+uz*H+rZ*H-fZ*H,
             ox+ux*H+rX*H+fX*H, oy+uy*H+rY*H+fY*H, oz+uz*H+rZ*H+fZ*H,
             ox+ux*H-rX*H+fX*H, oy+uy*H-rY*H+fY*H, oz+uz*H-rZ*H+fZ*H,
             BRIGHT[0], tiles[0]);

        // Bottom face (-up)
        quad(ox-ux*H+rX*H-fX*H, oy-uy*H+rY*H-fY*H, oz-uz*H+rZ*H-fZ*H,
             ox-ux*H-rX*H-fX*H, oy-uy*H-rY*H-fY*H, oz-uz*H-rZ*H-fZ*H,
             ox-ux*H-rX*H+fX*H, oy-uy*H-rY*H+fY*H, oz-uz*H-rZ*H+fZ*H,
             ox-ux*H+rX*H+fX*H, oy-uy*H+rY*H+fY*H, oz-uz*H+rZ*H+fZ*H,
             BRIGHT[1], tiles[1]);

        // +Right face
        quad(ox+rX*H+ux*H-fX*H, oy+rY*H+uy*H-fY*H, oz+rZ*H+uz*H-fZ*H,
             ox+rX*H+ux*H+fX*H, oy+rY*H+uy*H+fY*H, oz+rZ*H+uz*H+fZ*H,
             ox+rX*H-ux*H+fX*H, oy+rY*H-uy*H+fY*H, oz+rZ*H-uz*H+fZ*H,
             ox+rX*H-ux*H-fX*H, oy+rY*H-uy*H-fY*H, oz+rZ*H-uz*H-fZ*H,
             BRIGHT[2], tiles[2]);

        // -Right (Left) face
        quad(ox-rX*H+ux*H+fX*H, oy-rY*H+uy*H+fY*H, oz-rZ*H+uz*H+fZ*H,
             ox-rX*H+ux*H-fX*H, oy-rY*H+uy*H-fY*H, oz-rZ*H+uz*H-fZ*H,
             ox-rX*H-ux*H-fX*H, oy-rY*H-uy*H-fY*H, oz-rZ*H-uz*H-fZ*H,
             ox-rX*H-ux*H+fX*H, oy-rY*H-uy*H+fY*H, oz-rZ*H-uz*H+fZ*H,
             BRIGHT[3], tiles[3]);

        // +Forward face
        quad(ox+fX*H-rX*H+ux*H, oy+fY*H-rY*H+uy*H, oz+fZ*H-rZ*H+uz*H,
             ox+fX*H+rX*H+ux*H, oy+fY*H+rY*H+uy*H, oz+fZ*H+rZ*H+uz*H,
             ox+fX*H+rX*H-ux*H, oy+fY*H+rY*H-uy*H, oz+fZ*H+rZ*H-uz*H,
             ox+fX*H-rX*H-ux*H, oy+fY*H-rY*H-uy*H, oz+fZ*H-rZ*H-uz*H,
             BRIGHT[4], tiles[4]);

        // -Forward (Back) face
        quad(ox-fX*H+rX*H+ux*H, oy-fY*H+rY*H+uy*H, oz-fZ*H+rZ*H+uz*H,
             ox-fX*H-rX*H+ux*H, oy-fY*H-rY*H+uy*H, oz-fZ*H-rZ*H+uz*H,
             ox-fX*H-rX*H-ux*H, oy-fY*H-rY*H-uy*H, oz-fZ*H-rZ*H-uz*H,
             ox-fX*H+rX*H-ux*H, oy-fY*H+rY*H-uy*H, oz-fZ*H+rZ*H-uz*H,
             BRIGHT[5], tiles[5]);
    }

    if (vcount == 0) return;

    const VkDeviceSize vsize = static_cast<VkDeviceSize>(vcount * sizeof(V));
    void* vdata = nullptr;
    vkMapMemory(device_, dropVertexMemory_, 0, vsize, 0, &vdata);
    std::memcpy(vdata, vbuf, static_cast<size_t>(vsize));
    vkUnmapMemory(device_, dropVertexMemory_);

    dropIndexCount_ = (vcount / 4) * 6;  // 4 verts per quad, 6 indices per quad
}
