/* ortho_demo.c —— `#655 ui_3d_viewport_set_ortho` 的例程判据。
 * ortho_demo.c - the acceptance example for `#655 ui_3d_viewport_set_ortho`.
 *
 * 要证明的就一件事：**正交下"离得远"不再等于"变小"**（透视下会）。
 * It proves exactly one thing: under an orthographic projection **distance no longer means "smaller"**
 * (under perspective it does).
 *
 * 场景：两个**一模一样**的方块，一个在 z = 0、一个在 z = -400（离相机远得多），相机在 +z 上正对着看。
 * Scene: two **identical** boxes, one at z = 0 and one at z = -400 (much farther from the camera), with the
 * camera on the +z axis looking straight at them.
 *
 * 判据（每条都要能响）：
 * Criteria (each must be able to go red):
 *   ① 透视下：近的那个**明显更宽**（这条是"老行为"的基线）；
 *   ① perspective: the near box is **visibly wider** (the baseline for the old behaviour);
 *   ② 正交下：两个**一样宽**（差不超过 1 个像素）—— 这就是 `#655` 的全部价值；
 *   ② orthographic: both are **exactly as wide** (within one pixel) - that is the entire value of `#655`;
 *   ③ 正交 + 雾：两个方块各自的雾浓度**按"到相机的距离"**走（不是整片纯雾色，也不是与距离无关）；
 *   ③ orthographic + fog: each box is fogged **by its distance to the camera** (not a blanket colour, and
 *      not independent of distance either);
 *   ④ 正交关掉 ⇒ **逐位**回到透视那条基线；
 *   ④ turning ortho off returns **bit-identically** to the perspective baseline;
 *   ⑤ 坏视口 / NaN 一律返回负数。
 *   ⑤ a bad viewport / NaN always returns a negative value.
 *
 * ⚠ ②③ 是这一批唯一会碰到"雾与正交"组合的地方：本仓的雾走**逐物体距离**（`uFogDist`），与投影方式无关。
 * ⚠ ② and ③ are the only place this batch touches "fog meets orthographic": this engine's fog uses a
 * per-object distance (uFogDist) and is therefore independent of the projection.
 * 见 `ui_3d_viewport_set_ortho` 的说明 —— 四份着色器里那句"将来加正交要换 varying"是**过时的话**，已改掉。
 * See `ui_3d_viewport_set_ortho`: the "ortho will need a varying" note in the four shaders was **stale**.
 *
 * 机器判读：逐行 `PASS …` / `FAIL …`，末行 `ORTHO_DEMO PASS|FAIL (fails=N)`。
 * Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `ORTHO_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 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); }
}

/* ── 场景常量（判据里的期望值都由它们算出来）────────────────────────────────────── */
/* -- Scene constants; every expected value below is derived from them -- */
#define VW        320            /* 视口语宽 */
/* viewport width in pixels */
#define VH        320            /* 视口语高 */
/* viewport height in pixels */
#define BOX_HALF  40.0f          /* 方块半边长（两个方块一模一样）*/
/* half edge of a box; both boxes are identical */
#define NEAR_X    (-120.0f)      /* 近方块的 x */
/* x of the near box */
#define FAR_X     (120.0f)       /* 远方块的 x */
/* x of the far box */
#define FAR_Z     (-400.0f)      /* 远方块的 z（近的那个在 z = 0）*/
/* z of the far box; the near one sits at z = 0 */
#define ORTHO_HH  200.0f         /* 正交的竖直半高 ⇒ 1 像素 = 400/320 = 1.25 世界单位 */
/* vertical half-height of the ortho box; 1 pixel = 400/320 = 1.25 world units */
#define RED_BOX   argb(255, 0, 0)
#define BLUE_BOX  argb(0, 0, 255)

float MB[1024];
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;
}
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;
}

/* 一个立方体（h = 半边长），六个面各一个朝外的常量法线。
 * One cube (h = half edge); each of the six faces gets an outward constant normal. */
