c/3d/ortho_demo.c · 13057 字节 · 下载原文
/* 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;
}