/* tex_demo.c —— **3D 贴图**（`ui_3d_obj_set_tex` `#620`）示例 + 判据。
 * tex_demo.c — **3D texturing** (`ui_3d_obj_set_tex` `#620`): example and criteria.
 *
 * ## 这个程序演示什么
 * ## What this program demonstrates
 *
 * 「在 2D 画布上画一张图 → 抓成图像句柄 → 贴到 3D 物体上」，也就是本仓 3D 里
 * 从前**唯一**没实现的那个接口。纹理源与 2D 绘图共用同一条链：
 * "Draw a picture on the 2D canvas -> grab it as an image handle -> map it onto a 3D object",
 * i.e. the one 3D entry point that used to be the **only** unimplemented one. The texture source
 * reuses the very same 2D chain:
 *
 *     ui_2d_rect(...)         画图案（这里是一个棋盘）
 *     ui_2d_get_image(x,y,w,h) 抓成句柄 —— 像素是 **RGBA8 行优先**，与 GPU 纹理格式逐字节同构
 *     ui_3d_obj_set_tex(obj, h) 贴上去（传 0 = 取消贴图）
 *
 * ## 为什么判据必须用「uv 有变化」的网格
 * ## Why the grid must have varying uv
 *
 * 这是本次最容易写错、也最难发现的一条：**uv 属性没接上时，GL 对"被禁用的顶点属性"
 * 读的是常量 (0,0)** ⇒ 所有顶点都取 `texel(0,0)` ⇒ 物体变成**单一颜色**，而
 * **编译、链接、运行全绿、一个错都不报**。
 * This is the easiest thing to get wrong and the hardest to notice: when the uv attribute is not
 * hooked up, GL reads a constant (0,0) for a disabled attribute => every vertex samples texel(0,0)
 * => the object becomes a **single colour**, while compiling, linking and running all stay green.
 *
 * ⇒ 若网格 uv 全 0（`g3_all.c` 那份就是），"贴什么图"与"uv 坏了"**读出来一模一样**，
 *   拿它写的判据恒假绿。本文件的方片**四角 uv 互不相同**，并且断言的是
 *   「棋盘的两色**同时在**物体上出现」—— 这才分辨得出来。
 * => With an all-zero uv grid the two situations read identically and the criterion is a false green.
 *   This quad has four **distinct** corner uvs and asserts that **both** checker colours appear
 *   on the object at once.
 *
 * ⚠ 另有一条同族的静默坑：**纹理的 v 方向**。`ui_2d_get_image` 抓下来的像素是**自上而下**的
 * 行优先，而纹理坐标 v=0 对应**第一行**（图像顶部）。方片把"屏幕上方"映到 v=0，于是图案正立；
 * 写反了图案会上下颠倒 —— 对**棋盘**看不出来（对称），所以这里另外把判据押在
 * 「红/白**两色都出现**」上：颠倒不会改变这个结论，而"uv 没接上"会（只剩一色）。
 * ⚠ A cousin trap: the **v direction**. Grabbed pixels are top-down row-major and v=0 is the first
 *   row, so mapping "screen up" to v=0 keeps the pattern upright. For a checkerboard a flip is
 *   invisible, so the criterion leans on "both colours appear", which a flip preserves but a
 *   broken uv attribute destroys.
 *
 * ## 跑法
 * ## How to run
 *   dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll \
 *          third_party/vml/Examples/c/3d/tex_demo.c --screen 393x757
 *
 * 机器判读：逐行 `PASS …` / `FAIL …`，末行 `TEX_DEMO PASS|FAIL (fails=N)`。
 * Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `TEX_DEMO PASS|FAIL (fails=N)`.
 */

#include <vml_ui.h>
#include <vml_3d.h>
#include <math.h>
#include <stdio.h>

/* ══ 记分 ══════════════════════════════════════════════════════════════════ */
/* == Scoring == */

int g_fails;

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); }
}

int rch(int c) { return (c >> 16) & 0xFF; }
int gch(int c) { return (c >> 8) & 0xFF; }
int bch(int c) { return c & 0xFF; }

/* ══ 网格装配（pos3 + nrm3 + uv2 = 8 个 float32 = 32 字节/顶点，紧随其后是 int32 索引）════
 * == Mesh assembly (pos3 + nrm3 + uv2 = 8 float32 = 32 bytes per vertex, then int32 indices) == */

float MB[256];
int g_vn;
int g_in;

void mb_vertex(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;      /* ← uv 就在这 8 个 float 的尾巴上 */
}
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]; }

/* 一个正对 +Z 的方片（边长 2、中心在原点）。**四角 uv 互不相同** —— 见文件头的说明，
 * 这是整份判据能不能响的前提。v=0 对应图像**第一行**（屏幕上方）。
 * A quad facing +Z (side 2, centred at the origin). The four corner uvs are **all distinct**;
 * v=0 is the first image row (screen up). */
