 

#include "pipeline.hpp"
#include "game.hpp"
#include <SDL3/SDL.h>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>





struct StageInfo {
    const char* name;
    const char* description;
    const char* detail;
    const char* godot_api;
};

static const StageInfo stage_info[] = {
    {"1. Object Space",
     "Local space: vertices defined relative to the model's pivot.",
     "Identity transform - raw vertex data as authored.",
     "Godot: MeshInstance3D / ArrayMesh vertex data"},
    {"2. World Space",
     "Object placed in the scene via the Model Matrix.",
     "Model Matrix = Translation * Rotation * Scale",
     "Godot: Node3D.global_transform"},
    {"3. View Space",
     "Scene transformed to the camera's frame of reference.",
     "View Matrix = inverse(Camera Transform)",
     "Godot: Camera3D.get_camera_transform().inverse()"},
    {"4. Clip Space",
     "Homogeneous coordinates after projection. W != 1.",
     "Projection Matrix maps frustum to clip volume.",
     "Godot: Camera3D.get_camera_projection()"},
    {"5. NDC (Normalized Device Coordinates)",
     "After perspective divide (xyz / w). Range: [-1, 1].",
     "Vertices outside [-1,1] are clipped.",
     "Godot: RenderingServer (GPU perspective divide)"},
    {"6. Screen Space",
     "Final pixel coordinates for rasterization.",
     "Viewport transform maps NDC to screen resolution.",
     "Godot: Viewport.get_visible_rect() / Camera3D.unproject_position()"},
};





static const Vec3 cube_verts[NUM_CUBE_VERTS] = {
    {-0.7f, -0.7f, -0.7f}, 
    { 0.7f, -0.7f, -0.7f}, 
    { 0.7f,  0.7f, -0.7f}, 
    {-0.7f,  0.7f, -0.7f}, 
    {-0.7f, -0.7f,  0.7f}, 
    { 0.7f, -0.7f,  0.7f}, 
    { 0.7f,  0.7f,  0.7f}, 
    {-0.7f,  0.7f,  0.7f}, 
};

static const int cube_edges[NUM_CUBE_EDGES][2] = {
    {0,1}, {1,2}, {2,3}, {3,0}, 
    {4,5}, {5,6}, {6,7}, {7,4}, 
    {0,4}, {1,5}, {2,6}, {3,7}, 
};





static const Vec3 tri_verts[NUM_TRI_VERTS] = {
    { 0.0f,  1.0f, 0.0f},  
    {-0.8f, -0.6f, 0.0f},  
    { 0.8f, -0.6f, 0.0f},  
};

static const int tri_edges[NUM_TRI_EDGES][2] = {
    {0, 1}, {1, 2}, {2, 0},
};


// Z-fighting demo plane (flat quad)
static const Vec3 plane_verts[4] = {
    {-1.5f, -1.5f, 0.0f},
    { 1.5f, -1.5f, 0.0f},
    { 1.5f,  1.5f, 0.0f},
    {-1.5f,  1.5f, 0.0f},
};
static const int plane_edges[4][2] = {{0,1}, {1,2}, {2,3}, {3,0}};

struct RGBA { uint8_t r, g, b, a; };
static const RGBA vert_colors[NUM_CUBE_VERTS] = {
    {255,  80,  80, 255}, 
    { 80, 255,  80, 255}, 
    { 80,  80, 255, 255}, 
    {255, 255,  80, 255}, 
    {255,  80, 255, 255}, 
    { 80, 255, 255, 255}, 
    {255, 180,  80, 255}, 
    {180,  80, 255, 255}, 
};

static const RGBA tri_colors[NUM_TRI_VERTS] = {
    {255, 255, 255, 255}, 
    {180, 220, 255, 255}, 
    {255, 200, 180, 255}, 
};


static const RGBA stage_colors[] = {
    {255, 120,  80, 255},
    {255, 200,  80, 255},
    {120, 255,  80, 255},
    { 80, 200, 255, 255},
    {120, 120, 255, 255},
    {200,  80, 255, 255},
};

static const char* stage_short_names[] = {
    "Object", "World", "View", "Clip", "NDC", "Screen"
};





enum SliderID {
    SLIDER_POS_X, SLIDER_POS_Y, SLIDER_POS_Z,
    SLIDER_ROT_Y, SLIDER_SCALE,
    SLIDER_FOV, SLIDER_NEAR, SLIDER_FAR,
    SLIDER_SHADER_AMP, SLIDER_SHADER_FREQ,
    SLIDER_PLANE_Z,
    SLIDER_COUNT
};

struct SliderDef {
    const char* label;
    float min_val, max_val;
};

static const SliderDef slider_defs[SLIDER_COUNT] = {
    {"Pos X",    -10.0f, 10.0f},
    {"Pos Y",     -5.0f,  5.0f},
    {"Pos Z",    -10.0f, 10.0f},
    {"Rot Y",      0.0f, 360.0f},
    {"Scale",      0.1f,  3.0f},
    {"FOV",       20.0f, 120.0f},
    {"Near",      0.01f,  5.0f},
    {"Far",        5.0f, 50.0f},
    {"Amp",        0.0f,  1.0f},
    {"Freq",       0.5f,  8.0f},
    {"Plane Z",   -10.0f, 10.0f},
};

static float slider_get(const PipelineState& ps, int id) {
    switch (id) {
        case SLIDER_POS_X: return ps.obj_pos_x;
        case SLIDER_POS_Y: return ps.obj_pos_y;
        case SLIDER_POS_Z: return ps.obj_pos_z;
        case SLIDER_ROT_Y: return ps.obj_rot_y * 180.0f / PI;
        case SLIDER_SCALE: return ps.obj_scale;
        case SLIDER_FOV:   return ps.pipe_cam_fov * 180.0f / PI;
        case SLIDER_NEAR:  return ps.pipe_cam_near;
        case SLIDER_FAR:   return ps.pipe_cam_far;
        case SLIDER_SHADER_AMP:  return ps.shader_amplitude;
        case SLIDER_SHADER_FREQ: return ps.shader_frequency;
        case SLIDER_PLANE_Z:     return ps.zfight_plane_z;
        default: return 0;
    }
}

static void slider_set(PipelineState& ps, int id, float val) {
    switch (id) {
        case SLIDER_POS_X: ps.obj_pos_x = val; break;
        case SLIDER_POS_Y: ps.obj_pos_y = val; break;
        case SLIDER_POS_Z: ps.obj_pos_z = val; break;
        case SLIDER_ROT_Y: ps.obj_rot_y = val * PI / 180.0f; break;
        case SLIDER_SCALE: ps.obj_scale = val; break;
        case SLIDER_FOV:   ps.pipe_cam_fov = val * PI / 180.0f; break;
        case SLIDER_NEAR:  ps.pipe_cam_near = val; break;
        case SLIDER_FAR:   ps.pipe_cam_far = val; break;
        case SLIDER_SHADER_AMP:  ps.shader_amplitude = val; break;
        case SLIDER_SHADER_FREQ: ps.shader_frequency = val; break;
        case SLIDER_PLANE_Z:     ps.zfight_plane_z = val; break;
    }
}


static constexpr float SLIDER_PANEL_X = 24.0f;
static constexpr float SLIDER_PANEL_Y_OFFSET = 210.0f;
static constexpr float SLIDER_W = 140.0f;
static constexpr float SLIDER_H = 14.0f;
static constexpr float SLIDER_SPACING = 26.0f;
static constexpr float SLIDER_HANDLE_W = 8.0f;
static constexpr float SLIDER_HANDLE_H = 14.0f;
static constexpr float SLIDER_SECTION_GAP = 18.0f;


static constexpr float SLIDER_TOP_PAD = 36.0f; // room for header + section label

static SDL_FRect slider_track_rect(float vp_y, int idx) {
    float x = SLIDER_PANEL_X + 52;
    float y = vp_y + SLIDER_PANEL_Y_OFFSET + SLIDER_TOP_PAD + idx * SLIDER_SPACING;
    // Extra gap before Camera and Shader sections
    if (idx >= SLIDER_FOV) y += SLIDER_SECTION_GAP;
    if (idx >= SLIDER_SHADER_AMP) y += SLIDER_SECTION_GAP;
    if (idx >= SLIDER_PLANE_Z) y += SLIDER_SECTION_GAP;
    return {x, y, SLIDER_W, SLIDER_H};
}


static float slider_handle_x(const SDL_FRect& track, float val, float min_v, float max_v) {
    float t = (val - min_v) / (max_v - min_v);
    t = std::clamp(t, 0.0f, 1.0f);
    return track.x + t * track.w;
}


// Check if a slider should be visible based on current toggles
static bool slider_visible(const PipelineState& ps, int id) {
    if ((id == SLIDER_SHADER_AMP || id == SLIDER_SHADER_FREQ) && !ps.shader_mode) return false;
    if (id == SLIDER_PLANE_Z && !ps.show_zfight_plane) return false;
    return true;
}

static int slider_hit_test(const PipelineState& ps, float mx, float my, float vp_y) {
    if (!ps.show_sliders) return -1;
    for (int i = 0; i < SLIDER_COUNT; i++) {
        if (!slider_visible(ps, i)) continue;
        SDL_FRect track = slider_track_rect(vp_y, i);
        float val = slider_get(ps, i);
        float hx = slider_handle_x(track, val, slider_defs[i].min_val, slider_defs[i].max_val);
        float hy = track.y + track.h * 0.5f;

        if (mx >= hx - 10 && mx <= hx + 10 &&
            my >= hy - 10 && my <= hy + 10) {
            return i;
        }
    }
    return -1;
}


static const int cube_faces[6][4] = {
    {0, 1, 2, 3}, 
    {4, 5, 6, 7}, 
    {0, 3, 7, 4}, 
    {1, 2, 6, 5}, 
    {0, 1, 5, 4}, 
    {2, 3, 7, 6}, 
};


static bool is_3d_stage(int stage) { return stage <= 3; }


static void get_stage_verts(const PipelineState& ps, int stage, Vec3 out[NUM_CUBE_VERTS]) {
    for (int i = 0; i < NUM_CUBE_VERTS; i++) {
        switch (stage) {
            case 0: out[i] = ps.verts_object[i].xyz(); break;
            case 1: out[i] = ps.verts_world[i].xyz(); break;
            case 2: out[i] = ps.verts_view[i].xyz(); break;
            case 3: out[i] = ps.verts_clip[i].xyz(); break;
            case 4: out[i] = ps.verts_ndc[i]; break;
            case 5: out[i] = ps.verts_screen[i]; break;
        }
    }
}

static void get_tri_stage_verts(const PipelineState& ps, int stage, Vec3 out[NUM_TRI_VERTS]) {
    for (int i = 0; i < NUM_TRI_VERTS; i++) {
        switch (stage) {
            case 0: out[i] = ps.tri_object[i].xyz(); break;
            case 1: out[i] = ps.tri_world[i].xyz(); break;
            case 2: out[i] = ps.tri_view[i].xyz(); break;
            case 3: out[i] = ps.tri_clip[i].xyz(); break;
            case 4: out[i] = ps.tri_ndc[i]; break;
            case 5: out[i] = ps.tri_screen[i]; break;
        }
    }
}






static Vec3 project_to_viewport(Vec3 p, float pitch, float yaw, float distance,
                                 float vp_x, float vp_y, float vp_w, float vp_h) {
    float ax = pitch * PI / 180.0f;
    float ay = yaw * PI / 180.0f;

    Vec3 eye = {
        distance * std::cos(ax) * std::sin(ay),
        distance * std::sin(ax),
        distance * std::cos(ax) * std::cos(ay)
    };

    Mat4 view = Mat4::lookAt(eye, {0, 0, 0}, {0, 1, 0});
    Mat4 proj = Mat4::perspective(0.8f, vp_w / vp_h, 0.1f, 100.0f);
    Mat4 vp = proj * view;

    Vec4 clip = vp * Vec4{p.x, p.y, p.z, 1.0f};
    if (clip.w <= 0.01f) return {-10000, -10000, 0};

    Vec3 ndc = clip.perspDiv();
    float sx = vp_x + (ndc.x * 0.5f + 0.5f) * vp_w;
    float sy = vp_y + (1.0f - (ndc.y * 0.5f + 0.5f)) * vp_h;
    return {sx, sy, ndc.z};
}

