/* skybox_radius.c —— **天空盒半径 180 = 可见世界半径**，两个方向各一条判据。
 * skybox_radius.c - **the skybox radius of 180 is the visible world radius**; one criterion per direction.
 *
 * 存在的理由：天空盒不是"无限远的背景"，而是**真的放在世界里**的一个半径 180 的盒子（本仓没有"无限远"机制）。
 * Why this exists: the skybox is not an infinitely far background; it is a real box of radius 180 placed in the
 * world (this repo has no "infinity" mechanism).
 *
 * 盒子**以相机为中心**（见 `Vml3dRetained.RenderViewport`），于是它与深度测试一起把"看得见的世界"钉成一个球。
 * The box is **centred on the camera** (see `Vml3dRetained.RenderViewport`), so together with depth testing it pins the visible world to a ball.
 * ⚠ **距相机 180 以内的几何看得见、180 以外的被后壁挡住**。
 * ⚠ **Geometry within 180 of the camera shows; anything farther is hidden by the far wall.**
 *
 * ⚠ 在加这条判据之前，**两个方向都没有人守**：`landscape.c` 的注释写着"一切都在 180 以内"，但没有任何一条判据会因为它变红。
 * ⚠ Before this file, **neither direction was guarded**: `landscape.c` comments that everything stays within 180, but no criterion went red when it changed.
 * 半径被改错时（太大 ⇒ 远处不再被挡；太小 ⇒ 近处被吃掉）**一声不响**。
 * A wrong radius (too large hides nothing, too small eats the near geometry) went completely silent.
 *
 * ⚠ 注释里那句"半径必须小于 zfar 减去相机距离"是**旧话**（盒子已改为跟随相机，`zfar` 不再是约束）。
 * ⚠ The old note "the radius must be less than zfar minus the camera distance" is **stale**: the box now
 * follows the camera, so `zfar` is no longer the binding constraint.
 *
 * 机器判读：逐行 `PASS …` / `FAIL …`，末行 `SKYBOX_RADIUS PASS|FAIL (fails=N)`。
 * Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `SKYBOX_RADIUS 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); }
}

/* 一格颜色是不是那个 RGB（忽略 alpha）。
 * Is a colour channel triple that RGB (alpha ignored)? */
int is_rgb(int c, int r, int g, int b) { return rch(c) == r && gch(c) == g && bch(c) == b; }