void gen_quad(void) {
    mb_vertex(0, -1.0f, -1.0f, 0.0f,  0.0f, 0.0f, 1.0f,  0.0f, 1.0f);
    mb_vertex(1,  1.0f, -1.0f, 0.0f,  0.0f, 0.0f, 1.0f,  1.0f, 1.0f);
    mb_vertex(2,  1.0f,  1.0f, 0.0f,  0.0f, 0.0f, 1.0f,  1.0f, 0.0f);
    mb_vertex(3, -1.0f,  1.0f, 0.0f,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f);
    g_vn = 4;
    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;
}

/* ══ 像素取样：在物体**自己报出来的**屏幕位置周围抽稀取点 ══════════════════════
 * == Pixel sampling: sparse probes around the screen position the object itself reports ==
 *
 * ⚠ 两个约束，都是实测踩出来的：
 *   ① **不能按屏幕比例猜物体在哪儿** —— 物体的覆盖范围随相机/宽高比变；用
 *      `ui_3d_obj_get_screen` 报的那个点做中心才稳（619 段也是这么取的）。
 *   ② **必须抽稀** —— `ui_3d_viewport_get_pixel` 是**逐点一趟线程往返**，实测量过
 *      6561 次 = 33 秒。400 次是可接受的量级。
 * ⚠ Two constraints, both measured: do not guess the object's screen extent from proportions
 *   (it moves with camera/aspect - centre on what `ui_3d_obj_get_screen` reports), and do **not**
 *   read densely (`ui_3d_viewport_get_pixel` is one thread round-trip per point; 6561 points
 *   measured 33 seconds). 400 probes is fine.
 */

int sx(int sc) { return sc & 0xFFFF; }
int sy(int sc) { return (sc >> 16) & 0xFFFF; }

/* 数"明显偏红"的采样点。**通道强不对称**是关键：红与白在 R 上分不开，要靠 G/B 低。 */
/* Counts samples that are clearly red. Strongly asymmetric channels are the point. */
int count_red(int vp, int cx, int cy, int rad) {
    int i; int j; int n; n = 0;
    for (j = -10; j < 10; j = j + 1)
        for (i = -10; i < 10; i = i + 1) {
            int c = ui_3d_viewport_get_pixel(vp, cx + i * rad / 10, cy + j * rad / 10);
            if (rch(c) > 200 && gch(c) < 60 && bch(c) < 60) n = n + 1;
        }
    return n;
}

/* 数"明显偏白"的采样点（三个通道都高）。 */
int count_white(int vp, int cx, int cy, int rad) {
    int i; int j; int n; n = 0;
    for (j = -10; j < 10; j = j + 1)
        for (i = -10; i < 10; i = i + 1) {
            int c = ui_3d_viewport_get_pixel(vp, cx + i * rad / 10, cy + j * rad / 10);
            if (rch(c) > 200 && gch(c) > 200 && bch(c) > 200) n = n + 1;
        }
    return n;
}