static void draw_line_3d(SDL_Renderer* r, Vec3 a, Vec3 b,
                          float pitch, float yaw, float dist,
                          float vp_x, float vp_y, float vp_w, float vp_h) {
    Vec3 sa = project_to_viewport(a, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    Vec3 sb = project_to_viewport(b, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    if (sa.x < -5000 || sb.x < -5000) return;
    SDL_RenderLine(r, sa.x, sa.y, sb.x, sb.y);
}

static void draw_axes_3d(SDL_Renderer* r, Vec3 origin, float length,
                          float pitch, float yaw, float dist,
                          float vp_x, float vp_y, float vp_w, float vp_h) {
    
    SDL_SetRenderDrawColor(r, 255, 60, 60, 255);
    draw_line_3d(r, origin, {origin.x + length, origin.y, origin.z},
                 pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    
    SDL_SetRenderDrawColor(r, 60, 255, 60, 255);
    draw_line_3d(r, origin, {origin.x, origin.y + length, origin.z},
                 pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    
    SDL_SetRenderDrawColor(r, 60, 60, 255, 255);
    draw_line_3d(r, origin, {origin.x, origin.y, origin.z + length},
                 pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
}

static void draw_grid_3d(SDL_Renderer* r, float size, int divisions,
                          float pitch, float yaw, float dist,
                          float vp_x, float vp_y, float vp_w, float vp_h) {
    SDL_SetRenderDrawColor(r, 45, 45, 55, 255);
    float half = size * 0.5f;
    float step = size / divisions;
    for (int i = 0; i <= divisions; i++) {
        float p = -half + i * step;
        draw_line_3d(r, {p, 0, -half}, {p, 0, half},
                     pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
        draw_line_3d(r, {-half, 0, p}, {half, 0, p},
                     pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    }
}


static void draw_wireframe_3d(SDL_Renderer* r, const Vec3 verts[],
                               float pitch, float yaw, float dist,
                               float vp_x, float vp_y, float vp_w, float vp_h) {
    
    for (int i = 0; i < NUM_CUBE_EDGES; i++) {
        int a = cube_edges[i][0], b = cube_edges[i][1];
        uint8_t cr = (uint8_t)((vert_colors[a].r + vert_colors[b].r) / 2);
        uint8_t cg = (uint8_t)((vert_colors[a].g + vert_colors[b].g) / 2);
        uint8_t cb = (uint8_t)((vert_colors[a].b + vert_colors[b].b) / 2);
        SDL_SetRenderDrawColor(r, cr, cg, cb, 255);
        draw_line_3d(r, verts[a], verts[b],
                     pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    }

    
    for (int i = 0; i < NUM_CUBE_VERTS; i++) {
        Vec3 sp = project_to_viewport(verts[i], pitch, yaw, dist,
                                       vp_x, vp_y, vp_w, vp_h);
        if (sp.x < -5000) continue;
        SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, vert_colors[i].b, 255);
        SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6};
        SDL_RenderFillRect(r, &dot);
    }
}


static void draw_wireframe_2d(SDL_Renderer* r, const Vec3 verts[],
                               float vp_x, float vp_y, float vp_w, float vp_h,
                               float coord_min, float coord_max) {
    float range = coord_max - coord_min;

    auto to_screen = [&](Vec3 v) -> Vec3 {
        float sx = vp_x + ((v.x - coord_min) / range) * vp_w;
        float sy = vp_y + (1.0f - (v.y - coord_min) / range) * vp_h;
        return {sx, sy, v.z};
    };

    for (int i = 0; i < NUM_CUBE_EDGES; i++) {
        int a = cube_edges[i][0], b = cube_edges[i][1];
        uint8_t cr = (uint8_t)((vert_colors[a].r + vert_colors[b].r) / 2);
        uint8_t cg = (uint8_t)((vert_colors[a].g + vert_colors[b].g) / 2);
        uint8_t cb = (uint8_t)((vert_colors[a].b + vert_colors[b].b) / 2);
        SDL_SetRenderDrawColor(r, cr, cg, cb, 255);
        Vec3 sa = to_screen(verts[a]);
        Vec3 sb = to_screen(verts[b]);
        SDL_RenderLine(r, sa.x, sa.y, sb.x, sb.y);
    }

    for (int i = 0; i < NUM_CUBE_VERTS; i++) {
        Vec3 sp = to_screen(verts[i]);
        SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, vert_colors[i].b, 255);
        SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6};
        SDL_RenderFillRect(r, &dot);
    }
}


static void draw_faces_3d(SDL_Renderer* r, const Vec3 verts[],
                           float pitch, float yaw, float dist,
                           float vp_x, float vp_y, float vp_w, float vp_h,
                           uint8_t face_alpha) {
    
    Vec3 screen[NUM_CUBE_VERTS];
    for (int i = 0; i < NUM_CUBE_VERTS; i++) {
        screen[i] = project_to_viewport(verts[i], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    }

    
    struct FaceSort { int idx; float avg_z; };
    FaceSort sorted[6];
    for (int f = 0; f < 6; f++) {
        sorted[f].idx = f;
        float z_sum = 0;
        for (int v = 0; v < 4; v++) z_sum += screen[cube_faces[f][v]].z;
        sorted[f].avg_z = z_sum / 4.0f;
    }

    
    std::sort(sorted, sorted + 6, [](const FaceSort& a, const FaceSort& b) {
        return a.avg_z > b.avg_z;
    });

    
    for (int si = 0; si < 6; si++) {
        int f = sorted[si].idx;
        const int* fi = cube_faces[f];

        
        Vec3 s0 = screen[fi[0]], s1 = screen[fi[1]], s2 = screen[fi[2]];
        float cross_z = (s1.x - s0.x) * (s2.y - s0.y) - (s1.y - s0.y) * (s2.x - s0.x);
        if (cross_z > 0) continue; 

        
        int avg_r = 0, avg_g = 0, avg_b = 0;
        for (int v = 0; v < 4; v++) {
            avg_r += vert_colors[fi[v]].r;
            avg_g += vert_colors[fi[v]].g;
            avg_b += vert_colors[fi[v]].b;
        }
        SDL_FColor col = {
            (avg_r / 4) / 255.0f,
            (avg_g / 4) / 255.0f,
            (avg_b / 4) / 255.0f,
            face_alpha / 255.0f
        };

        
        SDL_Vertex sdl_verts[4];
        for (int v = 0; v < 4; v++) {
            sdl_verts[v].position = {screen[fi[v]].x, screen[fi[v]].y};
            sdl_verts[v].color = col;
        }
        int indices[6] = {0, 1, 2, 0, 2, 3};
        SDL_RenderGeometry(r, nullptr, sdl_verts, 4, indices, 6);
    }
}


static void draw_faces_2d(SDL_Renderer* r, const Vec3 verts[],
                           float vp_x, float vp_y, float vp_w, float vp_h,
                           float coord_min, float coord_max, uint8_t face_alpha) {
    float range = coord_max - coord_min;

    auto to_screen = [&](Vec3 v) -> Vec3 {
        float sx = vp_x + ((v.x - coord_min) / range) * vp_w;
        float sy = vp_y + (1.0f - (v.y - coord_min) / range) * vp_h;
        return {sx, sy, v.z};
    };

    
    struct FaceSort { int idx; float avg_z; };
    FaceSort sorted[6];
    for (int f = 0; f < 6; f++) {
        sorted[f].idx = f;
        float z_sum = 0;
        for (int v = 0; v < 4; v++) z_sum += verts[cube_faces[f][v]].z;
        sorted[f].avg_z = z_sum / 4.0f;
    }
    std::sort(sorted, sorted + 6, [](const FaceSort& a, const FaceSort& b) {
        return a.avg_z > b.avg_z;
    });

    for (int si = 0; si < 6; si++) {
        int f = sorted[si].idx;
        const int* fi = cube_faces[f];

        Vec3 s[4];
        for (int v = 0; v < 4; v++) s[v] = to_screen(verts[fi[v]]);

        
        float cross_z = (s[1].x - s[0].x) * (s[2].y - s[0].y) - (s[1].y - s[0].y) * (s[2].x - s[0].x);
        if (cross_z > 0) continue;

        int avg_r = 0, avg_g = 0, avg_b = 0;
        for (int v = 0; v < 4; v++) {
            avg_r += vert_colors[fi[v]].r;
            avg_g += vert_colors[fi[v]].g;
            avg_b += vert_colors[fi[v]].b;
        }
        SDL_FColor col = {
            (avg_r / 4) / 255.0f,
            (avg_g / 4) / 255.0f,
            (avg_b / 4) / 255.0f,
            face_alpha / 255.0f
        };

        SDL_Vertex sdl_verts[4];
        for (int v = 0; v < 4; v++) {
            sdl_verts[v].position = {s[v].x, s[v].y};
            sdl_verts[v].color = col;
        }
        int indices[6] = {0, 1, 2, 0, 2, 3};
        SDL_RenderGeometry(r, nullptr, sdl_verts, 4, indices, 6);
    }
}


static void draw_faces_screen(SDL_Renderer* r, const Vec3 verts[],
                               float w, float h,
                               float sx, float sy, float sw, float sh,
                               uint8_t face_alpha) {
    
    Vec3 mapped[NUM_CUBE_VERTS];
    for (int i = 0; i < NUM_CUBE_VERTS; i++) {
        mapped[i] = {
            sx + (verts[i].x / w) * sw,
            sy + (verts[i].y / h) * sh,
            verts[i].z
        };
    }

    struct FaceSort { int idx; float avg_z; };
    FaceSort sorted[6];
    for (int f = 0; f < 6; f++) {
        sorted[f].idx = f;
        float z_sum = 0;
        for (int v = 0; v < 4; v++) z_sum += verts[cube_faces[f][v]].z;
        sorted[f].avg_z = z_sum / 4.0f;
    }
    std::sort(sorted, sorted + 6, [](const FaceSort& a, const FaceSort& b) {
        return a.avg_z > b.avg_z;
    });

    for (int si = 0; si < 6; si++) {
        int f = sorted[si].idx;
        const int* fi = cube_faces[f];

        
        float cross_z = (mapped[fi[1]].x - mapped[fi[0]].x) * (mapped[fi[2]].y - mapped[fi[0]].y)
                       - (mapped[fi[1]].y - mapped[fi[0]].y) * (mapped[fi[2]].x - mapped[fi[0]].x);
        if (cross_z > 0) continue;

        int avg_r = 0, avg_g = 0, avg_b = 0;
        for (int v = 0; v < 4; v++) {
            avg_r += vert_colors[fi[v]].r;
            avg_g += vert_colors[fi[v]].g;
            avg_b += vert_colors[fi[v]].b;
        }
        SDL_FColor col = {
            (avg_r / 4) / 255.0f,
            (avg_g / 4) / 255.0f,
            (avg_b / 4) / 255.0f,
            face_alpha / 255.0f
        };

        SDL_Vertex sdl_verts[4];
        for (int v = 0; v < 4; v++) {
            sdl_verts[v].position = {mapped[fi[v]].x, mapped[fi[v]].y};
            sdl_verts[v].color = col;
        }
        int indices[6] = {0, 1, 2, 0, 2, 3};
        SDL_RenderGeometry(r, nullptr, sdl_verts, 4, indices, 6);
    }
}






static void draw_tri_wireframe_3d(SDL_Renderer* r, const Vec3 verts[],
                                   float pitch, float yaw, float dist,
                                   float vp_x, float vp_y, float vp_w, float vp_h) {
    for (int i = 0; i < NUM_TRI_EDGES; i++) {
        int a = tri_edges[i][0], b = tri_edges[i][1];
        uint8_t cr = (uint8_t)((tri_colors[a].r + tri_colors[b].r) / 2);
        uint8_t cg = (uint8_t)((tri_colors[a].g + tri_colors[b].g) / 2);
        uint8_t cb = (uint8_t)((tri_colors[a].b + tri_colors[b].b) / 2);
        SDL_SetRenderDrawColor(r, cr, cg, cb, 220);
        draw_line_3d(r, verts[a], verts[b], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    }
    for (int i = 0; i < NUM_TRI_VERTS; i++) {
        Vec3 sp = project_to_viewport(verts[i], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
        if (sp.x < -5000) continue;
        SDL_SetRenderDrawColor(r, tri_colors[i].r, tri_colors[i].g, tri_colors[i].b, 255);
        SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6};
        SDL_RenderFillRect(r, &dot);
    }
}


static void draw_tri_face_3d(SDL_Renderer* r, const Vec3 verts[],
                              float pitch, float yaw, float dist,
                              float vp_x, float vp_y, float vp_w, float vp_h,
                              uint8_t face_alpha) {
    Vec3 screen[3];
    for (int i = 0; i < 3; i++)
        screen[i] = project_to_viewport(verts[i], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    if (screen[0].x < -5000 || screen[1].x < -5000 || screen[2].x < -5000) return;

    SDL_FColor col = {0.85f, 0.9f, 1.0f, face_alpha / 255.0f};
    SDL_Vertex sdl_verts[3];
    for (int i = 0; i < 3; i++) {
        sdl_verts[i].position = {screen[i].x, screen[i].y};
        sdl_verts[i].color = col;
    }
    int indices[3] = {0, 1, 2};
    SDL_RenderGeometry(r, nullptr, sdl_verts, 3, indices, 3);
}


static void draw_tri_wireframe_2d(SDL_Renderer* r, const Vec3 verts[],
                                   float vp_x, float vp_y, float vp_w, float vp_h,
                                   float coord_min, float coord_max) {
    float range = coord_max - coord_min;
    auto to_screen = [&](Vec3 v) -> Vec3 {
        float sx = vp_x + ((v.x - coord_min) / range) * vp_w;
        float sy = vp_y + (1.0f - (v.y - coord_min) / range) * vp_h;
        return {sx, sy, v.z};
    };

    for (int i = 0; i < NUM_TRI_EDGES; i++) {
        int a = tri_edges[i][0], b = tri_edges[i][1];
        uint8_t cr = (uint8_t)((tri_colors[a].r + tri_colors[b].r) / 2);
        uint8_t cg = (uint8_t)((tri_colors[a].g + tri_colors[b].g) / 2);
        uint8_t cb = (uint8_t)((tri_colors[a].b + tri_colors[b].b) / 2);
        SDL_SetRenderDrawColor(r, cr, cg, cb, 220);
        Vec3 sa = to_screen(verts[a]), sb = to_screen(verts[b]);
        SDL_RenderLine(r, sa.x, sa.y, sb.x, sb.y);
    }
    for (int i = 0; i < NUM_TRI_VERTS; i++) {
        Vec3 sp = to_screen(verts[i]);
        SDL_SetRenderDrawColor(r, tri_colors[i].r, tri_colors[i].g, tri_colors[i].b, 255);
        SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6};
        SDL_RenderFillRect(r, &dot);
    }
}


static void draw_tri_face_2d(SDL_Renderer* r, const Vec3 verts[],
                              float vp_x, float vp_y, float vp_w, float vp_h,
                              float coord_min, float coord_max, uint8_t face_alpha) {
    float range = coord_max - coord_min;
    SDL_FColor col = {0.85f, 0.9f, 1.0f, face_alpha / 255.0f};
    SDL_Vertex sdl_verts[3];
    for (int i = 0; i < 3; i++) {
        float sx = vp_x + ((verts[i].x - coord_min) / range) * vp_w;
        float sy = vp_y + (1.0f - (verts[i].y - coord_min) / range) * vp_h;
        sdl_verts[i].position = {sx, sy};
        sdl_verts[i].color = col;
    }
    int indices[3] = {0, 1, 2};
    SDL_RenderGeometry(r, nullptr, sdl_verts, 3, indices, 3);
}


static void draw_text(SDL_Renderer* r, float x, float y, const char* text, float s = 1.0f);






static bool is_inside_clip_volume(Vec4 clip) {
    return clip.w > 0 &&
           clip.x >= -clip.w && clip.x <= clip.w &&
           clip.y >= -clip.w && clip.y <= clip.w &&
           clip.z >= -clip.w && clip.z <= clip.w;
}


// ============================================================================
// Sutherland-Hodgman clipping
// ============================================================================

// Test if a clip-space point is inside a specific clip plane
// Planes: 0=+X (x<=w), 1=-X (x>=-w), 2=+Y, 3=-Y, 4=+Z, 5=-Z
static bool inside_clip_plane(Vec4 v, int plane) {
    switch (plane) {
        case 0: return v.x <=  v.w;
        case 1: return v.x >= -v.w;
        case 2: return v.y <=  v.w;
        case 3: return v.y >= -v.w;
        case 4: return v.z <=  v.w;
        case 5: return v.z >= -v.w;
        default: return true;
    }
}

// Compute intersection of edge (a→b) with a clip plane
static Vec4 clip_intersect(Vec4 a, Vec4 b, int plane) {
    // Compute signed distances to the plane
    float da, db;
    switch (plane) {
        case 0: da = a.w - a.x; db = b.w - b.x; break;
        case 1: da = a.w + a.x; db = b.w + b.x; break;
        case 2: da = a.w - a.y; db = b.w - b.y; break;
        case 3: da = a.w + a.y; db = b.w + b.y; break;
        case 4: da = a.w - a.z; db = b.w - b.z; break;
        case 5: da = a.w + a.z; db = b.w + b.z; break;
        default: da = 1; db = 1; break;
    }
    float t = da / (da - db);
    return {
        a.x + (b.x - a.x) * t,
        a.y + (b.y - a.y) * t,
        a.z + (b.z - a.z) * t,
        a.w + (b.w - a.w) * t,
    };
}

// Clip a polygon against all 6 frustum planes
static void sutherland_hodgman(const Vec4* in_verts, int in_count,
                                Vec4* out_verts, int& out_count) {
    constexpr int MAX = PipelineState::ClippedFace::MAX_VERTS;
    Vec4 buf_a[MAX], buf_b[MAX];
    int count_a = std::min(in_count, MAX);
    for (int i = 0; i < count_a; i++) buf_a[i] = in_verts[i];

    Vec4* src = buf_a;
    Vec4* dst = buf_b;
    int src_count = count_a;

    for (int plane = 0; plane < 6; plane++) {
        int dst_count = 0;
        if (src_count == 0) break;

        for (int i = 0; i < src_count; i++) {
            Vec4 cur = src[i];
            Vec4 nxt = src[(i + 1) % src_count];
            bool cur_in = inside_clip_plane(cur, plane);
            bool nxt_in = inside_clip_plane(nxt, plane);

            if (cur_in && nxt_in) {
                if (dst_count < MAX) dst[dst_count++] = nxt;
            } else if (cur_in && !nxt_in) {
                if (dst_count < MAX) dst[dst_count++] = clip_intersect(cur, nxt, plane);
            } else if (!cur_in && nxt_in) {
                if (dst_count < MAX) dst[dst_count++] = clip_intersect(cur, nxt, plane);
                if (dst_count < MAX) dst[dst_count++] = nxt;
            }
            // both outside: skip
        }

        // Swap buffers
        std::swap(src, dst);
        src_count = dst_count;
    }

    out_count = src_count;
    for (int i = 0; i < out_count; i++) out_verts[i] = src[i];
}

static void compute_clipped_geometry(PipelineState& ps) {
    for (int f = 0; f < 6; f++) {
        const int* fi = cube_faces[f];
        Vec4 face_verts[4];
        for (int v = 0; v < 4; v++) face_verts[v] = ps.verts_clip[fi[v]];

        // Check if any vertex is outside
        bool any_outside = false;
        for (int v = 0; v < 4; v++) {
            if (!is_inside_clip_volume(face_verts[v])) { any_outside = true; break; }
        }

        sutherland_hodgman(face_verts, 4, ps.clipped_faces[f].verts, ps.clipped_faces[f].count);
        ps.clipped_faces[f].was_clipped = any_outside;
    }
}

// Draw clipped geometry in clip space view
static void draw_clipped_geometry_3d(SDL_Renderer* r, const PipelineState& ps,
                                      float pitch, float yaw, float dist,
                                      float vp_x, float vp_y, float vp_w, float vp_h) {
    for (int f = 0; f < 6; f++) {
        const auto& cf = ps.clipped_faces[f];
        if (cf.count < 2) continue;
        if (!cf.was_clipped) continue; // only draw clipped faces

        // Draw clipped polygon edges in bright yellow
        SDL_SetRenderDrawColor(r, 255, 220, 50, 255);
        for (int i = 0; i < cf.count; i++) {
            Vec3 a = cf.verts[i].xyz();
            Vec3 b = cf.verts[(i + 1) % cf.count].xyz();
            draw_line_3d(r, a, b, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
        }

        // Draw intersection vertices as diamonds
        for (int i = 0; i < cf.count; i++) {
            Vec3 sp = project_to_viewport(cf.verts[i].xyz(), pitch, yaw, dist,
                                           vp_x, vp_y, vp_w, vp_h);
            if (sp.x < -5000) continue;

            // Check if this is a new vertex (not one of the original face verts)
            bool is_original = false;
            const int* fi = cube_faces[f];
            for (int v = 0; v < 4; v++) {
                Vec4 orig = ps.verts_clip[fi[v]];
                float dx = cf.verts[i].x - orig.x;
                float dy = cf.verts[i].y - orig.y;
                float dz = cf.verts[i].z - orig.z;
                if (dx*dx + dy*dy + dz*dz < 0.001f) { is_original = true; break; }
            }

            if (!is_original) {
                // Diamond marker for clipped vertices
                SDL_SetRenderDrawColor(r, 255, 255, 50, 255);
                float s = 5;
                SDL_RenderLine(r, sp.x, sp.y - s, sp.x + s, sp.y);
                SDL_RenderLine(r, sp.x + s, sp.y, sp.x, sp.y + s);
                SDL_RenderLine(r, sp.x, sp.y + s, sp.x - s, sp.y);
                SDL_RenderLine(r, sp.x - s, sp.y, sp.x, sp.y - s);
            }
        }
    }
}

static void draw_clip_status_3d(SDL_Renderer* r, const PipelineState& ps,
                                 float pitch, float yaw, float dist,
                                 float vp_x, float vp_y, float vp_w, float vp_h) {
    for (int i = 0; i < NUM_CUBE_VERTS; i++) {
        Vec3 sp = project_to_viewport(ps.display_verts[i], pitch, yaw, dist,
                                       vp_x, vp_y, vp_w, vp_h);
        if (sp.x < -5000) continue;

        bool inside = is_inside_clip_volume(ps.verts_clip[i]);
        if (inside) {
            SDL_SetRenderDrawColor(r, 60, 255, 60, 180);
        } else {
            SDL_SetRenderDrawColor(r, 255, 60, 60, 220);
        }
        
        float rad = 8.0f;
        int segs = 16;
        for (int s = 0; s < segs; s++) {
            float a1 = (float)s / segs * 2.0f * PI;
            float a2 = (float)(s + 1) / segs * 2.0f * PI;
            SDL_RenderLine(r, sp.x + std::cos(a1) * rad, sp.y + std::sin(a1) * rad,
                              sp.x + std::cos(a2) * rad, sp.y + std::sin(a2) * rad);
        }
        
        draw_text(r, sp.x + 10, sp.y - 10, inside ? "IN" : "OUT");
    }

    
    if (ps.show_triangle) {
        for (int i = 0; i < NUM_TRI_VERTS; i++) {
            Vec3 sp = project_to_viewport(ps.tri_display[i], pitch, yaw, dist,
                                           vp_x, vp_y, vp_w, vp_h);
            if (sp.x < -5000) continue;

            bool inside = is_inside_clip_volume(ps.tri_clip[i]);
            SDL_SetRenderDrawColor(r, inside ? 60 : 255, inside ? 255 : 60, 60, 180);
            float rad = 8.0f;
            int segs = 16;
            for (int s = 0; s < segs; s++) {
                float a1 = (float)s / segs * 2.0f * PI;
                float a2 = (float)(s + 1) / segs * 2.0f * PI;
                SDL_RenderLine(r, sp.x + std::cos(a1) * rad, sp.y + std::sin(a1) * rad,
                                  sp.x + std::cos(a2) * rad, sp.y + std::sin(a2) * rad);
            }
        }
    }
}


static void draw_near_plane_3d(SDL_Renderer* r, const PipelineState& ps,
                                float pitch, float yaw, float dist,
                                float vp_x, float vp_y, float vp_w, float vp_h) {
    
    
    
    
    Vec3 cam_eye = {ps.pipe_cam_x, ps.pipe_cam_y, ps.pipe_cam_z};
    Vec3 cam_target = {0, 0, 0};
    Vec3 forward = (cam_target - cam_eye).normalized();
    Vec3 right_v = forward.cross({0, 1, 0}).normalized();
    Vec3 up_v = right_v.cross(forward);

    float near = ps.pipe_cam_near;
    float half_h = near * std::tan(ps.pipe_cam_fov * 0.5f);
    float aspect = ps.proj_matrix.m[1][1] / ps.proj_matrix.m[0][0]; // extract from live projection
    float half_w = half_h * aspect;

    Vec3 center = cam_eye + forward * near;
    Vec3 corners[4] = {
        center + up_v * half_h - right_v * half_w,
        center + up_v * half_h + right_v * half_w,
        center - up_v * half_h + right_v * half_w,
        center - up_v * half_h - right_v * half_w,
    };

    
    Vec3 sc[4];
    for (int i = 0; i < 4; i++) {
        sc[i] = project_to_viewport(corners[i], pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
        if (sc[i].x < -5000) return;
    }

    
    SDL_FColor col = {0.8f, 0.2f, 0.2f, 0.12f};
    SDL_Vertex verts[4];
    for (int i = 0; i < 4; i++) {
        verts[i].position = {sc[i].x, sc[i].y};
        verts[i].color = col;
    }
    int indices[6] = {0, 1, 2, 0, 2, 3};
    SDL_RenderGeometry(r, nullptr, verts, 4, indices, 6);

    
    SDL_SetRenderDrawColor(r, 255, 80, 80, 120);
    for (int i = 0; i < 4; i++) {
        int j = (i + 1) % 4;
        SDL_RenderLine(r, sc[i].x, sc[i].y, sc[j].x, sc[j].y);
    }

    
    Vec3 label_pos = project_to_viewport(center + up_v * (half_h + 0.3f),
                                          pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    if (label_pos.x > -5000) {
        SDL_SetRenderDrawColor(r, 255, 100, 100, 200);
        draw_text(r, label_pos.x - 28, label_pos.y, "NEAR PLANE");
    }
}


static bool point_in_tri(float px, float py,
                          float x0, float y0, float x1, float y1, float x2, float y2) {
    float d1 = (px - x1) * (y0 - y1) - (x0 - x1) * (py - y1);
    float d2 = (px - x2) * (y1 - y2) - (x1 - x2) * (py - y2);
    float d3 = (px - x0) * (y2 - y0) - (x2 - x0) * (py - y0);
    bool has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
    bool has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
    return !(has_neg && has_pos);
}


static void draw_raster_grid(SDL_Renderer* r, const PipelineState& ps,
                              float win_w, float win_h,
                              float sx, float sy, float sw, float sh) {
    constexpr int GRID_X = 40;
    constexpr int GRID_Y = 28;
    float cell_w = sw / GRID_X;
    float cell_h = sh / GRID_Y;

    
    
    struct Tri2D { float x[3], y[3], z[3]; uint8_t cr, cg, cb; };
    Tri2D tris[12 + 1 + 2]; // cube faces + triangle + plane
    int num_tris = 0;

    for (int f = 0; f < 6; f++) {
        const int* fi = cube_faces[f];
        float fx[4], fy[4], fz[4];
        for (int v = 0; v < 4; v++) {
            fx[v] = ps.verts_screen[fi[v]].x;
            fy[v] = ps.verts_screen[fi[v]].y;
            fz[v] = ps.verts_screen[fi[v]].z;
        }
        int avg_r = 0, avg_g = 0, avg_b = 0;
        for (int v = 0; v < 4; v++) {
            avg_r += vert_colors[fi[v]].r;
            avg_g += vert_colors[fi[v]].g;
            avg_b += vert_colors[fi[v]].b;
        }
        uint8_t cr = (uint8_t)(avg_r / 4);
        uint8_t cg = (uint8_t)(avg_g / 4);
        uint8_t cb = (uint8_t)(avg_b / 4);
        tris[num_tris++] = {{fx[0], fx[1], fx[2]}, {fy[0], fy[1], fy[2]}, {fz[0], fz[1], fz[2]}, cr, cg, cb};
        tris[num_tris++] = {{fx[0], fx[2], fx[3]}, {fy[0], fy[2], fy[3]}, {fz[0], fz[2], fz[3]}, cr, cg, cb};
    }

    if (ps.show_triangle) {
        tris[num_tris++] = {
            {ps.tri_screen[0].x, ps.tri_screen[1].x, ps.tri_screen[2].x},
            {ps.tri_screen[0].y, ps.tri_screen[1].y, ps.tri_screen[2].y},
            {ps.tri_screen[0].z, ps.tri_screen[1].z, ps.tri_screen[2].z},
            200, 220, 255
        };
    }

    // Z-fighting plane (2 triangles for the quad)
    if (ps.show_zfight_plane) {
        Vec3 ps0 = ps.plane_screen[0], ps1 = ps.plane_screen[1];
        Vec3 ps2 = ps.plane_screen[2], ps3 = ps.plane_screen[3];
        tris[num_tris++] = {
            {ps0.x, ps1.x, ps2.x}, {ps0.y, ps1.y, ps2.y}, {ps0.z, ps1.z, ps2.z},
            255, 180, 60
        };
        tris[num_tris++] = {
            {ps0.x, ps2.x, ps3.x}, {ps0.y, ps2.y, ps3.y}, {ps0.z, ps2.z, ps3.z},
            255, 180, 60
        };
    }

    // Z-fighting epsilon: proportional to depth range
    float zfight_eps = 0.005f / std::max(ps.pipe_cam_far - ps.pipe_cam_near, 0.1f);

    for (int gy = 0; gy < GRID_Y; gy++) {
        for (int gx = 0; gx < GRID_X; gx++) {
            float px = (gx + 0.5f) / GRID_X * win_w;
            float py = (gy + 0.5f) / GRID_Y * win_h;

            uint8_t hit_r = 0, hit_g = 0, hit_b = 0;
            bool hit = false;
            float hit_z = 1e30f;
            float second_z = 1e30f; // for Z-fighting detection
            for (int t = 0; t < num_tris; t++) {
                if (point_in_tri(px, py,
                                 tris[t].x[0], tris[t].y[0],
                                 tris[t].x[1], tris[t].y[1],
                                 tris[t].x[2], tris[t].y[2])) {
                    float dx0 = tris[t].x[1] - tris[t].x[0], dy0 = tris[t].y[1] - tris[t].y[0];
                    float dx1 = tris[t].x[2] - tris[t].x[0], dy1 = tris[t].y[2] - tris[t].y[0];
                    float dpx = px - tris[t].x[0], dpy = py - tris[t].y[0];
                    float denom = dx0 * dy1 - dx1 * dy0;
                    if (std::fabs(denom) < 1e-6f) continue;
                    float u = (dpx * dy1 - dx1 * dpy) / denom;
                    float v = (dx0 * dpy - dpx * dy0) / denom;
                    float z = tris[t].z[0] + u * (tris[t].z[1] - tris[t].z[0]) + v * (tris[t].z[2] - tris[t].z[0]);

                    if (z < hit_z) {
                        second_z = hit_z;
                        hit_z = z;
                        hit_r = tris[t].cr;
                        hit_g = tris[t].cg;
                        hit_b = tris[t].cb;
                        hit = true;
                    } else if (z < second_z) {
                        second_z = z;
                    }
                }
            }

            float cx = sx + gx * cell_w;
            float cy = sy + gy * cell_h;

            if (hit) {
                // Z-fighting: two surfaces nearly same depth
                bool zfighting = (second_z < 1e29f) && (std::fabs(hit_z - second_z) < zfight_eps);

                if (zfighting) {
                    // Alternating red/yellow checkerboard to show the fight
                    if ((gx + gy) % 2 == 0) {
                        SDL_SetRenderDrawColor(r, 255, 60, 60, 80);
                    } else {
                        SDL_SetRenderDrawColor(r, 255, 255, 60, 80);
                    }
                } else if (ps.show_depth_viz) {
                    // Depth grayscale: closer = brighter
                    int g = 255 - (int)std::clamp((hit_z * 0.5f + 0.5f) * 255.0f, 0.0f, 255.0f);
                    SDL_SetRenderDrawColor(r, (uint8_t)g, (uint8_t)g, (uint8_t)g, 70);
                } else {
                    SDL_SetRenderDrawColor(r, hit_r, hit_g, hit_b, 50);
                }
                SDL_FRect cell = {cx + 0.5f, cy + 0.5f, cell_w - 1.0f, cell_h - 1.0f};
                SDL_RenderFillRect(r, &cell);
            }
        }
    }

    
    SDL_SetRenderDrawColor(r, 60, 60, 80, 40);
    for (int gx = 0; gx <= GRID_X; gx++) {
        float lx = sx + gx * cell_w;
        SDL_RenderLine(r, lx, sy, lx, sy + sh);
    }
    for (int gy = 0; gy <= GRID_Y; gy++) {
        float ly = sy + gy * cell_h;
        SDL_RenderLine(r, sx, ly, sx + sw, ly);
    }
}






static void draw_text(SDL_Renderer* r, float x, float y, const char* text, float s) {
    SDL_SetRenderScale(r, s, s);
    SDL_RenderDebugText(r, x / s, y / s, text);
    SDL_SetRenderScale(r, 1.0f, 1.0f);
}


static void draw_matrix(SDL_Renderer* r, const Mat4& mat, float x, float y,
                         const char* label, bool breakdown) {
    SDL_SetRenderDrawColor(r, 140, 140, 160, 255);
    draw_text(r, x, y, label);

    if (!breakdown) {
        // Original uniform color rendering
        SDL_SetRenderDrawColor(r, 100, 110, 130, 255);
        char buf[80];
        for (int row = 0; row < 4; row++) {
            std::snprintf(buf, sizeof(buf), "[%6.2f %6.2f %6.2f %6.2f]",
                          mat.m[row][0], mat.m[row][1], mat.m[row][2], mat.m[row][3]);
            draw_text(r, x, y + 16 + row * 12, buf);
        }
        return;
    }

    // Color-coded breakdown: render each cell individually
    float cell_w = 56; // width per column (~7 chars * 8px)
    float row_y = y + 16;

    for (int row = 0; row < 4; row++) {
        // Opening bracket
        SDL_SetRenderDrawColor(r, 70, 70, 85, 255);
        draw_text(r, x, row_y, "[");

        for (int col = 0; col < 4; col++) {
            // Pick color by matrix region
            if (row == 3) {
                SDL_SetRenderDrawColor(r, 255, 100, 180, 255); // pink: projection/W row
            } else if (col == 3) {
                SDL_SetRenderDrawColor(r, 80, 255, 120, 255);  // green: translation
            } else if (row == col) {
                SDL_SetRenderDrawColor(r, 255, 200, 80, 255);  // yellow: scale (diagonal)
            } else {
                SDL_SetRenderDrawColor(r, 100, 180, 255, 255); // blue: rotation
            }

            char val[16];
            std::snprintf(val, sizeof(val), "%6.2f", mat.m[row][col]);
            draw_text(r, x + 8 + col * cell_w, row_y, val);
        }

        // Closing bracket
        SDL_SetRenderDrawColor(r, 70, 70, 85, 255);
        draw_text(r, x + 8 + 4 * cell_w, row_y, "]");

        row_y += 12;
    }

    // Legend below the matrix
    float ly = row_y + 6;
    auto draw_swatch = [&](float lx, float ly, uint8_t cr, uint8_t cg, uint8_t cb, const char* lbl) {
        SDL_SetRenderDrawColor(r, cr, cg, cb, 255);
        SDL_FRect sw = {lx, ly + 2, 6, 6};
        SDL_RenderFillRect(r, &sw);
        SDL_SetRenderDrawColor(r, 120, 120, 140, 255);
        draw_text(r, lx + 10, ly, lbl);
    };
    draw_swatch(x,       ly, 100, 180, 255, "Rotation");
    draw_swatch(x + 80,  ly, 255, 200,  80, "Scale");
    draw_swatch(x,       ly + 14, 80, 255, 120, "Translation");
    draw_swatch(x + 80,  ly + 14, 255, 100, 180, "Proj/W");

    SDL_SetRenderDrawColor(r, 70, 70, 90, 255);
    draw_text(r, x, ly + 30, "(M: toggle)");
}


static void draw_frustum_3d(SDL_Renderer* r,
                             Vec3 cam_eye, Vec3 cam_target,
                             float fov, float aspect, float near_dist, float far_dist,
                             float pitch, float yaw, float dist,
                             float vp_x, float vp_y, float vp_w, float vp_h) {
    
    Vec3 forward = (cam_target - cam_eye).normalized();
    Vec3 right = forward.cross({0, 1, 0}).normalized();
    Vec3 up = right.cross(forward);

    
    float near_h = near_dist * std::tan(fov * 0.5f);
    float near_w = near_h * aspect;
    
    float vis_far = std::min(far_dist, 15.0f);
    float vis_far_h = vis_far * std::tan(fov * 0.5f);
    float vis_far_w = vis_far_h * aspect;

    
    Vec3 nc = cam_eye + forward * near_dist;
    Vec3 fc = cam_eye + forward * vis_far;

    
    Vec3 ntl = nc + up * near_h - right * near_w;
    Vec3 ntr = nc + up * near_h + right * near_w;
    Vec3 nbr = nc - up * near_h + right * near_w;
    Vec3 nbl = nc - up * near_h - right * near_w;

    Vec3 ftl = fc + up * vis_far_h - right * vis_far_w;
    Vec3 ftr = fc + up * vis_far_h + right * vis_far_w;
    Vec3 fbr = fc - up * vis_far_h + right * vis_far_w;
    Vec3 fbl = fc - up * vis_far_h - right * vis_far_w;

    
    SDL_SetRenderDrawColor(r, 255, 255, 100, 80);

    
    draw_line_3d(r, ntl, ntr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, ntr, nbr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, nbr, nbl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, nbl, ntl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);

    
    draw_line_3d(r, ftl, ftr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, ftr, fbr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, fbr, fbl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, fbl, ftl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);

    
    SDL_SetRenderDrawColor(r, 255, 255, 100, 50);
    draw_line_3d(r, ntl, ftl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, ntr, ftr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, nbr, fbr, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
    draw_line_3d(r, nbl, fbl, pitch, yaw, dist, vp_x, vp_y, vp_w, vp_h);
}


static void draw_info_panel(SDL_Renderer* r, const PipelineState& ps, int stage,
                             float x, float y) {
    char buf[128];
    float line_h = 14.0f;
    float ix = x + 8;  // inner content x
    float cy = y;

    Vec3 v_obj = ps.verts_object[0].xyz();
    Vec3 v_world = ps.verts_world[0].xyz();
    Vec3 v_view = ps.verts_view[0].xyz();
    Vec4 v_clip4 = ps.verts_clip[0];
    Vec3 v_ndc = ps.verts_ndc[0];
    Vec3 v_scr = ps.verts_screen[0];
    RGBA vc = vert_colors[0];

    // --- Measure panel height first so we can draw the background ---
    // Rough height per stage: header(20) + formula(14-28) + gap(6) + input(14) + arrow(14) + output(14) + pad(8)
    float panel_h = 0;
    switch (stage) {
        case 0: panel_h = 20 + 14 + 6 + 14 + 10; break;
        case 5: panel_h = 20 + 28 + 6 + 14 + 14 + 14 + 10; break;
        default: panel_h = 20 + 14 + 14 + 6 + 14 + 14 + 14 + 10; break;
    }

    // Background card
    SDL_SetRenderDrawColor(r, 18, 18, 28, 210);
    SDL_FRect bg = {x, y, 280, panel_h};
    SDL_RenderFillRect(r, &bg);
    SDL_SetRenderDrawColor(r, 55, 55, 70, 180);
    SDL_RenderRect(r, &bg);

    // Header: colored dot + title
    SDL_SetRenderDrawColor(r, vc.r, vc.g, vc.b, 255);
    SDL_FRect dot = {ix, cy + 4, 8, 8};
    SDL_RenderFillRect(r, &dot);
    SDL_SetRenderDrawColor(r, 190, 190, 210, 255);
    draw_text(r, ix + 12, cy + 2, "Vertex 0 - Step by Step");
    cy += 20;

    // Helpers
    auto draw_formula = [&](const char* text) {
        SDL_SetRenderDrawColor(r, 170, 200, 255, 255);
        draw_text(r, ix, cy, text);
        cy += line_h;
    };
    auto draw_dim_label = [&](const char* text) {
        SDL_SetRenderDrawColor(r, 110, 120, 140, 255);
        draw_text(r, ix, cy, text);
        cy += line_h;
    };
    auto draw_input = [&](const char* label, const char* val) {
        SDL_SetRenderDrawColor(r, 130, 140, 160, 255);
        draw_text(r, ix + 4, cy, label);
        SDL_SetRenderDrawColor(r, vc.r, vc.g, vc.b, 200);
        draw_text(r, ix + 60, cy, val);
        cy += line_h;
    };
    auto draw_arrow = [&](const char* op) {
        SDL_SetRenderDrawColor(r, 80, 180, 120, 255);
        draw_text(r, ix + 16, cy, op);
        cy += line_h;
    };
    auto draw_output = [&](const char* label, const char* val) {
        SDL_SetRenderDrawColor(r, 200, 210, 230, 255);
        draw_text(r, ix + 4, cy, label);
        SDL_SetRenderDrawColor(r, vc.r, vc.g, vc.b, 255);
        draw_text(r, ix + 60, cy, val);
        cy += line_h;
    };

    switch (stage) {
        case 0:
            if (ps.shader_mode) {
                draw_formula("v = authored + sin(t*f + y*4)*A");
            } else {
                draw_formula("v_local = authored position");
            }
            cy += 6;
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_obj.x, v_obj.y, v_obj.z);
            draw_output("v0 =", buf);
            break;

        case 1:
            draw_formula("v_world = M_model * v_local");
            draw_dim_label("M = Translate * Rotate * Scale");
            cy += 6;
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_obj.x, v_obj.y, v_obj.z);
            draw_input("Input:", buf);
            draw_arrow("* Model Matrix");
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_world.x, v_world.y, v_world.z);
            draw_output("Output:", buf);
            break;

        case 2:
            draw_formula("v_view = M_view * v_world");
            draw_dim_label("M_view = lookAt(eye, target, up)");
            cy += 6;
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_world.x, v_world.y, v_world.z);
            draw_input("Input:", buf);
            draw_arrow("* View Matrix");
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_view.x, v_view.y, v_view.z);
            draw_output("Output:", buf);
            break;

        case 3:
            draw_formula("v_clip = M_proj * v_view");
            draw_dim_label("Perspective projection");
            cy += 6;
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v_view.x, v_view.y, v_view.z);
            draw_input("Input:", buf);
            draw_arrow("* Projection Matrix");
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f, w=%.2f)", v_clip4.x, v_clip4.y, v_clip4.z, v_clip4.w);
            draw_output("Output:", buf);
            break;

        case 4:
            draw_formula("v_ndc = v_clip.xyz / v_clip.w");
            draw_dim_label("Perspective divide");
            cy += 6;
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f, w=%.2f)", v_clip4.x, v_clip4.y, v_clip4.z, v_clip4.w);
            draw_input("Input:", buf);
            std::snprintf(buf, sizeof(buf), "/ w = %.2f", v_clip4.w);
            draw_arrow(buf);
            std::snprintf(buf, sizeof(buf), "(%.3f, %.3f, %.3f)", v_ndc.x, v_ndc.y, v_ndc.z);
            draw_output("Output:", buf);
            break;

        case 5:
            draw_formula("x = (ndc.x*0.5+0.5) * W");
            draw_formula("y = (1-(ndc.y*0.5+0.5)) * H");
            cy += 6;
            std::snprintf(buf, sizeof(buf), "(%.3f, %.3f)", v_ndc.x, v_ndc.y);
            draw_input("Input:", buf);
            draw_arrow("=> viewport transform");
            std::snprintf(buf, sizeof(buf), "(%.0f, %.0f) px", v_scr.x, v_scr.y);
            draw_output("Output:", buf);
            break;
    }
}


static void draw_vertex_highlight(SDL_Renderer* r, float sx, float sy, RGBA col, float time) {
    float pulse = 6.0f + 2.0f * std::sin(time * 4.0f);
    int segments = 20;
    SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 255);
    for (int i = 0; i < segments; i++) {
        float a1 = (float)i / segments * 2.0f * PI;
        float a2 = (float)(i + 1) / segments * 2.0f * PI;
        SDL_RenderLine(r,
            sx + std::cos(a1) * pulse, sy + std::sin(a1) * pulse,
            sx + std::cos(a2) * pulse, sy + std::sin(a2) * pulse);
    }
    
    SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 200);
    SDL_FRect dot = {sx - 4, sy - 4, 8, 8};
    SDL_RenderFillRect(r, &dot);
}


