/* shadowmap_demo.c —— `#654 ui_3d_viewport_set_shadow_map` 的例程判据。
 * shadowmap_demo.c - the acceptance example for `#654 ui_3d_viewport_set_shadow_map`.
 *
 * 它要证明的不是"画面上多了块黑"（那 `#642` 平面投影阴影也能做到），而是**新的那一件事**：
 * It has to prove is not "some black appeared" (the planar shadow `#642` does that too) but the **new
 * capability**:
 * 影子落在**一块抬高的平面**上 —— 老路（把物体沿光方向压到固定的 `y = 0`）**够不着**那里。
 * the shadow lands on a **raised surface**, which the old path cannot reach (it flattens objects onto a
 * fixed `y = 0` plane).
 *
 * 场景：一大块地面（y = 0）+ 一座台子（顶面 y = 90）+ 悬在台子上方的方块。
 * Scene: a wide ground plane (y = 0) + a plateau (top face at y = 90) + a box floating above it.
 *
 * 判据（每条都要能响；反证见 `docs/VML3D对标浏览器.md` 的 D 段）：
 * Criteria (each must be able to go red):
 *   ① 没开阴影时，台子顶面在方块正下方是**亮的**；
 *   ① with no shadow at all, the plateau top under the caster is **lit**;
 *   ② 开 `#642`（平面投影）**够不着**台子顶面（那个像素逐位不变）—— 这就是"新老之分"；
 *   ② with `#642` (the planar path) it **cannot reach** the raised top (that pixel stays bit-identical);
 *   ③ 开 `#654` 之后同一个像素**变暗**；
 *   ③ with `#654` that same pixel **darkens**;
 *   ④ 关掉 `#654` 之后**逐位还原**（老程序不受影响那条保证）；
 *   ④ turning `#654` off restores it **bit-identically**;
 *   ⑤ 没有自阴影噪声（acne）：台子上远离影子的取样点与"没开阴影"**逐位相同**；
 *   ⑤ no shadow acne: a plateau point far from the shadow stays **bit-identical** to the unshadowed frame;
 *   ⑥ **光转了，影子跟着转**：把主光换到另一边，新落点变暗、老落点变亮。
 *   ⑥ **the shadow follows the light**: move the key light and the new spot darkens while the old one lights up.
 *
 * 机器判读：逐行 `PASS …` / `FAIL …`，末行 `SHADOWMAP_DEMO PASS|FAIL (fails=N)`。
 * Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `SHADOWMAP_DEMO PASS|FAIL (fails=N)`.
 */

#include <vml_ui.h>
#include <vml_3d.h>
#include <math.h>
#include <stdio.h>

int g_fails;

int rch(int c) { return (c >> 16) & 0xFF; }
int gch(int c) { return (c >> 8) & 0xFF; }
int bch(int c) { return c & 0xFF; }
int lum(int c) { return rch(c) + gch(c) + bch(c); }
int argb(int r, int g, int b) { return -16777216 + r * 65536 + g * 256 + b; }

/* 通过 / 失败各打一行。
 * One PASS / FAIL line each. */
void ck(char* tag, int cond, int a, int b) {
    if (cond) { printf("PASS %s %d %d\n", tag, a, b); }
    else { g_fails = g_fails + 1; printf("FAIL %s %d %d\n", tag, a, b); }
}

/* ══ 网格缓冲：一张方片、一座台子、一个方块 ══════════════════════════════════════ */
/* == Mesh buffer: a ground quad, a plateau box, a cube == */

float MB[2048];
int g_vn; int g_in;

void mb_vertex_uv(int i, float px, float py, float pz,
                  float nx, float ny, float nz, float u, float v) {
    int b = i * 8;
    MB[b + 0] = px; MB[b + 1] = py; MB[b + 2] = pz;
    MB[b + 3] = nx; MB[b + 4] = ny; MB[b + 5] = nz;
    MB[b + 6] = u;  MB[b + 7] = v;
}
/* ⚠ 索引必须**等全部顶点写完再写**（落笔在 `g_vn*8+k`，即"紧跟顶点之后"）。
 * ⚠ Indices must be written **after every vertex is in place** (they land at `g_vn*8+k`). */
void mb_index(int k, int v) { int* ip; ip = (int*)&MB[0]; ip[g_vn * 8 + k] = v; }
int mb_ptr(void) { return (int)&MB[0]; }