int main(void) {
    int w; int h; int vp; int mesh; int obj;
    int img_check; int img_red;
    int r; int i; int j; int sc; int px; int py; int rad;
    int red_a; int white_a; int red_b; int white_b; int red_c;
    int col_r = 0xFFD02020;      /* 棋盘的"红"格 */
    int col_w = 0xFFFFFFFF;      /* 棋盘的"白"格 */

    g_fails = 0;

    ui_win_open_ex("3D 贴图示例", 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);
    printf("SCR=%dx%d\n", w, h);
    if (w < 64 || h < 64) { printf("TEX_DEMO FAIL (fails=99)\n"); return 99; }

    /* ── ① 在 2D 画布上画一张 8×8 的棋盘（每格 4 像素 = 32×32）───────────────
     * ① Draw an 8x8 checkerboard on the 2D canvas (4 px per cell = 32x32)
     *
     * ⚠ 图案**必须两色且通道强不对称**：纯灰阶的两色图抓不出"红蓝被换"这个错，
     *   而"红蓝被换"正是纹理上传最容易犯的错（RGBA 序 vs 0xAARRGGBB）。
     * The two colours must be strongly channel-asymmetric; a greyscale pair cannot catch a
     * red/blue swap, which is the classic texturing mistake. */
    for (j = 0; j < 8; j = j + 1)
        for (i = 0; i < 8; i = i + 1) {
            int c = col_r;
            if ((i + j) % 2 == 0) c = col_w;
            ui_2d_rect(4 + i * 4, 4 + j * 4, 4, 4, c, 1, 0, 0);
        }
    ui_2d_present();
    img_check = ui_2d_get_image(4, 4, 32, 32);
    ck("get_image(checker) -> handle", img_check > 0, img_check, 0);

    /* ── ② 3D：视口 + 相机 + 方片 + 贴图 ─────────────────────────────────── */
    /* ② 3D: viewport + camera + quad + texture */
    vp = ui_3d_viewport_create(0, 0, w, h, 0);
    ck("viewport -> handle", vp > 0, vp, 0);
    ui_3d_viewport_set_camera(vp, 0.0f, 0.0f, 3.0f, 0.0f, 0.0f, 0.0f);
    ui_3d_viewport_set_fov(vp, 60.0f, 0.1f, 100.0f);
    ui_3d_viewport_set_bg(vp, 0xFF101018);           /* 深色背景：与红/白都不撞 */
    ui_3d_viewport_set_light(vp, 0.0f, 0.0f, 1.0f, 0xFFFFFFFF, 0.35f);

    gen_quad();
    mesh = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
    ck("mesh_create -> handle", mesh > 0, mesh, 0);
    obj = ui_3d_obj_create(vp, mesh);
    ck("obj_create -> handle", obj > 0, obj, 0);

    /* 物体色**白**（乘性 tint 的中性元），并开**自发光**：
     * 自发光让片元直接用基色、不乘光照 ⇒ 读回来的就是纹理的原色 ⇒ 判据阈值可以卡严。
     * White object colour (the neutral element of a multiplicative tint) plus **emissive**, so the
     * fragment takes the base colour directly and the read-back is the texture's own colour. */
    ui_3d_obj_set_color(obj, 0xFFFFFFFF);
    ui_3d_obj_set_emissive(obj, 1);

    sc = ui_3d_obj_get_screen(obj);
    px = sx(sc); py = sy(sc);
    rad = w / 12;                                   /* 取样半径：方片远大于它 */
    printf("OBJSCREEN=%d,%d rad=%d\n", px, py, rad);

    /* ── ③ 贴上去之前：物体是**纯白**的（此时 "红点" 一个都不该有）──────────── */
    /* ③ Before texturing the object is plain white (no red probe should hit) */
    ui_2d_present();
    red_a = count_red(vp, px, py, rad);
    ck("no-tex: no red", red_a == 0, red_a, 0);

    /* ── ④ 贴棋盘 ⇒ 物体上**同时**出现红与白 ─────────────────────────────── */
    /* ④ Map the checkerboard: **both** colours must appear on the object at once */
    r = ui_3d_obj_set_tex(obj, img_check);
    ck("obj_set_tex(checker) rc", r == 0, r, 0);
    ui_2d_present();
    red_a = count_red(vp, px, py, rad);
    white_a = count_white(vp, px, py, rad);
    ck("tex(checker): red cells present", red_a > 0, red_a, 0);
    ck("tex(checker): white cells present", white_a > 0, white_a, 0);
    /* ⚠ **这一条才是能响的那条**：uv 若没接上（全取 texel(0,0)），物体只会剩**一种**颜色，
     *   上面两条里必有一条变 0。只断言"两色都出现"而不看"是否真的分了格"是不行的 ——
     *   所以再加一条：红与白在**同一片区域里**同时可观测，即两者都非零且都不到 400。
     * The loud one: with a broken uv attribute the object collapses to a single colour and one of
     * the two counts above goes to zero. Both being non-zero *and* neither saturating means the
     * checker actually landed across the quad. */
    ck("tex(checker): both colours coexist (not one flat colour)",
       red_a > 0 && white_a > 0 && red_a < 400 && white_a < 400, red_a, white_a);

    /* ── ⑤ 换成**纯红**图 ⇒ 白点全部消失（证明采样真的跟着源走）───────────── */
    /* ⑤ Swap in a **solid red** image: every white probe must vanish */
    ui_2d_rect(4, 4, 32, 32, col_r, 1, 0, 0);       /* 整块盖成纯红 */
    ui_2d_present();
    img_red = ui_2d_get_image(4, 4, 32, 32);
    ck("get_image(solid red) -> handle", img_red > 0, img_red, 0);
    r = ui_3d_obj_set_tex(obj, img_red);
    ck("obj_set_tex(red) rc", r == 0, r, 0);
    ui_2d_present();
    red_b = count_red(vp, px, py, rad);
    white_b = count_white(vp, px, py, rad);
    ck("tex(red): red everywhere", red_b > 0, red_b, 0);
    ck("tex(red): no white left (source really swapped)", white_b == 0, white_b, 0);

    /* ── ⑥ 取消贴图（传 0）⇒ 回到物体的**纯白** ───────────────────────────── */
    /* ⑥ Detach the texture (pass 0): back to the object's plain white */
    r = ui_3d_obj_set_tex(obj, 0);
    ck("obj_set_tex(0) rc", r == 0, r, 0);
    ui_2d_present();
    red_c = count_red(vp, px, py, rad);
    ck("untint: back to plain colour (no red)", red_c == 0, red_c, 0);

    /* ── ⑦ 错误路径：不存在的物体 / 不存在的图像都必须报负数 ────────────────── */
    /* ⑦ Error paths: a bogus object handle and a bogus image handle must both fail */
    r = ui_3d_obj_set_tex(9999, img_red);
    ck("bad object rejected", r < 0, r, 0);
    r = ui_3d_obj_set_tex(obj, 999999);
    ck("bad image rejected", r < 0, r, 0);

    printf("TEX_DEMO %s (fails=%d)\n", g_fails == 0 ? "PASS" : "FAIL", g_fails);
    return g_fails;
}