static void draw_vertex_sidebar(SDL_Renderer* r, const PipelineState& ps,
                                 int vi, int current_stage,
                                 float x, float y, float panel_w) {
    RGBA col = vert_colors[vi];
    char buf[96];
    float line_h = 13.0f;
    float cy = y;

    
    SDL_SetRenderDrawColor(r, 20, 20, 30, 200);
    SDL_FRect bg = {x - 4, y - 4, panel_w + 8, 130};
    SDL_RenderFillRect(r, &bg);
    SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 120);
    SDL_RenderRect(r, &bg);

    
    static const char* vert_names[] = {
        "v0 (front-bot-L)", "v1 (front-bot-R)", "v2 (front-top-R)", "v3 (front-top-L)",
        "v4 (back-bot-L)",  "v5 (back-bot-R)",  "v6 (back-top-R)",  "v7 (back-top-L)"
    };
    SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 255);
    draw_text(r, x, cy, vert_names[vi], 1.2f);
    cy += 18;

    
    struct StageEntry {
        const char* label;
        Vec3 v3;
        Vec4 v4;
        bool use_v4;
        bool is_screen;
    };

    StageEntry entries[6] = {
        {"Object", ps.verts_object[vi].xyz(), {}, false, false},
        {"World",  ps.verts_world[vi].xyz(),  {}, false, false},
        {"View",   ps.verts_view[vi].xyz(),   {}, false, false},
        {"Clip",   {},                         ps.verts_clip[vi], true, false},
        {"NDC",    ps.verts_ndc[vi],           {}, false, false},
        {"Screen", ps.verts_screen[vi],        {}, false, true},
    };

    float val_x = x + 56; // align all coordinate values at a fixed column

    for (int s = 0; s < 6; s++) {
        // Highlight current stage row
        if (s == current_stage) {
            SDL_SetRenderDrawColor(r, col.r, col.g, col.b, 40);
            SDL_FRect highlight = {x - 2, cy - 1, panel_w + 4, line_h + 2};
            SDL_RenderFillRect(r, &highlight);
        }

        // Stage label
        RGBA sc = stage_colors[s];
        SDL_SetRenderDrawColor(r, sc.r, sc.g, sc.b, (uint8_t)(s == current_stage ? 255 : 160));
        draw_text(r, x, cy, entries[s].label);

        // Coordinate value on the same line, at fixed column
        SDL_SetRenderDrawColor(r, col.r, col.g, col.b, (uint8_t)(s == current_stage ? 255 : 150));
        if (entries[s].use_v4) {
            Vec4 c = entries[s].v4;
            std::snprintf(buf, sizeof(buf), "(%.1f, %.1f, %.1f, w=%.1f)",
                          c.x, c.y, c.z, c.w);
        } else if (entries[s].is_screen) {
            Vec3 v = entries[s].v3;
            std::snprintf(buf, sizeof(buf), "(%.0f, %.0f)", v.x, v.y);
        } else {
            Vec3 v = entries[s].v3;
            std::snprintf(buf, sizeof(buf), "(%.2f, %.2f, %.2f)", v.x, v.y, v.z);
        }
        draw_text(r, val_x, cy, buf);
        cy += line_h + 4;
    }
}