void add_face(float ax, float ay, float az, float bx, float by, float bz,
              float cx, float cy, float cz, float dx, float dy, float dz,
              float nx, float ny, float nz) {
    int b;
    b = g_vn;
    mb_vertex_uv(b + 0, ax, ay, az, nx, ny, nz, 0.0f, 1.0f);
    mb_vertex_uv(b + 1, bx, by, bz, nx, ny, nz, 1.0f, 1.0f);
    mb_vertex_uv(b + 2, cx, cy, cz, nx, ny, nz, 1.0f, 0.0f);
    mb_vertex_uv(b + 3, dx, dy, dz, nx, ny, nz, 0.0f, 0.0f);
    g_vn = g_vn + 4;
}

void quad(float hx, float hz, float y, float nx, float ny, float nz) {
    g_vn = 0; g_in = 0;
    mb_vertex_uv(0, 0 - hx, y, 0 - hz, nx, ny, nz, 0.0f, 1.0f);
    mb_vertex_uv(1, hx, y, 0 - hz, nx, ny, nz, 1.0f, 1.0f);
    mb_vertex_uv(2, hx, y, hz, nx, ny, nz, 1.0f, 0.0f);
    mb_vertex_uv(3, 0 - hx, y, hz, nx, ny, nz, 0.0f, 0.0f);
    g_vn = 4;
    g_in = 0;
    mb_index(0, 0); mb_index(1, 1); mb_index(2, 2);
    mb_index(3, 0); mb_index(4, 2); mb_index(5, 3);
    g_in = 6;
}

/* 长方体（半边长各自给）：**六个面各四个顶点**，为的是每面一个常量法线。
 * Box with three half extents: **four vertices per face** so each face gets a constant normal. */
void box(float hx, float hy, float hz) {
    int f;
    g_vn = 0; g_in = 0;
    add_face(0-hx, 0-hy, hz,   hx, 0-hy, hz,   hx, hy, hz,   0-hx, hy, hz,   0, 0, 1);
    add_face(hx, 0-hy, 0-hz,   0-hx, 0-hy, 0-hz,   0-hx, hy, 0-hz,   hx, hy, 0-hz,   0, 0, 0-1);
    add_face(0-hx, hy, hz,   hx, hy, hz,   hx, hy, 0-hz,   0-hx, hy, 0-hz,   0, 1, 0);
    add_face(0-hx, 0-hy, 0-hz,   hx, 0-hy, 0-hz,   hx, 0-hy, hz,   0-hx, 0-hy, hz,   0, 0-1, 0);
    add_face(hx, 0-hy, hz,   hx, 0-hy, 0-hz,   hx, hy, 0-hz,   hx, hy, hz,   1, 0, 0);
    add_face(0-hx, 0-hy, 0-hz,   0-hx, 0-hy, hz,   0-hx, hy, hz,   0-hx, hy, 0-hz,   0-1, 0, 0);
    for (f = 0; f < 6; f = f + 1) {
        int b = f * 4, k = f * 6;
        mb_index(k+0, b+0); mb_index(k+1, b+1); mb_index(k+2, b+2);
        mb_index(k+3, b+0); mb_index(k+4, b+2); mb_index(k+5, b+3);
    }
    g_in = 36;
}

/* ── 场景常量（判据里的期望值都是**按它们算出来的**，不是拍出来的）────────────── */
/* -- Scene constants; every expected value below is **derived** from them -- */
#define PLATEAU_HALF 120.0f     /* 台子半宽（x/z）*/
/* plateau half width in x and z */
#define PLATEAU_TOP   90.0f     /* 台子顶面的高度 y */
/* world y of the plateau's top face */
#define CASTER_Y     150.0f     /* 方块中心的高度 */
/* world y of the caster's centre */
#define CASTER_HALF   25.0f

/* 影子落点：把方块中心沿**光线方向**投到台子顶面上。
 * Shadow landing point: project the caster's centre onto the plateau top **along the light**. */
void shadow_point(float lx, float ly, float lz, float* outx, float* outz) {
    float ln = sqrt(lx * lx + ly * ly + lz * lz);
    float t = (CASTER_Y - PLATEAU_TOP) / (ly / ln);
    *outx = 0.0f - (lx / ln) * t;
    *outz = 0.0f - (lz / ln) * t;
}

/* 建一个极小的自发光标记物，拿它的屏幕坐标，然后**藏起来**（藏起来之后它既不画、也不投影）。
 * Make a tiny emissive marker, read its screen position, then **hide it** (hidden means it is neither
 * drawn nor a shadow caster). */