/* ══ 网格缓冲：一张 60x60 的方片，法线 +Z（正对相机），自发光靠调用方设 ══════════ */
/* == Mesh buffer: one 60x60 quad, normal +Z (facing the camera); emissive is set by the caller == */
float MB[512];
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`, right after the vertices). */
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 gen_quad(float half) {
    g_vn = 0; g_in = 0;
    mb_vertex_uv(0, 0 - half, 0 - half, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
    mb_vertex_uv(1, half, 0 - half, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
    mb_vertex_uv(2, half, half, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
    mb_vertex_uv(3, 0 - half, half, 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;
}

/* 盒子半径（世界单位）—— 与宿主 `Vml3dRetained.SkyboxRadius` **必须一致**，下面两条探针距离围着它取。
 * Box radius in world units; it **must match** the host's `Vml3dRetained.SkyboxRadius`. The two probe
 * distances below are chosen around it. */
#define SKY_RADIUS 180.0f

int main(void) {
    int w; int h; int vp; int mesh; int obj; int img;
    int r; int c; int c_near; int c_far;

    g_fails = 0;
    ui_win_open_ex("天空盒半径 / Skybox radius", 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);
    if (w <= 0) w = 393;
    if (h <= 0) h = 757;

    /* 一张**纯蓝**的等距圆柱图：任何方向采到的都是同一个色 ⇒ 与"红方片"一眼分得开。
     * A **solid blue** equirectangular image: every direction samples the same colour, so it is
     * unmistakable against the red quad. */
    ui_2d_clear(0);
    ui_2d_rect(0, 0, 32, 16, argb(30, 30, 200), 1, 0, 0);
    ui_2d_present();
    img = ui_2d_get_image(0, 0, 32, 16);
    ck("skybox image -> handle", img > 0, img, 0);
    if (img <= 0) { printf("SKYBOX_RADIUS FAIL (fails=1)\n"); return 1; }

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

    /* 相机在原点、看向 -Z：探针方片就摆在 -Z 上，距离 = |z| —— 读数好解释。
     * The camera sits at the origin looking down -Z, so the probe quad sits on -Z and its distance is |z|,
     * which keeps the readings easy to read. */
    ui_3d_viewport_set_camera(vp, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f - 1.0f);
    ui_3d_viewport_set_fov(vp, 60.0f, 1.0f, 8000.0f);
    ui_3d_viewport_set_bg(vp, argb(10, 10, 10));
    ui_2d_clear(0);

    gen_quad(30.0f);
    ck("mesh buffer fits", g_vn * 8 + g_in <= 512, g_vn * 8 + g_in, 512);
    mesh = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
    ck("mesh -> handle", mesh > 0, mesh, 0);

    obj = ui_3d_obj_create(vp, mesh);
    ck("obj -> handle", obj > 0, obj, 0);
    /* 自发光 ⇒ 读回来的就是那个橙色（255,0,0）那一族，与光照方向无关。
     * Emissive, so the read-back is that red family regardless of the light direction. */
    ui_3d_obj_set_emissive(obj, 1);
    ui_3d_obj_set_color(obj, argb(255, 0, 0));

    /* ── 基线：没有天空盒时，远近两个距离都看得见（深色方片）──────────────────────
     * -- Baseline: with no skybox, both the near and the far quad are visible (red) -- */
    ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f - (SKY_RADIUS * 0.55f));
    c_near = ui_3d_viewport_get_pixel(vp, 128, 128);
    ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f - (SKY_RADIUS * 2.2f));
    c_far = ui_3d_viewport_get_pixel(vp, 128, 128);
    ck("no skybox -> near quad visible", is_rgb(c_near, 255, 0, 0), c_near, argb(255, 0, 0));
    ck("no skybox -> far quad visible", is_rgb(c_far, 255, 0, 0), c_far, argb(255, 0, 0));

    /* ── 开天空盒 ────────────────────────────────────────────────────────────
     * -- Turn the skybox on -- */
    r = ui_3d_viewport_set_skybox(vp, img);
    ck("set_skybox rc", r == 0, r, 0);

    /* 角落（天上、没有几何）应当是天空盒那个蓝 —— 先证明**盒子真的在画**。
     * The corner (sky, no geometry) must be the skybox blue; this first proves **the box is really drawn**.
     * 少了这一条，"半径外被挡住"会以"整个视口都是清屏色"的样子假绿。
     * Without it, "outside is occluded" would go green spuriously as "the whole viewport is the clear colour". */
    c = ui_3d_viewport_get_pixel(vp, 4, 4);
    ck("skybox drew something (corner is sky)", is_rgb(c, 30, 30, 200), c, argb(30, 30, 200));

    /* ① 半径**以内**的东西看得见：盒子的前壁在相机前面，方片在它之前 ⇒ 赢深度测试。
     * ① Geometry **within** the radius shows: the box front wall is in front of the camera and the quad is
     *    ahead of it, so it wins the depth test. */
    ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f - (SKY_RADIUS * 0.55f));
    c_near = ui_3d_viewport_get_pixel(vp, 128, 128);
    ck("skybox: inside radius still visible", is_rgb(c_near, 255, 0, 0), c_near, argb(255, 0, 0));

    /* ② 半径**以外**的东西被后壁挡住：方片在盒子之外 ⇒ 读到的是天空盒的颜色，不是红。
     * ② Geometry **beyond** the radius is hidden by the far wall: the quad lies outside the box, so the
     *    read-back is the skybox colour, not red. */
    ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f - (SKY_RADIUS * 2.2f));
    c_far = ui_3d_viewport_get_pixel(vp, 128, 128);
    ck("skybox: outside radius is occluded", is_rgb(c_far, 30, 30, 200), c_far, argb(30, 30, 200));

    /* ── 反向：取消天空盒 ⇒ 远处那个方片**逐位**回到基线（老程序不受影响那条保证）──
     * -- Reverse: clearing the skybox brings the far quad **bit-identically** back to the baseline (the
     *    "old programs are unaffected" guarantee) -- */
    r = ui_3d_viewport_set_skybox(vp, 0);
    ck("clear skybox rc", r == 0, r, 0);
    c = ui_3d_viewport_get_pixel(vp, 128, 128);
    ck("skybox off -> far quad visible again (bit-identical)", c == argb(255, 0, 0), c, argb(255, 0, 0));

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