static void draw_slider_panel(SDL_Renderer* r, const PipelineState& ps, float vp_y) {
    float panel_x = SLIDER_PANEL_X;
    float panel_y = vp_y + SLIDER_PANEL_Y_OFFSET - 20;

    // Count visible sliders for background height
    int visible_count = 0;
    int section_gaps = 1; // Camera section always has a gap
    for (int i = 0; i < SLIDER_COUNT; i++) {
        if (slider_visible(ps, i)) visible_count++;
    }
    if (ps.shader_mode) section_gaps++;
    if (ps.show_zfight_plane) section_gaps++;

    SDL_SetRenderDrawColor(r, 18, 18, 28, 220);
    float bg_h = SLIDER_TOP_PAD + (float)visible_count * SLIDER_SPACING
               + section_gaps * SLIDER_SECTION_GAP + 18;
    SDL_FRect bg = {panel_x - 6, panel_y - 4, SLIDER_W + 112, bg_h};
    SDL_RenderFillRect(r, &bg);
    SDL_SetRenderDrawColor(r, 55, 55, 70, 200);
    SDL_RenderRect(r, &bg);

    
    SDL_SetRenderDrawColor(r, 160, 160, 185, 255);
    draw_text(r, panel_x, panel_y + 2, "Sliders (T: toggle)");

    
    // Section labels positioned with enough clearance
    {
        SDL_FRect first_track = slider_track_rect(vp_y, SLIDER_POS_X);
        SDL_SetRenderDrawColor(r, 100, 100, 125, 180);
        draw_text(r, panel_x - 2, first_track.y - 14, "Object");

        SDL_FRect cam_track = slider_track_rect(vp_y, SLIDER_FOV);
        draw_text(r, panel_x - 2, cam_track.y - 14, "Camera");

        if (ps.shader_mode) {
            SDL_FRect shader_track = slider_track_rect(vp_y, SLIDER_SHADER_AMP);
            draw_text(r, panel_x - 2, shader_track.y - 14, "Shader (V)");
        }

        if (ps.show_zfight_plane) {
            SDL_FRect plane_track = slider_track_rect(vp_y, SLIDER_PLANE_Z);
            draw_text(r, panel_x - 2, plane_track.y - 14, "Depth (X)");
        }
    }

    for (int i = 0; i < SLIDER_COUNT; i++) {
        if (!slider_visible(ps, i)) continue;
        SDL_FRect track = slider_track_rect(vp_y, i);
        float val = slider_get(ps, i);
        float hx = slider_handle_x(track, val, slider_defs[i].min_val, slider_defs[i].max_val);
        float hy = track.y + track.h * 0.5f;

        
        SDL_SetRenderDrawColor(r, 130, 130, 155, 255);
        draw_text(r, panel_x, track.y + 1, slider_defs[i].label);

        
        SDL_SetRenderDrawColor(r, 40, 40, 55, 255);
        SDL_FRect track_bg = {track.x, hy - 2, track.w, 4};
        SDL_RenderFillRect(r, &track_bg);

        
        SDL_SetRenderDrawColor(r, 80, 120, 180, 180);
        SDL_FRect filled = {track.x, hy - 2, hx - track.x, 4};
        SDL_RenderFillRect(r, &filled);

        
        bool active = (ps.dragging_slider == i);
        if (active) {
            SDL_SetRenderDrawColor(r, 120, 180, 255, 255);
        } else {
            SDL_SetRenderDrawColor(r, 180, 180, 200, 255);
        }
        SDL_FRect handle = {hx - SLIDER_HANDLE_W * 0.5f, hy - SLIDER_HANDLE_H * 0.5f,
                            SLIDER_HANDLE_W, SLIDER_HANDLE_H};
        SDL_RenderFillRect(r, &handle);

        
        char buf[32];
        if (slider_defs[i].max_val >= 100) {
            std::snprintf(buf, sizeof(buf), "%.0f", val);
        } else if (slider_defs[i].max_val <= 1) {
            std::snprintf(buf, sizeof(buf), "%.2f", val);
        } else {
            std::snprintf(buf, sizeof(buf), "%.1f", val);
        }
        SDL_SetRenderDrawColor(r, 180, 180, 200, 220);
        draw_text(r, track.x + track.w + 6, track.y + 1, buf);
    }
}