int marker_at(int vp, float x, float y, float z, int* px, int* py) {
    int m; int o; int sc;
    quad(1.5f, 1.5f, 0.0f, 0.0f, 1.0f, 0.0f);
    m = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
    o = ui_3d_obj_create(vp, m);
    ui_3d_obj_set_pos(o, x, y, z);
    ui_3d_obj_set_color(o, argb(255, 0, 255));
    ui_3d_obj_set_emissive(o, 1);
    sc = ui_3d_obj_get_screen(o);
    *px = sc & 0xFFFF;
    *py = (sc >> 16) & 0xFFFF;
    ui_3d_obj_set_visible(o, 0);
    return o;
}

int main(void) {
    int w; int h; int vp; int mg; int mb; int mc;
    int og; int ob; int oc; int r; int c;
    int sx; int sy; int fx; int fy; int sx2; int sy2;
    float px1; float pz1; float px2; float pz2;
    int ground_off; int plateau_off; int far_off;
    int plateau_on; int far_on; int ground_on;

    g_fails = 0;
    ui_win_open_ex("阴影贴图 / Shadow map", 0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD);
    w = ui_scr_get_w_ex(ui_scr_get_orientation(), VML_CHROME_NONE);
    h = ui_scr_get_h_ex(ui_scr_get_orientation(), VML_CHROME_NONE);
    (void)w; (void)h;

    vp = ui_3d_viewport_create(0, 0, 320, 320, 0);
    ck("viewport -> handle", vp > 0, vp, 0);
    if (vp <= 0) { printf("SHADOWMAP_DEMO FAIL (fails=1)\n"); return 1; }

    /* 相机从 +z 上方斜看；影子朝 +z 落（在方块**靠相机这一侧**）⇒ 不会被方块自己挡住。
     * The camera looks down from +z; the shadow lands toward +z (the camera side of the caster), so the
     * caster never hides it. */
    ui_3d_viewport_set_camera(vp, 0.0f, 420.0f, 420.0f, 0.0f, 60.0f, 0.0f);
    ui_3d_viewport_set_fov(vp, 50.0f, 1.0f, 4000.0f);
    ui_3d_viewport_set_bg(vp, argb(18, 20, 26));
    ui_3d_viewport_set_light(vp, 0.3f, 1.0f, 0.0f - 0.4f, argb(255, 255, 255), 0.35f);
    ui_2d_clear(0);

    /* 地面（y = 0） */
    /* Ground (y = 0) */
    quad(260.0f, 260.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    mg = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
    ck("ground mesh rc", mg > 0, mg, 0);
    og = ui_3d_obj_create(vp, mg);
    ui_3d_obj_set_color(og, argb(190, 190, 190));

    /* 台子：半高 45 ⇒ 底面 y = 0、顶面 y = 90（正是判据要的那块**抬高的接收面**）。
     * Plateau: half height 45, so its bottom is y = 0 and its top is y = 90 - the **raised receiver**. */
    box(PLATEAU_HALF, PLATEAU_TOP * 0.5f, PLATEAU_HALF);
    mb = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
    ck("plateau mesh rc", mb > 0, mb, 0);
    ob = ui_3d_obj_create(vp, mb);
    ui_3d_obj_set_pos(ob, 0.0f, PLATEAU_TOP * 0.5f, 0.0f);
    ui_3d_obj_set_color(ob, argb(200, 210, 205));

    /* 悬在台子上方的方块（投影体） */
    /* The box floating above the plateau (our caster). */
    box(CASTER_HALF, CASTER_HALF, CASTER_HALF);
    mc = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
    ck("caster mesh rc", mc > 0, mc, 0);
    oc = ui_3d_obj_create(vp, mc);
    ui_3d_obj_set_pos(oc, 0.0f, CASTER_Y, 0.0f);
    ui_3d_obj_set_color(oc, argb(230, 90, 60));

    /* 三个取样点：影子落点（台子顶面）、台子上一个远离影子的点、地面上的一个点。
     * Three sample points: the shadow spot (plateau top), a plateau point far from the shadow, and a
     * point on the lower ground. */
    shadow_point(0.3f, 1.0f, 0.0f - 0.4f, &px1, &pz1);
    marker_at(vp, px1, PLATEAU_TOP + 0.5f, pz1, &sx, &sy);
    marker_at(vp, 0.0f - 95.0f, PLATEAU_TOP + 0.5f, 0.0f - 95.0f, &fx, &fy);
    printf("SHADOW_AT=%.1f,%.1f SCREEN=%d,%d FARSCREEN=%d,%d\n", px1, pz1, sx, sy, fx, fy);

    /* ① 基线：没开任何阴影 */
    /* (1) Baseline: no shadow at all. */
    ground_off = ui_3d_viewport_get_pixel(vp, sx, sy);
    plateau_off = ui_3d_viewport_get_pixel(vp, fx, fy);
    printf("BASE shadowspot=%d plateaufar=%d\n", lum(ground_off), lum(plateau_off));
    ck("1 baseline: receiver under the caster is lit",
       lum(ground_off) > 300, lum(ground_off), 300);

    /* ② 老路（`#642` 平面投影）**够不着**抬高了的接收面 —— 这就是"新老之分"。
     * ② The old path (`#642`, planar) **cannot reach** the raised receiver - this is the whole
     *    difference between old and new. */
    r = ui_3d_viewport_set_shadow(vp, 1);
    ck("2 set planar shadow rc", r == 0, r, 0);
    c = ui_3d_viewport_get_pixel(vp, sx, sy);
    printf("PLANAR shadowspot=%d\n", lum(c));
    ck("2 planar shadow does NOT reach the raised top", c == ground_off, c, ground_off);
    ui_3d_viewport_set_shadow(vp, 0);

    /* ③ 阴影贴图：同一个像素变暗 */
    /* (3) Shadow map: the same pixel darkens. */
    r = ui_3d_viewport_set_shadow_map(vp, 1);
    ck("3 set shadow map rc", r == 0, r, 0);
    ground_on = ui_3d_viewport_get_pixel(vp, sx, sy);
    printf("SHADOWMAP shadowspot=%d\n", lum(ground_on));
    ck("3 shadow map darkens the raised receiver",
       lum(ground_on) < lum(ground_off) - 60, lum(ground_on), lum(ground_off));

    /* ⑤ 没有自阴影噪声：远离影子的台面与"没开阴影"**逐位相同**
     * ⑤ No acne: a plateau point far from the shadow stays **bit-identical** to the unshadowed frame */
    far_on = ui_3d_viewport_get_pixel(vp, fx, fy);
    ck("5 no shadow acne away from the caster", far_on == plateau_off, far_on, plateau_off);

    /* ⑥ 光转了，影子跟着转：新落点变暗、老落点变亮 */
    /* (6) Move the light: the new spot darkens and the old one lights up. */
    shadow_point(0.0f - 0.3f, 1.0f, 0.0f - 0.4f, &px2, &pz2);
    marker_at(vp, px2, PLATEAU_TOP + 0.5f, pz2, &sx2, &sy2);
    printf("SHADOW2_AT=%.1f,%.1f SCREEN=%d,%d\n", px2, pz2, sx2, sy2);
    ui_3d_viewport_set_light(vp, 0.0f - 0.3f, 1.0f, 0.0f - 0.4f, argb(255, 255, 255), 0.35f);
    {
        int old_spot; int new_spot;
        old_spot = ui_3d_viewport_get_pixel(vp, sx, sy);
        new_spot = ui_3d_viewport_get_pixel(vp, sx2, sy2);
        printf("MOVED old=%d new=%d\n", lum(old_spot), lum(new_spot));
        ck("6a old spot becomes lit again", lum(old_spot) > lum(ground_on) + 60, lum(old_spot), lum(ground_on));
        ck("6b new spot is shadowed", lum(new_spot) < lum(ground_off) - 60, lum(new_spot), lum(ground_off));
    }
    /* 把光放回去（后面的④要靠"关掉之后逐位还原"） */
    /* Put the light back (step 4 relies on a bit-identical restore). */
    ui_3d_viewport_set_light(vp, 0.3f, 1.0f, 0.0f - 0.4f, argb(255, 255, 255), 0.35f);

    /* ④ 关掉 ⇒ **逐位**回到基线（老程序不受影响那条保证的落点） */
    /* (4) Off => **bit-identical** back to the baseline. */
    r = ui_3d_viewport_set_shadow_map(vp, 0);
    ck("4 clear shadow map rc", r == 0, r, 0);
    c = ui_3d_viewport_get_pixel(vp, sx, sy);
    ck("4 shadow map off -> bit-identical restore", c == ground_off, c, ground_off);

    /* 句柄校验：坏视口必须**失败**（负数），而不是静默成功 */
    /* Handle validation: a bad viewport must **fail** (negative), never silently succeed. */
    r = ui_3d_viewport_set_shadow_map(9999, 1);
    ck("bad viewport rejected", r < 0, r, 0);

    printf("SHADOWMAP_DEMO %s (fails=%d)\n", g_fails == 0 ? "PASS" : "FAIL", g_fails);
    return g_fails;
}