void cube(float h) {
    int f;
    g_vn = 0; g_in = 0;
    add_face(0-h, 0-h, h,   h, 0-h, h,   h, h, h,   0-h, h, h,   0, 0, 1);
    add_face(h, 0-h, 0-h,   0-h, 0-h, 0-h,   0-h, h, 0-h,   h, h, 0-h,   0, 0, 0-1);
    add_face(0-h, h, h,   h, h, h,   h, h, 0-h,   0-h, h, 0-h,   0, 1, 0);
    add_face(0-h, 0-h, 0-h,   h, 0-h, 0-h,   h, 0-h, h,   0-h, 0-h, h,   0, 0-1, 0);
    add_face(h, 0-h, h,   h, 0-h, 0-h,   h, h, 0-h,   h, h, h,   1, 0, 0);
    add_face(0-h, 0-h, 0-h,   0-h, 0-h, h,   0-h, h, h,   0-h, h, 0-h,   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;
}

/* 在第 row 行上数"颜色正好是 rgb"的像素个数 —— 方块是**自发光**的，颜色逐位就是基色。
 * Count, on row `row`, the pixels whose colour is exactly `rgb` - the boxes are **emissive**, so the
 * colour is bit-exactly the base colour. */
int count_row(int vp, int row, int r, int g, int b) {
    int x; int n; int c;
    n = 0;
    for (x = 0; x < VW; x = x + 1) {
        c = ui_3d_viewport_get_pixel(vp, x, row);
        if (rch(c) == r && gch(c) == g && bch(c) == b) n = n + 1;
    }
    return n;
}

int main(void) {
    int vp; int mesh; int oa; int ob; int r; int c;
    int rowA; int rowB; int sc;
    int wA_p; int wB_p; int wA_o; int wB_o;
    int a_p; int a_o; int a_o_fog; int b_o_fog;

    g_fails = 0;
    ui_win_open_ex("正交投影 / Orthographic", 0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD);

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

    ui_3d_viewport_set_camera(vp, 0.0f, 0.0f, 600.0f, 0.0f, 0.0f, 0.0f);
    ui_3d_viewport_set_fov(vp, 60.0f, 1.0f, 4000.0f);
    ui_3d_viewport_set_bg(vp, argb(15, 15, 15));
    ui_2d_clear(0);

    cube(BOX_HALF);
    mesh = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
    ck("cube mesh rc", mesh > 0, mesh, 0);

    /* 两个**一模一样**的方块：近的偏左、远的偏右（错开是为了不互相遮挡）。
     * Two **identical** boxes: the near one to the left, the far one to the right (offset so that neither
     * occludes the other). */
    oa = ui_3d_obj_create(vp, mesh);
    ui_3d_obj_set_pos(oa, NEAR_X, 0.0f, 0.0f);
    ui_3d_obj_set_color(oa, RED_BOX);
    ui_3d_obj_set_emissive(oa, 1);
    ob = ui_3d_obj_create(vp, mesh);
    ui_3d_obj_set_pos(ob, FAR_X, 0.0f, FAR_Z);
    ui_3d_obj_set_color(ob, BLUE_BOX);
    ui_3d_obj_set_emissive(ob, 1);

    sc = ui_3d_obj_get_screen(oa);
    rowA = (sc >> 16) & 0xFFFF;
    sc = ui_3d_obj_get_screen(ob);
    rowB = (sc >> 16) & 0xFFFF;
    printf("ROWS near=%d far=%d\n", rowA, rowB);

    /* ── ① 透视基线：近的**更宽** ──────────────────────────────────────────── */
    /* -- ① Perspective baseline: the near box is **wider** -- */
    wA_p = count_row(vp, rowA, 255, 0, 0);
    wB_p = count_row(vp, rowB, 0, 0, 255);
    a_p = ui_3d_viewport_get_pixel(vp, 160 - (int)(120.0f * 1.732f / 600.0f * 160.0f), rowA);
    printf("PERSPECTIVE near_w=%d far_w=%d\n", wA_p, wB_p);
    ck("1 perspective: near box is visibly wider", wA_p > wB_p + 8, wA_p, wB_p);

    /* ── ② 正交：两个**一样宽** ────────────────────────────────────────────── */
    /* -- ② Orthographic: both are the **same width** -- */
    r = ui_3d_viewport_set_ortho(vp, ORTHO_HH);
    ck("2 set_ortho rc", r == 0, r, 0);
    wA_o = count_row(vp, rowA, 255, 0, 0);
    wB_o = count_row(vp, rowB, 0, 0, 255);
    printf("ORTHO near_w=%d far_w=%d\n", wA_o, wB_o);
    ck("2 ortho: the two boxes are the same width", wA_o - wB_o <= 1 && wB_o - wA_o <= 1, wA_o, wB_o);
    /* 期望值 = 2 × BOX_HALF / (2 × ORTHO_HH / VW) = 80 / 1.25 = 64 像素。
     * Expected = 2 x BOX_HALF / (2 x ORTHO_HH / VW) = 80 / 1.25 = 64 pixels. */
    ck("2 ortho: width matches the declared half-height", wA_o > 58 && wA_o < 70, wA_o, 64);

    /* ── ③ 正交 + 雾：雾按"到相机的距离"走，不是整片纯雾色 ────────────────────── */
    /* -- ③ Orthographic + fog: fog follows the distance to the camera, it is not a blanket colour -- */
    a_o = ui_3d_viewport_get_pixel(vp, 160 - 96, rowA);      /* 近方块中心（正交下 x = 160 − 120/1.25）*/
    /* the near box's centre; under ortho that is x = 160 - 120/1.25 */
    r = ui_3d_viewport_set_fog(vp, argb(0, 0, 0), 500.0f, 1200.0f);
    ck("3 set_fog rc", r == 0, r, 0);
    a_o_fog = ui_3d_viewport_get_pixel(vp, 160 - 96, rowA);
    b_o_fog = ui_3d_viewport_get_pixel(vp, 160 + 96, rowB);
    printf("ORTHO+FOG near=(%d,%d,%d) far=(%d,%d,%d)\n",
           rch(a_o_fog), gch(a_o_fog), bch(a_o_fog), rch(b_o_fog), gch(b_o_fog), bch(b_o_fog));
    /* 距离走**物体原点**：近 = |(-120,0,0) − (0,0,600)| ≈ 611.9 ⇒ f ≈ 0.160 ⇒ 红 ≈ 214。
     * Distances use the **object origin**: near ~611.9, so f ~0.160 and red ~214.
     * 远 = |(120,0,-400) − (0,0,600)| ≈ 1007.1 ⇒ f ≈ 0.724 ⇒ 蓝 ≈ 70。
     * far ~1007.1, so f ~0.724 and blue ~70. */
    ck("3 fog is NOT a blanket colour (near end still bright)",
       rch(a_o_fog) > 190 && rch(a_o_fog) < 240, rch(a_o_fog), 214);
    ck("3 far end is fogged much harder", bch(b_o_fog) > 55 && bch(b_o_fog) < 85, bch(b_o_fog), 70);
    ck("3 near is less fogged than far", rch(a_o_fog) > bch(b_o_fog) + 100, rch(a_o_fog), bch(b_o_fog));
    ck("3 no fog on the near box would be wrong too", rch(a_o_fog) != rch(a_o), rch(a_o_fog), rch(a_o));
    ui_3d_viewport_set_fog(vp, argb(0, 0, 0), 0.0f, 0.0f);   /* 关雾（far <= near）*/
    /* fog off (far <= near) */

    /* ── ④ 关掉正交 ⇒ **逐位**回到透视基线 ────────────────────────────────── */
    /* -- ④ Turning ortho off returns **bit-identically** to the perspective baseline -- */
    r = ui_3d_viewport_set_ortho(vp, 0.0f);
    ck("4 clear ortho rc", r == 0, r, 0);
    {
        int wA2; int a2;
        wA2 = count_row(vp, rowA, 255, 0, 0);
        a2 = ui_3d_viewport_get_pixel(vp, 160 - (int)(120.0f * 1.732f / 600.0f * 160.0f), rowA);
        printf("BACK TO PERSPECTIVE near_w=%d\n", wA2);
        ck("4 ortho off -> identical to the perspective baseline",
           wA2 == wA_p && a2 == a_p, wA2, wA_p);
    }

    /* ── ⑤ 参数校验 ───────────────────────────────────────────────────────── */
    /* -- ⑤ Parameter validation -- */
    r = ui_3d_viewport_set_ortho(9999, 100.0f);
    ck("5 bad viewport rejected", r < 0, r, 0);
    r = ui_3d_viewport_set_ortho(vp, 0.0f / 0.0f);
    ck("5 NaN half-height rejected", r < 0, r, 0);
    /* ⚠ **负数是"关"，不是"错"**（与 `#636` 的 `far <= near` 同一条约定）。
     * ⚠ A **negative value means "off", not "wrong"** (same convention as `#636`'s `far <= near`).
     * 于是"没调过"与"显式关掉"是同一条路径，老程序画面逐位不变。
     * So "never called" and "explicitly off" are one path, and old programs stay bit-identical. */
    r = ui_3d_viewport_set_ortho(vp, 0.0f - 5.0f);
    ck("5 negative = off (accepted, not an error)", r == 0, r, 0);

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