c/3d/cube_min.c · 10156 字节 · 下载原文
/* cube_min.c —— **最小立方体**:只验"接线通不通",不验观感。
* cube_min.c — **smallest cube**: checks the wiring only, not the looks.
*
* 存在的理由:`landscape.c` 在 Windows/MAUI 上开得出窗口却一路卡住、一条判据都到不了。
* Why this exists: on Windows/MAUI `landscape.c` opens its window then hangs, reaching no criterion at all.
*
* 那种时候**要的不是更复杂的场景,是更少的东西** —— 这里只留 2D 清屏 → 建视口 → 建网格 → 画 → 读一个像素。
* What that calls for is **not a bigger scene but a smaller one**: clear 2D, make a viewport, a mesh, draw, read one pixel.
*
* ⚠ 每一步都**覆盖写**一个进度文件;卡住时那个文件的最后一行就是它走到哪儿了。
* ⚠ Every step **overwrites** a progress file, so on a hang its last line says how far it got.
*
* 这条通道是给"画布画不出来"准备的 —— 结果不经过窗口,直接落盘,`adb pull`/资源管理器都读得到。
* That channel exists for when the canvas shows nothing: results land on disk, bypassing the window entirely.
*
* 机器判读:逐行 `PASS …` / `FAIL …`,末行 `CUBE_MIN PASS|FAIL (fails=N)`。
* Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `CUBE_MIN PASS|FAIL (fails=N)`.
*/
#include <vml_ui.h>
#include <vml_3d.h>
#include <math.h>
#include <stdio.h>
#include <file.h>
int g_fails;
/* ⚠ 句柄 **0 是合法值**(实测 `fopen` 成功返回 0)⇒ 判"开没开成"必须用**负值**。
* ⚠ Handle **0 is valid** (measured: a successful `fopen` returns 0), so test for a **negative** value. */
int g_step = -2;
int g_res = -2;
void wr(int h, char* s) {
int n;
if (h < 0) return;
n = 0;
while (s[n] != 0) n = n + 1;
fwrite(h, s, n);
}
/* 进度:覆盖写,所以文件里永远是**最后到达的那一步**。
* Progress: overwritten each time, so the file always holds the **last step reached**. */
void mark(char* s) {
int f;
f = fopen("_cube_step.txt", 1);
wr(f, s);
if (f >= 0) fclose(f);
}
void ck(char* tag, int cond, int a, int b) {
char line[160];
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); }
sprintf(line, "%s %s %d %d\n", cond ? "PASS" : "FAIL", tag, a, b);
wr(g_res, line);
}
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; }
int lum(int c) { return rch(c) + gch(c) + bch(c); }
/* ══ 网格:一个立方体(每面各自一组顶点 ⇒ 每面常量法线)═════════════════════ */
/* == Mesh: one cube, four vertices per face so every face gets a constant normal == */
/* 顶点 = pos3 + nrm3 + uv2 = 8 个 float32;索引 int32 紧随其后。
* Vertex = pos3 + nrm3 + uv2 = 8 float32; int32 indices follow immediately after. */
/* 24 个顶点 × 8 + 36 个索引 = **228** 个 float,取 512 留足余量。
* 24 vertices x 8 + 36 indices = **228** floats, rounded up to 512 for headroom. */
#define MB_CAP 512
float MB[MB_CAP];
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;
}
/* ⚠⚠ **索引必须等全部顶点写完再写** —— `mb_index` 落笔在 `ip[g_vn * 8 + k]`,
* ⚠⚠ **Indices must be written only after every vertex is in place** - `mb_index` lands at `ip[g_vn * 8 + k]`.
*
* 也就是"紧跟全部顶点之后"。边写顶点边写索引 ⇒ 索引落进**后面还没写的顶点区**,接着被顶点覆盖掉。
* That is, immediately after all vertices. Interleaving them drops the indices into the not-yet-written vertex area, which then overwrites them.
*
* 症状:索引缓冲里全是顶点数据 ⇒ 画出**一堆垃圾三角形**,而 `mesh_create` 照常返回句柄、不报任何错。
* Symptom: the index buffer holds vertex data, so it draws **garbage triangles** while `mesh_create` returns a handle and reports nothing.
*
* 实测(2026-10-08):立方体被画成屏幕左侧一条窄长的平行四边形,而 `obj_get_screen` 照旧报屏幕正中。
* Measured 2026-10-08: the cube came out as a narrow parallelogram at the left edge while `obj_get_screen` still reported dead centre. */
/* 棱长 2(±1),中心在原点 —— 六个面各给一个朝外的常量法线。
* Edge length 2 (+/-1) centred at the origin; each of the six faces gets an outward constant normal. */
void gen_cube(void) {
int f;
g_vn = 0;
g_in = 0;
add_face(0 - 1, 0 - 1, 1, 1, 0 - 1, 1, 1, 1, 1, 0 - 1, 1, 1, 0, 0, 1);
add_face(1, 0 - 1, 0 - 1, 0 - 1, 0 - 1, 0 - 1, 0 - 1, 1, 0 - 1, 1, 1, 0 - 1, 0, 0, 0 - 1);
add_face(0 - 1, 1, 1, 1, 1, 1, 1, 1, 0 - 1, 0 - 1, 1, 0 - 1, 0, 1, 0);
add_face(0 - 1, 0 - 1, 0 - 1, 1, 0 - 1, 0 - 1, 1, 0 - 1, 1, 0 - 1, 0 - 1, 1, 0, 0 - 1, 0);
add_face(1, 0 - 1, 1, 1, 0 - 1, 0 - 1, 1, 1, 0 - 1, 1, 1, 1, 1, 0, 0);
add_face(0 - 1, 0 - 1, 0 - 1, 0 - 1, 0 - 1, 1, 0 - 1, 1, 1, 0 - 1, 1, 0 - 1, 0 - 1, 0, 0);
/* 顶点齐了(24 个)再写 36 个索引,每个面两个三角形。
* With all 24 vertices in place, write the 36 indices: two triangles per face. */
for (f = 0; f < 6; f = f + 1) {
int b;
int k;
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;
}
int main(void) {
int w; int h; int vp; int mesh; int obj;
int r; int sc; int px; int py; int c;
int lang;
g_fails = 0;
g_res = fopen("_cube_result.txt", 1);
lang = ui_scr_get_language();
mark("1 lang");
ui_win_open_ex(lang == 0 ? "立方体最小例" : "Cube Minimal",
0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD);
mark("2 window");
/* 尺寸必须**开窗之后**用 `_ex` 问(先问会矮一截、视口贴左上角)。
* The size must be asked **after** opening, via `_ex` (asking earlier comes up short and pins the viewport to the corner). */
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);
mark("3 size");
if (w < 64 || h < 64) {
ck("screen size sane", 0, w, h);
mark("9 EXIT bad size");
if (g_res >= 0) fclose(g_res);
return 99;
}
ck("screen size sane", 1, w, h);
vp = ui_3d_viewport_create(0, 0, w, h, 0);
mark("4 viewport_create");
ck("viewport -> handle", vp > 0, vp, 0);
ui_3d_viewport_set_camera(vp, 0.0f, 0.0f, 6.0f, 0.0f, 0.0f, 0.0f);
ui_3d_viewport_set_fov(vp, 55.0f, 0.1f, 100.0f);
ui_3d_viewport_set_bg(vp, argb(20, 28, 40));
ui_3d_viewport_set_light(vp, 0.4f, 0.9f, 0.6f, argb(255, 255, 255), 0.25f);
mark("5 viewport_setup");
/* 2D 层必须清成透明 —— 3D 视口是**透过 2D 层**看的,不清就是一片黑。
* The 2D layer must be cleared to transparent: the 3D viewport shows **through** it, and an opaque layer is a black screen. */
ui_2d_clear(0);
mark("6 2d_clear");
gen_cube();
ck("mesh buffer fits", g_vn * 8 + g_in <= MB_CAP, g_vn * 8 + g_in, MB_CAP);
mesh = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
mark("7 mesh_create");
ck("mesh -> handle", mesh > 0, mesh, 0);
obj = ui_3d_obj_create(vp, mesh);
ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f);
ui_3d_obj_set_color(obj, argb(210, 120, 60));
ui_3d_obj_set_emissive(obj, 1);
mark("8 obj_create");
ck("obj -> handle", obj > 0, obj, 0);
/* 自证:立方体真在屏幕里 —— "物体在画面外"既不报错也不崩,只能直接量。
* Self-check: the cube really is on screen; "object off screen" neither errors nor crashes, so measure it. */
sc = ui_3d_obj_get_screen(obj);
px = sc & 0xFFFF; py = (sc >> 16) & 0xFFFF;
mark("9 obj_get_screen");
printf("OBJSCREEN=%d,%d\n", px, py);
ck("cube centre on screen", px > 0 && px < w && py > 0 && py < h, px, py);
ui_2d_present();
mark("10 first present");
c = ui_3d_viewport_get_pixel(vp, px, py);
mark("11 get_pixel");
printf("CENTRE=%d r=%d g=%d b=%d\n", c, rch(c), gch(c), bch(c));
/* 自发光 ⇒ 片元直接输出基色 ⇒ 读回来的应当就是那个橙色(210,120,60)那一族。
* Emissive makes the fragment output the base colour directly, so read-back should be that orange family. */
ck("centre pixel is the cube colour", rch(c) > 120 && gch(c) > 60 && bch(c) < 120, rch(c), bch(c));
/* ⚠ 这一条才是"真画了东西"的判据:空视口读回的是清屏底色(20,28,40),不是基色。
* ⚠ This is the "something was really drawn" criterion: an empty viewport reads back the clear colour (20,28,40). */
ck("centre differs from background", c != argb(20, 28, 40), c, argb(20, 28, 40));
mark("12 done");
printf("CUBE_MIN %s (fails=%d)\n", g_fails == 0 ? "PASS" : "FAIL", g_fails);
if (g_res >= 0) fclose(g_res);
return g_fails;
}