// ============================================================================
// First-person camera view (split-screen)
// ============================================================================

// Project a 3D world-space point through the pipeline camera to a viewport
static Vec3 project_first_person(Vec3 p, const Mat4& view, const Mat4& proj,
                                  float vp_x, float vp_y, float vp_w, float vp_h) {
    Vec4 view_pt = view * Vec4{p.x, p.y, p.z, 1.0f};
    Vec4 clip = proj * view_pt;
    if (clip.w <= 0.01f) return {-10000, -10000, 0};
    Vec3 ndc = clip.perspDiv();
    if (ndc.x < -1.5f || ndc.x > 1.5f || ndc.y < -1.5f || ndc.y > 1.5f)
        return {-10000, -10000, 0};
    float sx = vp_x + (ndc.x * 0.5f + 0.5f) * vp_w;
    float sy = vp_y + (1.0f - (ndc.y * 0.5f + 0.5f)) * vp_h;
    return {sx, sy, ndc.z};
}

static void draw_first_person_view(SDL_Renderer* r, const PipelineState& ps,
                                    float area_x, float area_y, float area_w, float area_h) {
    // Letterbox to preserve aspect ratio (use pipeline camera aspect)
    float cam_aspect = 16.0f / 9.0f;
    float area_aspect = area_w / area_h;
    float rv_x, rv_y, rv_w, rv_h;
    if (area_aspect > cam_aspect) {
        // Area is wider than camera - pillarbox
        rv_h = area_h;
        rv_w = area_h * cam_aspect;
        rv_x = area_x + (area_w - rv_w) * 0.5f;
        rv_y = area_y;
    } else {
        // Area is taller than camera - letterbox
        rv_w = area_w;
        rv_h = area_w / cam_aspect;
        rv_x = area_x;
        rv_y = area_y + (area_h - rv_h) * 0.5f;
    }

    // Background
    SDL_SetRenderDrawColor(r, 22, 22, 32, 255);
    SDL_FRect bg = {rv_x, rv_y, rv_w, rv_h};
    SDL_RenderFillRect(r, &bg);

    // Border
    SDL_SetRenderDrawColor(r, 255, 255, 100, 120);
    SDL_RenderRect(r, &bg);

    // Label
    SDL_SetRenderDrawColor(r, 255, 255, 100, 255);
    draw_text(r, rv_x + 4, rv_y + 4, "Camera View (F: toggle)");

    // Use the pipeline camera's projection but with the render viewport's aspect
    float rv_aspect = rv_w / rv_h;
    Mat4 fp_proj = Mat4::perspective(ps.pipe_cam_fov, rv_aspect, ps.pipe_cam_near, ps.pipe_cam_far);
    const Mat4& view = ps.view_matrix;

    // Ground grid
    SDL_SetRenderDrawColor(r, 50, 50, 65, 180);
    float half = 6.0f;
    int divs = 12;
    float step = (half * 2) / divs;
    for (int i = 0; i <= divs; i++) {
        float p = -half + i * step;
        Vec3 a = project_first_person({p, 0, -half}, view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        Vec3 b = project_first_person({p, 0, half}, view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        if (a.x > -5000 && b.x > -5000) SDL_RenderLine(r, a.x, a.y, b.x, b.y);
        a = project_first_person({-half, 0, p}, view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        b = project_first_person({half, 0, p}, view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        if (a.x > -5000 && b.x > -5000) SDL_RenderLine(r, a.x, a.y, b.x, b.y);
    }

    // Axes
    auto draw_fp_axis = [&](Vec3 from, Vec3 to, uint8_t cr, uint8_t cg, uint8_t cb) {
        Vec3 a = project_first_person(from, view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        Vec3 b = project_first_person(to, view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        if (a.x > -5000 && b.x > -5000) {
            SDL_SetRenderDrawColor(r, cr, cg, cb, 200);
            SDL_RenderLine(r, a.x, a.y, b.x, b.y);
        }
    };
    draw_fp_axis({0,0,0}, {2,0,0}, 255, 60, 60);
    draw_fp_axis({0,0,0}, {0,2,0}, 60, 255, 60);
    draw_fp_axis({0,0,0}, {0,0,2}, 60, 60, 255);

    // World-space cube vertices
    Vec3 ws_cube[NUM_CUBE_VERTS];
    for (int i = 0; i < NUM_CUBE_VERTS; i++)
        ws_cube[i] = ps.verts_world[i].xyz();

    // Cube faces
    {
        Vec3 screen[NUM_CUBE_VERTS];
        for (int i = 0; i < NUM_CUBE_VERTS; i++)
            screen[i] = project_first_person(ws_cube[i], view, fp_proj, rv_x, rv_y, rv_w, rv_h);

        struct FaceSort { int idx; float avg_z; };
        FaceSort sorted[6];
        for (int f = 0; f < 6; f++) {
            sorted[f].idx = f;
            float z_sum = 0;
            for (int v = 0; v < 4; v++) z_sum += screen[cube_faces[f][v]].z;
            sorted[f].avg_z = z_sum / 4.0f;
        }
        std::sort(sorted, sorted + 6, [](const FaceSort& a, const FaceSort& b) {
            return a.avg_z > b.avg_z;
        });

        for (int si = 0; si < 6; si++) {
            int f = sorted[si].idx;
            const int* fi = cube_faces[f];
            bool all_visible = true;
            for (int v = 0; v < 4; v++)
                if (screen[fi[v]].x < -5000) { all_visible = false; break; }
            if (!all_visible) continue;

            float cross_z = (screen[fi[1]].x - screen[fi[0]].x) * (screen[fi[2]].y - screen[fi[0]].y)
                          - (screen[fi[1]].y - screen[fi[0]].y) * (screen[fi[2]].x - screen[fi[0]].x);
            if (cross_z > 0) continue;

            int avg_r = 0, avg_g = 0, avg_b = 0;
            for (int v = 0; v < 4; v++) {
                avg_r += vert_colors[fi[v]].r;
                avg_g += vert_colors[fi[v]].g;
                avg_b += vert_colors[fi[v]].b;
            }
            SDL_FColor col = {(avg_r / 4) / 255.0f, (avg_g / 4) / 255.0f,
                              (avg_b / 4) / 255.0f, 0.3f};
            SDL_Vertex sv[4];
            for (int v = 0; v < 4; v++) {
                sv[v].position = {screen[fi[v]].x, screen[fi[v]].y};
                sv[v].color = col;
            }
            int indices[6] = {0, 1, 2, 0, 2, 3};
            SDL_RenderGeometry(r, nullptr, sv, 4, indices, 6);
        }
    }

    // Cube wireframe
    for (int i = 0; i < NUM_CUBE_EDGES; i++) {
        int a = cube_edges[i][0], b = cube_edges[i][1];
        Vec3 sa = project_first_person(ws_cube[a], view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        Vec3 sb = project_first_person(ws_cube[b], view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        if (sa.x < -5000 || sb.x < -5000) continue;
        uint8_t cr = (uint8_t)((vert_colors[a].r + vert_colors[b].r) / 2);
        uint8_t cg = (uint8_t)((vert_colors[a].g + vert_colors[b].g) / 2);
        uint8_t cb = (uint8_t)((vert_colors[a].b + vert_colors[b].b) / 2);
        SDL_SetRenderDrawColor(r, cr, cg, cb, 255);
        SDL_RenderLine(r, sa.x, sa.y, sb.x, sb.y);
    }

    // Cube vertex dots
    for (int i = 0; i < NUM_CUBE_VERTS; i++) {
        Vec3 sp = project_first_person(ws_cube[i], view, fp_proj, rv_x, rv_y, rv_w, rv_h);
        if (sp.x < -5000) continue;
        SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, vert_colors[i].b, 255);
        SDL_FRect dot = {sp.x - 3, sp.y - 3, 6, 6};
        SDL_RenderFillRect(r, &dot);
    }

    // Triangle
    if (ps.show_triangle) {
        Vec3 ws_tri[NUM_TRI_VERTS];
        for (int i = 0; i < NUM_TRI_VERTS; i++)
            ws_tri[i] = ps.tri_world[i].xyz();

        Vec3 ts[3];
        bool all_vis = true;
        for (int i = 0; i < 3; i++) {
            ts[i] = project_first_person(ws_tri[i], view, fp_proj, rv_x, rv_y, rv_w, rv_h);
            if (ts[i].x < -5000) all_vis = false;
        }
        if (all_vis) {
            SDL_FColor tcol = {0.85f, 0.9f, 1.0f, 0.25f};
            SDL_Vertex tri_sv[3];
            for (int i = 0; i < 3; i++) {
                tri_sv[i].position = {ts[i].x, ts[i].y};
                tri_sv[i].color = tcol;
            }
            int ti[3] = {0, 1, 2};
            SDL_RenderGeometry(r, nullptr, tri_sv, 3, ti, 3);

            for (int i = 0; i < NUM_TRI_EDGES; i++) {
                int a = tri_edges[i][0], b = tri_edges[i][1];
                uint8_t cr = (uint8_t)((tri_colors[a].r + tri_colors[b].r) / 2);
                uint8_t cg = (uint8_t)((tri_colors[a].g + tri_colors[b].g) / 2);
                uint8_t cb = (uint8_t)((tri_colors[a].b + tri_colors[b].b) / 2);
                SDL_SetRenderDrawColor(r, cr, cg, cb, 220);
                SDL_RenderLine(r, ts[a].x, ts[a].y, ts[b].x, ts[b].y);
            }
            for (int i = 0; i < NUM_TRI_VERTS; i++) {
                SDL_SetRenderDrawColor(r, tri_colors[i].r, tri_colors[i].g, tri_colors[i].b, 255);
                SDL_FRect dot = {ts[i].x - 3, ts[i].y - 3, 6, 6};
                SDL_RenderFillRect(r, &dot);
            }
        }
    }
}

// ============================================================================
// Stage indicator
// ============================================================================

// ============================================================================
// Help overlay
// ============================================================================

static void draw_help_overlay(SDL_Renderer* r, const PipelineState& ps, float w, float h) {
    // Dark background
    SDL_SetRenderDrawColor(r, 10, 10, 20, 220);
    SDL_FRect bg = {0, 0, w, h};
    SDL_RenderFillRect(r, &bg);

    float cx = w * 0.5f;
    float top = 40;

    // Title
    SDL_SetRenderDrawColor(r, 240, 240, 250, 255);
    draw_text(r, cx - 60, top, "Controls", 2.0f);
    top += 50;

    // Helper: draw a toggle row with on/off indicator
    auto draw_toggle = [&](float x, float y, const char* key, const char* label, bool active) {
        // Dot indicator
        if (active) {
            SDL_SetRenderDrawColor(r, 80, 255, 80, 255);
        } else {
            SDL_SetRenderDrawColor(r, 60, 60, 70, 255);
        }
        SDL_FRect dot = {x, y + 3, 6, 6};
        SDL_RenderFillRect(r, &dot);

        SDL_SetRenderDrawColor(r, 200, 200, 220, 255);
        draw_text(r, x + 12, y, key);
        SDL_SetRenderDrawColor(r, 140, 140, 160, 255);
        draw_text(r, x + 80, y, label);
    };

    auto draw_row = [&](float x, float y, const char* key, const char* label) {
        SDL_SetRenderDrawColor(r, 200, 200, 220, 255);
        draw_text(r, x, y, key);
        SDL_SetRenderDrawColor(r, 140, 140, 160, 255);
        draw_text(r, x + 100, y, label);
    };

    float col1 = cx - 280;
    float col2 = cx + 40;
    float line_h = 18;
    float y = top;

    // Left column: Navigation
    SDL_SetRenderDrawColor(r, 180, 200, 255, 255);
    draw_text(r, col1, y, "Navigation", 1.3f);
    y += 24;
    draw_row(col1, y, "Left/Right", "Change pipeline stage");   y += line_h;
    draw_row(col1, y, "1 - 6",      "Jump to specific stage");  y += line_h;
    draw_row(col1, y, "WASD",       "Move object (XZ plane)");  y += line_h;
    draw_row(col1, y, "Q / E",      "Move object (Y axis)");    y += line_h;
    draw_row(col1, y, "Mouse drag", "Orbit display camera");    y += line_h;
    draw_row(col1, y, "Scroll",     "Zoom in/out");             y += line_h;
    draw_row(col1, y, "Right-click","Select vertex");           y += line_h;
    draw_row(col1, y, "Space",      "Toggle auto-rotation");    y += line_h;
    draw_row(col1, y, "R",          "Reset all transforms");    y += line_h;
    draw_row(col1, y, "P",          "Pause/unpause");           y += line_h;
    draw_row(col1, y, "ESC",        "Quit");

    // Right column: Toggles
    y = top;
    SDL_SetRenderDrawColor(r, 180, 200, 255, 255);
    draw_text(r, col2, y, "Toggles", 1.3f);
    y += 24;
    draw_toggle(col2, y, "T", "Sliders panel",        ps.show_sliders);       y += line_h;
    draw_toggle(col2, y, "G", "Triangle shape",       ps.show_triangle);      y += line_h;
    draw_toggle(col2, y, "F", "Camera split view",    ps.show_first_person);  y += line_h;
    draw_toggle(col2, y, "M", "Matrix breakdown",     ps.show_matrix_breakdown); y += line_h;
    draw_toggle(col2, y, "V", "Vertex shader sim",    ps.shader_mode);        y += line_h;
    draw_toggle(col2, y, "C", "Clipping visualizer",  ps.show_clipping);      y += line_h;
    draw_toggle(col2, y, "Z", "Depth visualization",  ps.show_depth_viz);     y += line_h;
    draw_toggle(col2, y, "X", "Z-fighting plane",     ps.show_zfight_plane);  y += line_h;

    // Footer
    SDL_SetRenderDrawColor(r, 100, 100, 120, 255);
    draw_text(r, cx - 60, h - 50, "Press H to close");
}

static void draw_stage_indicator(SDL_Renderer* r, int current, float w, float h) {
    float box_w = 90;
    float gap = 36;
    float total_w = 6 * box_w + 5 * gap;
    float start_x = (w - total_w) / 2.0f;
    float y = h - 58;

    for (int i = 0; i < 6; i++) {
        float bx = start_x + i * (box_w + gap);
        RGBA sc = stage_colors[i];

        if (i == current) {
            SDL_SetRenderDrawColor(r, sc.r, sc.g, sc.b, 255);
            SDL_FRect box = {bx, y, box_w, 32};
            SDL_RenderFillRect(r, &box);
            SDL_SetRenderDrawColor(r, 10, 10, 15, 255);
        } else {
            SDL_SetRenderDrawColor(r, 40, 40, 50, 255);
            SDL_FRect box = {bx, y, box_w, 32};
            SDL_RenderFillRect(r, &box);
            SDL_SetRenderDrawColor(r, sc.r, sc.g, sc.b, 180);
            SDL_RenderRect(r, &box);
            SDL_SetRenderDrawColor(r, 160, 160, 175, 255);
        }

        float name_len = (float)std::strlen(stage_short_names[i]);
        float tx = bx + (box_w - name_len * 8) / 2.0f;
        draw_text(r, tx, y + 12, stage_short_names[i]);

        
        if (i < 5) {
            SDL_SetRenderDrawColor(r, 70, 70, 85, 255);
            float ax = bx + box_w + 4;
            float ay = y + 16;
            float ae = bx + box_w + gap - 4;
            SDL_RenderLine(r, ax, ay, ae, ay);
            SDL_RenderLine(r, ae - 6, ay - 4, ae, ay);
            SDL_RenderLine(r, ae - 6, ay + 4, ae, ay);
        }
    }
}





void pipeline_handle_event(PipelineState& ps, const SDL_Event& event) {
    switch (event.type) {
        case SDL_EVENT_KEY_DOWN:
            switch (event.key.key) {
                case SDLK_LEFT:  ps.key_left = true; break;
                case SDLK_RIGHT: ps.key_right = true; break;
                case SDLK_SPACE: ps.key_space = true; break;
                case SDLK_R:     ps.key_r = true; break;
                case SDLK_T:     ps.show_sliders = !ps.show_sliders; break;
                case SDLK_G:     ps.show_triangle = !ps.show_triangle; break;
                case SDLK_F:     ps.show_first_person = !ps.show_first_person; break;
                case SDLK_M:     ps.show_matrix_breakdown = !ps.show_matrix_breakdown; break;
                case SDLK_V:     ps.shader_mode = !ps.shader_mode; break;
                case SDLK_H:     ps.show_help = !ps.show_help; break;
                case SDLK_C:     ps.show_clipping = !ps.show_clipping; break;
                case SDLK_Z:     ps.show_depth_viz = !ps.show_depth_viz; break;
                case SDLK_X:     ps.show_zfight_plane = !ps.show_zfight_plane; break;
                case SDLK_1:     ps.key_num[0] = true; break;
                case SDLK_2:     ps.key_num[1] = true; break;
                case SDLK_3:     ps.key_num[2] = true; break;
                case SDLK_4:     ps.key_num[3] = true; break;
                case SDLK_5:     ps.key_num[4] = true; break;
                case SDLK_6:     ps.key_num[5] = true; break;
                default: break;
            }
            break;

        case SDL_EVENT_MOUSE_BUTTON_DOWN:
            if (event.button.button == SDL_BUTTON_RIGHT) {
                ps.pending_click = true;
                ps.click_x = event.button.x;
                ps.click_y = event.button.y;
            }
            break;

        case SDL_EVENT_MOUSE_WHEEL:
            ps.cam_distance -= event.wheel.y * 0.5f;
            ps.cam_distance = std::clamp(ps.cam_distance, 3.0f, 30.0f);
            break;

        default:
            break;
    }
}





void pipeline_update(PipelineState& ps, GameContext& ctx) {
    float dt = ctx.delta_time;

    
    int new_stage = ps.current_stage;
    if (ps.key_left && ps.current_stage > 0) new_stage = ps.current_stage - 1;
    if (ps.key_right && ps.current_stage < 5) new_stage = ps.current_stage + 1;
    if (ps.key_space) ps.auto_rotate = !ps.auto_rotate;
    if (ps.key_r) {
        ps.obj_rot_y = 0;
        ps.obj_pos_x = 2; ps.obj_pos_y = 1; ps.obj_pos_z = -3;
        ps.obj_scale = 1;
        ps.cam_orbit_pitch = 25; ps.cam_orbit_yaw = -30;
        ps.cam_distance = 10;
        ps.auto_rotate = true;
    }
    for (int i = 0; i < 6; i++) {
        if (ps.key_num[i]) new_stage = i;
    }
    
    if (new_stage != ps.current_stage) {
        ps.prev_stage = ps.current_stage;
        ps.current_stage = new_stage;
        ps.transition_t = 0.0f;
        ps.fade_alpha = 1.0f;
    }
    
    ps.key_left = ps.key_right = ps.key_space = ps.key_r = false;
    for (int i = 0; i < 6; i++) ps.key_num[i] = false;

    
    float mx, my;
    auto buttons = SDL_GetMouseState(&mx, &my);
    float vp_y_calc = 92.0f;

    if (buttons & SDL_BUTTON_LMASK) {
        if (!ps.mouse_down) {
            ps.mouse_down = true;
            ps.mouse_start_x = mx;
            ps.mouse_start_y = my;

            
            int hit = slider_hit_test(ps, mx, my, vp_y_calc);
            if (hit >= 0) {
                ps.dragging_slider = hit;
            } else {
                ps.dragging_slider = -1;
                ps.orbit_start_pitch = ps.cam_orbit_pitch;
                ps.orbit_start_yaw = ps.cam_orbit_yaw;
            }
        }

        if (ps.dragging_slider >= 0) {
            
            int si = ps.dragging_slider;
            SDL_FRect track = slider_track_rect(vp_y_calc, si);
            float t = (mx - track.x) / track.w;
            t = std::clamp(t, 0.0f, 1.0f);
            float val = slider_defs[si].min_val + t * (slider_defs[si].max_val - slider_defs[si].min_val);
            slider_set(ps, si, val);
        } else {
            
            ps.cam_orbit_yaw = ps.orbit_start_yaw + (mx - ps.mouse_start_x) * 0.3f;
            ps.cam_orbit_pitch = ps.orbit_start_pitch + (my - ps.mouse_start_y) * 0.3f;
            ps.cam_orbit_pitch = std::clamp(ps.cam_orbit_pitch, -89.0f, 89.0f);
        }
    } else {
        ps.mouse_down = false;
        ps.dragging_slider = -1;
    }

    
    const bool* keys = SDL_GetKeyboardState(nullptr);
    float move_speed = 2.5f * dt;
    if (keys[SDL_SCANCODE_W]) ps.obj_pos_z -= move_speed;
    if (keys[SDL_SCANCODE_S]) ps.obj_pos_z += move_speed;
    if (keys[SDL_SCANCODE_A]) ps.obj_pos_x -= move_speed;
    if (keys[SDL_SCANCODE_D]) ps.obj_pos_x += move_speed;
    if (keys[SDL_SCANCODE_Q]) ps.obj_pos_y -= move_speed;
    if (keys[SDL_SCANCODE_E]) ps.obj_pos_y += move_speed;

    
    if (ps.auto_rotate) {
        ps.obj_rot_y += dt * 0.5f;
        if (ps.obj_rot_y > 2.0f * PI) ps.obj_rot_y -= 2.0f * PI;
    }

    

    
    ps.model_matrix = Mat4::translate(ps.obj_pos_x, ps.obj_pos_y, ps.obj_pos_z)
                    * Mat4::rotateY(ps.obj_rot_y)
                    * Mat4::scale(ps.obj_scale, ps.obj_scale, ps.obj_scale);

    
    Vec3 cam_eye = {ps.pipe_cam_x, ps.pipe_cam_y, ps.pipe_cam_z};
    Vec3 cam_target = {0, 0, 0};
    ps.view_matrix = Mat4::lookAt(cam_eye, cam_target, {0, 1, 0});

    
    // Clamp near < far to prevent division by zero in perspective matrix
    if (ps.pipe_cam_near >= ps.pipe_cam_far) {
        ps.pipe_cam_far = ps.pipe_cam_near + 0.1f;
    }
    float aspect = (float)ctx.window_width / std::max((float)ctx.window_height, 1.0f);
    ps.proj_matrix = Mat4::perspective(ps.pipe_cam_fov, aspect, ps.pipe_cam_near, ps.pipe_cam_far);

    
    // Vertex shader simulation
    if (ps.shader_mode) {
        ps.shader_time += dt;
        if (ps.shader_frequency > 0)
            ps.shader_time = std::fmod(ps.shader_time, 2.0f * PI / ps.shader_frequency);
    }

    for (int i = 0; i < NUM_CUBE_VERTS; i++) {
        Vec3 v = cube_verts[i];
        if (ps.shader_mode) {
            v.x += std::sin(ps.shader_time * ps.shader_frequency + v.y * 4.0f) * ps.shader_amplitude;
        }

        ps.verts_object[i] = {v.x, v.y, v.z, 1.0f};
        ps.verts_world[i]  = ps.model_matrix * ps.verts_object[i];
        ps.verts_view[i]   = ps.view_matrix  * ps.verts_world[i];
        ps.verts_clip[i]   = ps.proj_matrix  * ps.verts_view[i];
        ps.verts_ndc[i]    = ps.verts_clip[i].perspDiv();

        float sw = (float)ctx.window_width;
        float sh = (float)ctx.window_height;
        ps.verts_screen[i] = {
            (ps.verts_ndc[i].x * 0.5f + 0.5f) * sw,
            (1.0f - (ps.verts_ndc[i].y * 0.5f + 0.5f)) * sh,
            ps.verts_ndc[i].z
        };
    }

    for (int i = 0; i < NUM_TRI_VERTS; i++) {
        Vec3 v = tri_verts[i];
        if (ps.shader_mode) {
            v.x += std::sin(ps.shader_time * ps.shader_frequency + v.y * 4.0f) * ps.shader_amplitude;
        }
        ps.tri_object[i] = {v.x, v.y, v.z, 1.0f};
        ps.tri_world[i]  = ps.model_matrix * ps.tri_object[i];
        ps.tri_view[i]   = ps.view_matrix  * ps.tri_world[i];
        ps.tri_clip[i]   = ps.proj_matrix  * ps.tri_view[i];
        ps.tri_ndc[i]    = ps.tri_clip[i].perspDiv();

        float sw = (float)ctx.window_width;
        float sh = (float)ctx.window_height;
        ps.tri_screen[i] = {
            (ps.tri_ndc[i].x * 0.5f + 0.5f) * sw,
            (1.0f - (ps.tri_ndc[i].y * 0.5f + 0.5f)) * sh,
            ps.tri_ndc[i].z
        };
    }

    // Compute clipped geometry for the clip visualizer
    if (ps.show_clipping) {
        compute_clipped_geometry(ps);
    }

    // Z-fighting plane transform
    if (ps.show_zfight_plane) {
        Mat4 plane_model = Mat4::translate(ps.obj_pos_x, ps.obj_pos_y, ps.zfight_plane_z)
                         * Mat4::rotateY(ps.obj_rot_y)
                         * Mat4::scale(1.0f, 1.0f, 1.0f);
        for (int i = 0; i < 4; i++) {
            Vec3 v = plane_verts[i];
            Vec4 obj = {v.x, v.y, v.z, 1.0f};
            ps.plane_world[i] = plane_model * obj;
            Vec4 view = ps.view_matrix * ps.plane_world[i];
            Vec4 clip = ps.proj_matrix * view;
            Vec3 ndc = clip.perspDiv();
            float sw = (float)ctx.window_width;
            float sh = (float)ctx.window_height;
            ps.plane_screen[i] = {
                (ndc.x * 0.5f + 0.5f) * sw,
                (1.0f - (ndc.y * 0.5f + 0.5f)) * sh,
                ndc.z
            };
            ps.plane_display[i] = ps.plane_world[i].xyz(); // for 3D rendering
        }
    }

    if (ps.transition_t < 1.0f) {
        ps.transition_t = std::min(1.0f, ps.transition_t + dt * 3.0f);
    }

    
    bool mode_change = is_3d_stage(ps.prev_stage) != is_3d_stage(ps.current_stage);
    float t = smoothstep(ps.transition_t);

    if (mode_change) {
        
        
        if (ps.transition_t < 0.5f) {
            ps.fade_alpha = 1.0f - ps.transition_t * 2.0f;
            get_stage_verts(ps, ps.prev_stage, ps.display_verts);
            get_tri_stage_verts(ps, ps.prev_stage, ps.tri_display);
        } else {
            ps.fade_alpha = (ps.transition_t - 0.5f) * 2.0f;
            get_stage_verts(ps, ps.current_stage, ps.display_verts);
            get_tri_stage_verts(ps, ps.current_stage, ps.tri_display);
        }
    } else if (ps.transition_t >= 1.0f) {
        ps.fade_alpha = 1.0f;
        get_stage_verts(ps, ps.current_stage, ps.display_verts);
        get_tri_stage_verts(ps, ps.current_stage, ps.tri_display);
    } else {
        ps.fade_alpha = 1.0f;
        Vec3 prev_v[NUM_CUBE_VERTS], cur_v[NUM_CUBE_VERTS];
        get_stage_verts(ps, ps.prev_stage, prev_v);
        get_stage_verts(ps, ps.current_stage, cur_v);
        for (int i = 0; i < NUM_CUBE_VERTS; i++) {
            ps.display_verts[i] = lerp(prev_v[i], cur_v[i], t);
        }
        Vec3 prev_t[NUM_TRI_VERTS], cur_t[NUM_TRI_VERTS];
        get_tri_stage_verts(ps, ps.prev_stage, prev_t);
        get_tri_stage_verts(ps, ps.current_stage, cur_t);
        for (int i = 0; i < NUM_TRI_VERTS; i++) {
            ps.tri_display[i] = lerp(prev_t[i], cur_t[i], t);
        }
    }

    
    if (ps.pending_click) {
        ps.pending_click = false;

        float w = (float)ctx.window_width;
        float h = (float)ctx.window_height;
        float full_vp_w = w - 40;
        float vp_x = 20, vp_y = 92;
        float vp_h = h - 92 - 80;
        float vp_w = ps.show_first_person ? (full_vp_w - 10.0f) * 0.5f : full_vp_w;

        
        float best_dist = 20.0f; 
        int best = -1;

        for (int i = 0; i < NUM_CUBE_VERTS; i++) {
            float sx_proj, sy_proj;

            if (is_3d_stage(ps.current_stage)) {
                Vec3 sp = project_to_viewport(ps.display_verts[i],
                    ps.cam_orbit_pitch, ps.cam_orbit_yaw, ps.cam_distance,
                    vp_x, vp_y, vp_w, vp_h);
                if (sp.x < -5000) continue;
                sx_proj = sp.x;
                sy_proj = sp.y;
            } else if (ps.current_stage == 4) {
                
                float ndc_size = std::min(vp_w, vp_h) * 0.38f;
                float cx = vp_x + vp_w * 0.5f;
                float cy = vp_y + vp_h * 0.5f;
                Vec3 v = ps.display_verts[i];
                sx_proj = cx - ndc_size + ((v.x + 1.0f) / 2.0f) * ndc_size * 2;
                sy_proj = cy - ndc_size + (1.0f - (v.y + 1.0f) / 2.0f) * ndc_size * 2;
            } else {
                
                float screen_w = vp_w * 0.7f;
                float screen_h = vp_h * 0.7f;
                float sx = vp_x + (vp_w - screen_w) * 0.5f;
                float sy = vp_y + (vp_h - screen_h) * 0.5f;
                sx_proj = sx + (ps.display_verts[i].x / w) * screen_w;
                sy_proj = sy + (ps.display_verts[i].y / h) * screen_h;
            }

            float dx = sx_proj - ps.click_x;
            float dy = sy_proj - ps.click_y;
            float dist = std::sqrt(dx * dx + dy * dy);
            if (dist < best_dist) {
                best_dist = dist;
                best = i;
            }
        }

        
        if (best == ps.selected_vertex) {
            ps.selected_vertex = -1;
        } else {
            ps.selected_vertex = best;
        }
    }
}





void pipeline_render(PipelineState& ps, GameContext& ctx) {
    SDL_Renderer* r = ctx.renderer;
    float w = (float)ctx.window_width;
    float h = (float)ctx.window_height;

    SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);

    int stage = ps.current_stage;

    // Viewport layout: split when first-person view is active
    float full_vp_w = w - 40;
    float vp_x = 20;
    float vp_y = 92;
    float vp_h = h - 92 - 80;
    float vp_w, cam_vp_x, cam_vp_w;
    constexpr float SPLIT_GAP = 10.0f;

    if (ps.show_first_person) {
        vp_w = (full_vp_w - SPLIT_GAP) * 0.5f;
        cam_vp_x = vp_x + vp_w + SPLIT_GAP;
        cam_vp_w = full_vp_w - vp_w - SPLIT_GAP;
    } else {
        vp_w = full_vp_w;
        cam_vp_x = 0;
        cam_vp_w = 0;
    }

    // Pipeline viewport border
    SDL_SetRenderDrawColor(r, 50, 50, 60, 255);
    SDL_FRect vp_rect = {vp_x, vp_y, vp_w, vp_h};
    SDL_RenderRect(r, &vp_rect);

    
    SDL_SetRenderDrawColor(r, 240, 240, 250, 255);
    draw_text(r, 20, 8, stage_info[stage].name, 3.0f);

    SDL_SetRenderDrawColor(r, 155, 155, 175, 255);
    draw_text(r, 20, 40, stage_info[stage].description, 2.0f);
    SDL_SetRenderDrawColor(r, 110, 110, 130, 255);
    draw_text(r, 20, 68, stage_info[stage].detail, 1.2f);
    float detail_end = 20 + (float)std::strlen(stage_info[stage].detail) * 8.0f * 1.2f;
    SDL_SetRenderDrawColor(r, 80, 80, 95, 255);
    draw_text(r, detail_end + 8, 68, "-");
    SDL_SetRenderDrawColor(r, 100, 160, 120, 255);
    draw_text(r, detail_end + 24, 68, stage_info[stage].godot_api, 1.2f);
 
    // Shader mode badge
    if (ps.shader_mode) {
        SDL_SetRenderDrawColor(r, 255, 140, 60, 255);
        draw_text(r, vp_x + vp_w - 180, vp_y + vp_h - 60, "VERTEX SHADER ACTIVE");
    }

    bool in_transition = ps.transition_t < 1.0f;
    bool mode_change = in_transition && (is_3d_stage(ps.prev_stage) != is_3d_stage(ps.current_stage));
    
    int render_stage = stage;
    if (mode_change) {
        render_stage = (ps.transition_t < 0.5f) ? ps.prev_stage : ps.current_stage;
    }

    uint8_t alpha = (uint8_t)(ps.fade_alpha * 255.0f);
    uint8_t face_alpha = (uint8_t)(ps.fade_alpha * 40.0f); 

    
    if (is_3d_stage(render_stage)) {
        float p = ps.cam_orbit_pitch, y_angle = ps.cam_orbit_yaw, d = ps.cam_distance;

        
        draw_grid_3d(r, 12, 12, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);

        
        draw_axes_3d(r, {0, 0, 0}, 2.0f, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);

        
        auto label_axis = [&](Vec3 pos, const char* lbl, uint8_t cr, uint8_t cg, uint8_t cb) {
            Vec3 sp = project_to_viewport(pos, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
            if (sp.x > -5000) {
                SDL_SetRenderDrawColor(r, cr, cg, cb, alpha);
                draw_text(r, sp.x + 4, sp.y - 4, lbl);
            }
        };
        label_axis({2.2f, 0, 0}, "X", 255, 60, 60);
        label_axis({0, 2.2f, 0}, "Y", 60, 255, 60);
        label_axis({0, 0, 2.2f}, "Z", 60, 60, 255);

        
        draw_faces_3d(r, ps.display_verts, p, y_angle, d, vp_x, vp_y, vp_w, vp_h, face_alpha);

        
        draw_wireframe_3d(r, ps.display_verts, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);

        
        if (ps.show_triangle) {
            draw_tri_face_3d(r, ps.tri_display, p, y_angle, d, vp_x, vp_y, vp_w, vp_h, face_alpha);
            draw_tri_wireframe_3d(r, ps.tri_display, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
        }

        // Z-fighting plane wireframe (only in world/view stages where it makes sense)
        if (ps.show_zfight_plane && stage <= 1) {
            SDL_SetRenderDrawColor(r, 255, 180, 60, 180);
            for (int i = 0; i < 4; i++) {
                int a = plane_edges[i][0], b = plane_edges[i][1];
                draw_line_3d(r, ps.plane_display[a], ps.plane_display[b],
                             p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
            }
            // Semi-transparent face
            Vec3 screen_p[4];
            for (int i = 0; i < 4; i++)
                screen_p[i] = project_to_viewport(ps.plane_display[i], p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
            bool all_vis = true;
            for (int i = 0; i < 4; i++) if (screen_p[i].x < -5000) all_vis = false;
            if (all_vis) {
                SDL_FColor pcol = {1.0f, 0.7f, 0.24f, 0.15f};
                SDL_Vertex sv[4];
                for (int i = 0; i < 4; i++) {
                    sv[i].position = {screen_p[i].x, screen_p[i].y};
                    sv[i].color = pcol;
                }
                int idx[6] = {0, 1, 2, 0, 2, 3};
                SDL_RenderGeometry(r, nullptr, sv, 4, idx, 6);
            }
        }

        if (stage >= 1) {
            Vec3 cam = {ps.pipe_cam_x, ps.pipe_cam_y, ps.pipe_cam_z};
            Vec3 cam_target = {0, 0, 0};

            
            float aspect = (float)ctx.window_width / std::max((float)ctx.window_height, 1.0f);
            draw_frustum_3d(r, cam, cam_target,
                            ps.pipe_cam_fov, aspect, ps.pipe_cam_near, ps.pipe_cam_far,
                            p, y_angle, d, vp_x, vp_y, vp_w, vp_h);

            
            Vec3 sp = project_to_viewport(cam, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
            if (sp.x > -5000) {
                SDL_SetRenderDrawColor(r, 255, 255, 100, 255);
                SDL_FRect cam_dot = {sp.x - 5, sp.y - 5, 10, 10};
                SDL_RenderFillRect(r, &cam_dot);
                draw_text(r, sp.x + 8, sp.y - 4, "CAM");

                
                SDL_SetRenderDrawColor(r, 255, 255, 100, 100);
                draw_line_3d(r, cam, cam_target, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
            }
        }

        // Matrix display - anchored to pipeline viewport right edge
        float mat_x = vp_x + vp_w - 270;
        float mat_y = vp_y + 10;
        bool bd = ps.show_matrix_breakdown;
        if (stage == 1) draw_matrix(r, ps.model_matrix, mat_x, mat_y, "Model Matrix:", bd);
        if (stage == 2) draw_matrix(r, ps.view_matrix, mat_x, mat_y, "View Matrix:", bd);
        if (stage == 3) draw_matrix(r, ps.proj_matrix, mat_x, mat_y, "Projection Matrix:", bd);

        
        if (stage == 2) {
            draw_near_plane_3d(r, ps, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
        }

        
        if (stage == 3) {
            draw_clip_status_3d(r, ps, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
            if (ps.show_clipping) {
                draw_clipped_geometry_3d(r, ps, p, y_angle, d, vp_x, vp_y, vp_w, vp_h);
            }
        }

    
    } else if (render_stage == 4) {
        float ndc_size = std::min(vp_w, vp_h) * 0.38f;
        float cx = vp_x + vp_w * 0.5f;
        float cy = vp_y + vp_h * 0.5f;

        
        SDL_SetRenderDrawColor(r, 55, 55, 75, 255);
        SDL_FRect ndc_box = {cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2};
        SDL_RenderRect(r, &ndc_box);

        
        SDL_SetRenderDrawColor(r, 45, 45, 60, 255);
        SDL_RenderLine(r, cx - ndc_size, cy, cx + ndc_size, cy);
        SDL_RenderLine(r, cx, cy - ndc_size, cx, cy + ndc_size);

        
        SDL_SetRenderDrawColor(r, 80, 80, 100, 255);
        draw_text(r, cx - ndc_size - 24, cy - 4, "-1");
        draw_text(r, cx + ndc_size + 6, cy - 4, "+1");
        draw_text(r, cx - 8, cy - ndc_size - 14, "+1");
        draw_text(r, cx - 8, cy + ndc_size + 4, "-1");
        draw_text(r, cx + 4, cy + 4, "0");

        
        draw_faces_2d(r, ps.display_verts,
                      cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2,
                      -1.0f, 1.0f, face_alpha);

        
        draw_wireframe_2d(r, ps.display_verts,
                          cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2,
                          -1.0f, 1.0f);

        
        if (ps.show_triangle) {
            draw_tri_face_2d(r, ps.tri_display,
                             cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2,
                             -1.0f, 1.0f, face_alpha);
            draw_tri_wireframe_2d(r, ps.tri_display,
                                  cx - ndc_size, cy - ndc_size, ndc_size * 2, ndc_size * 2,
                                  -1.0f, 1.0f);
        }

    
    } else {
        float screen_w = vp_w * 0.7f;
        float screen_h = vp_h * 0.7f;
        float sx = vp_x + (vp_w - screen_w) * 0.5f;
        float sy = vp_y + (vp_h - screen_h) * 0.5f;

        
        SDL_SetRenderDrawColor(r, 38, 38, 52, 255);
        SDL_FRect screen_bg = {sx, sy, screen_w, screen_h};
        SDL_RenderFillRect(r, &screen_bg);
        SDL_SetRenderDrawColor(r, 75, 75, 95, 255);
        SDL_RenderRect(r, &screen_bg);

        
        SDL_SetRenderDrawColor(r, 80, 80, 100, 255);
        draw_text(r, sx + 2, sy + 2, "(0, 0)");
        char buf[48];
        std::snprintf(buf, sizeof(buf), "(%d, %d)", ctx.window_width, ctx.window_height);
        draw_text(r, sx + screen_w - (float)std::strlen(buf) * 8 - 4, sy + screen_h - 14, buf);

        
        draw_raster_grid(r, ps, w, h, sx, sy, screen_w, screen_h);

        
        draw_faces_screen(r, ps.display_verts, w, h, sx, sy, screen_w, screen_h, face_alpha);

        
        for (int i = 0; i < NUM_CUBE_EDGES; i++) {
            int a = cube_edges[i][0], b = cube_edges[i][1];
            Vec3 va = ps.display_verts[a];
            Vec3 vb = ps.display_verts[b];

            float ax = sx + (va.x / w) * screen_w;
            float ay = sy + (va.y / h) * screen_h;
            float bx = sx + (vb.x / w) * screen_w;
            float by = sy + (vb.y / h) * screen_h;

            uint8_t cr = (uint8_t)((vert_colors[a].r + vert_colors[b].r) / 2);
            uint8_t cg = (uint8_t)((vert_colors[a].g + vert_colors[b].g) / 2);
            uint8_t cb = (uint8_t)((vert_colors[a].b + vert_colors[b].b) / 2);
            SDL_SetRenderDrawColor(r, cr, cg, cb, alpha);
            SDL_RenderLine(r, ax, ay, bx, by);
        }

        
        for (int i = 0; i < NUM_CUBE_VERTS; i++) {
            float vx = sx + (ps.display_verts[i].x / w) * screen_w;
            float vy = sy + (ps.display_verts[i].y / h) * screen_h;
            SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g, vert_colors[i].b, alpha);
            SDL_FRect dot = {vx - 3, vy - 3, 6, 6};
            SDL_RenderFillRect(r, &dot);
        }

        
        if (ps.show_triangle) {
            
            SDL_FColor tcol = {0.85f, 0.9f, 1.0f, face_alpha / 255.0f};
            SDL_Vertex tri_sv[3];
            for (int i = 0; i < 3; i++) {
                tri_sv[i].position = {sx + (ps.tri_display[i].x / w) * screen_w,
                                      sy + (ps.tri_display[i].y / h) * screen_h};
                tri_sv[i].color = tcol;
            }
            int tri_idx[3] = {0, 1, 2};
            SDL_RenderGeometry(r, nullptr, tri_sv, 3, tri_idx, 3);

            
            for (int i = 0; i < NUM_TRI_EDGES; i++) {
                int a = tri_edges[i][0], b = tri_edges[i][1];
                uint8_t cr = (uint8_t)((tri_colors[a].r + tri_colors[b].r) / 2);
                uint8_t cg = (uint8_t)((tri_colors[a].g + tri_colors[b].g) / 2);
                uint8_t cb = (uint8_t)((tri_colors[a].b + tri_colors[b].b) / 2);
                SDL_SetRenderDrawColor(r, cr, cg, cb, alpha);
                SDL_RenderLine(r, tri_sv[a].position.x, tri_sv[a].position.y,
                               tri_sv[b].position.x, tri_sv[b].position.y);
            }
            
            for (int i = 0; i < NUM_TRI_VERTS; i++) {
                SDL_SetRenderDrawColor(r, tri_colors[i].r, tri_colors[i].g, tri_colors[i].b, alpha);
                SDL_FRect dot = {tri_sv[i].position.x - 3, tri_sv[i].position.y - 3, 6, 6};
                SDL_RenderFillRect(r, &dot);
            }
        }
    }

    
    if (ps.selected_vertex >= 0) {
        int vi = ps.selected_vertex;
        float time = (float)SDL_GetTicks() / 1000.0f;
        float sx_sel = 0, sy_sel = 0;
        bool visible = true;

        if (is_3d_stage(render_stage)) {
            Vec3 sp = project_to_viewport(ps.display_verts[vi],
                ps.cam_orbit_pitch, ps.cam_orbit_yaw, ps.cam_distance,
                vp_x, vp_y, vp_w, vp_h);
            if (sp.x < -5000) visible = false;
            sx_sel = sp.x;
            sy_sel = sp.y;
        } else if (render_stage == 4) {
            float ndc_size = std::min(vp_w, vp_h) * 0.38f;
            float cx = vp_x + vp_w * 0.5f;
            float cy = vp_y + vp_h * 0.5f;
            Vec3 v = ps.display_verts[vi];
            sx_sel = cx - ndc_size + ((v.x + 1.0f) / 2.0f) * ndc_size * 2;
            sy_sel = cy - ndc_size + (1.0f - (v.y + 1.0f) / 2.0f) * ndc_size * 2;
        } else {
            float screen_w = vp_w * 0.7f;
            float screen_h = vp_h * 0.7f;
            float sx = vp_x + (vp_w - screen_w) * 0.5f;
            float sy = vp_y + (vp_h - screen_h) * 0.5f;
            sx_sel = sx + (ps.display_verts[vi].x / w) * screen_w;
            sy_sel = sy + (ps.display_verts[vi].y / h) * screen_h;
        }

        if (visible) {
            draw_vertex_highlight(r, sx_sel, sy_sel, vert_colors[vi], time);
        }

        // Vertex sidebar - right edge, anchored just above vertex coordinates panel
        float sidebar_h = 130;
        float sidebar_y = vp_y + vp_h - 130 - sidebar_h - 10; // 130=vert coords height, 10=gap
        draw_vertex_sidebar(r, ps, vi, stage, vp_x + vp_w - 250, sidebar_y, 240);
    }

    // First-person camera split view
    if (ps.show_first_person) {
        draw_first_person_view(r, ps, cam_vp_x, vp_y, cam_vp_w, vp_h);
    }


    draw_stage_indicator(r, ps.current_stage, w, h);


    SDL_SetRenderDrawColor(r, 70, 70, 90, 255);
    draw_text(r, 20, h - 16,
              "Left/Right: Stage   WASD: Move   Drag: Orbit   RClick: Select   Space: Auto-rotate   H: Help");

    
    draw_info_panel(r, ps, stage, vp_x + 8, vp_y + 10);

    
    if (ps.show_sliders) {
        draw_slider_panel(r, ps, vp_y);
    }

    
    {
        char buf[64];
        float px = 20;
        float py = vp_y + vp_h - 44;
        SDL_SetRenderDrawColor(r, 90, 90, 110, 255);
        std::snprintf(buf, sizeof(buf), "Object: pos(%.1f, %.1f, %.1f)  rot(%.0f deg)",
                      ps.obj_pos_x, ps.obj_pos_y, ps.obj_pos_z,
                      ps.obj_rot_y * 180.0f / PI);
        draw_text(r, px, py, buf);
        std::snprintf(buf, sizeof(buf), "Camera: pos(%.1f, %.1f, %.1f)  fov(%.0f deg)",
                      ps.pipe_cam_x, ps.pipe_cam_y, ps.pipe_cam_z,
                      ps.pipe_cam_fov * 180.0f / PI);
        draw_text(r, px, py + 14, buf);
    }

    // Vertex coordinates - anchored to pipeline viewport right edge
    {
        float panel_x = vp_x + vp_w - 225;
        float panel_y = vp_y + vp_h - 155;
        SDL_SetRenderDrawColor(r, 110, 110, 130, 255);
        draw_text(r, panel_x, panel_y, "Vertex Coordinates:", 1.2f);

        for (int i = 0; i < NUM_CUBE_VERTS; i++) {
            char buf[80];
            if (stage == 3) {
                Vec4 c = ps.verts_clip[i];
                std::snprintf(buf, sizeof(buf), "(%5.1f,%5.1f,%5.1f, w=%4.1f)",
                              c.x, c.y, c.z, c.w);
            } else {
                Vec3 v;
                switch (stage) {
                    case 0: v = ps.verts_object[i].xyz(); break;
                    case 1: v = ps.verts_world[i].xyz(); break;
                    case 2: v = ps.verts_view[i].xyz(); break;
                    case 4: v = ps.verts_ndc[i]; break;
                    case 5: v = ps.verts_screen[i]; break;
                    default: v = {}; break;
                }
                if (stage == 5) {
                    std::snprintf(buf, sizeof(buf), "(%6.0f, %6.0f)", v.x, v.y);
                } else {
                    std::snprintf(buf, sizeof(buf), "(%5.2f, %5.2f, %5.2f)", v.x, v.y, v.z);
                }
            }
            SDL_SetRenderDrawColor(r, vert_colors[i].r, vert_colors[i].g,
                                   vert_colors[i].b, 220);
            draw_text(r, panel_x, panel_y + 18 + i * 15, buf, 1.2f);
        }
    }

    // Help hint - top right corner of window
    if (!ps.show_help) {
        float hw = 136, hh = 18;
        float hx = w - hw - 2, hy = 2;
        SDL_SetRenderDrawColor(r, 50, 50, 65, 140);
        SDL_FRect help_bg = {hx - 4, hy - 2, hw, hh};
        SDL_RenderFillRect(r, &help_bg);
        SDL_SetRenderDrawColor(r, 70, 70, 90, 160);
        SDL_RenderRect(r, &help_bg);
        SDL_SetRenderDrawColor(r, 100, 100, 120, 200);
        draw_text(r, hx, hy + 1, "Press H for help");
    }

    // Help overlay (drawn last, on top of everything)
    if (ps.show_help) {
        draw_help_overlay(r, ps, w, h);
    }
}
