// Per-face texture-projection selection oracle — CPU mirror of the bake pass.
//
// Stage-2 texturing (per-face GPU channel). Stage 1 gave every cell the brush's
// DOMINANT material_id, so a brush whose dominant texture is metal paints ALL its
// faces metal — including the one face carrying the START.MAP "quake" logo. The fix:
// per cell, pick the projection of the DOMINANT FACE = the most-coplanar (min |fd|)
// contributing face of the dominant brush. A cell sitting on the logo face is most
// coplanar with the logo plane, so it selects the logo projection even though 5 of
// the brush's 6 faces are metal.
//
// This oracle models exactly that: one axis box, 6 faces. Five faces = material METAL
// (proj 10), the +Z front face = material LOGO (proj 77). We march the surface cells
// of the front face and of a side face and check which projection each selects.
//
// OLD — brush dominant material (metal wins 5:1) → EVERY cell selects metal (10).
// FIX — min-|fd| contributing face of the dominant brush → front cells select the
// logo (77), side cells select metal (10).
//
// PASS if FIX gives 77 on the front face and 10 on the side, while OLD gives 10 on both
// (i.e. the logo is only reachable via the per-face pick). Mirrors the onbrush/T capture
// logic of shaders/bake_planeset.glsl byte-for-byte so the ported GLSL matches.
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
struct V3 { float x, y, z; };
static V3 operator-(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
static float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
// Face = outward plane (n·p = d) + its per-face projection index + material.
struct Face { V3 n; float d; unsigned proj; };
struct Brush { std::vector<Face> f; };
// A cell's dominant-face proj via the bake logic, restricted to one brush (the only
// brush here). Returns {old_proj, fix_proj} where old = brush dominant material's proj.
struct Pick { unsigned old_proj; unsigned fix_proj; bool on_surface; };
static Pick pick_cell(const Brush& b, V3 c, float T, unsigned brush_dominant_proj) {
Pick out{brush_dominant_proj, 0u, false};
float best_absfd = 1e30f;
for (size_t pi = 0; pi < b.f.size(); pi++) {
const Face& f = b.f[pi];
float fd = dot(f.n, c) - f.d;
if (std::fabs(fd) >= T) continue;
// onbrush: cell inside every OTHER face (within T) → this is a real surface face
bool onbrush = true;
for (size_t pj = 0; pj < b.f.size(); pj++) {
if (pj == pi) continue;
if (dot(b.f[pj].n, c) - b.f[pj].d > T) { onbrush = false; break; }
}
if (!onbrush) continue;
out.on_surface = true;
// FIX: dominant face = the most-coplanar contributing face (smallest |fd|).
if (std::fabs(fd) < best_absfd) { best_absfd = std::fabs(fd); out.fix_proj = f.proj; }
}
return out;
}
int main() {
// Axis box centred at origin, half-extent 2. Outward faces, n·p = d.
// +Z (front) = LOGO (proj 77); all others = METAL (proj 10).
const unsigned METAL = 10u, LOGO = 77u;
Brush box;
box.f.push_back({{ 1, 0, 0}, 2.0f, METAL}); // +X
box.f.push_back({{-1, 0, 0}, 2.0f, METAL}); // -X
box.f.push_back({{ 0, 1, 0}, 2.0f, METAL}); // +Y
box.f.push_back({{ 0,-1, 0}, 2.0f, METAL}); // -Y
box.f.push_back({{ 0, 0, 1}, 2.0f, LOGO }); // +Z (front — the logo face)
box.f.push_back({{ 0, 0,-1}, 2.0f, METAL}); // -Z
float voxel = 0.25f; // representative cell size
float T = voxel * 1.2f; // capture band (matches bake_planeset T)
// March cells across the FRONT face (z ≈ +2), interior of the face (avoid the very
// edges/corners where two faces tie). x,y in (-1.5 .. 1.5).
int front_ok_fix = 0, front_ok_old = 0, front_n = 0;
for (float x = -1.5f; x <= 1.5f; x += 0.5f)
for (float y = -1.5f; y <= 1.5f; y += 0.5f) {
V3 c{x, y, 2.0f - voxel * 0.5f}; // just inside the +Z face
Pick p = pick_cell(box, c, T, METAL);
if (!p.on_surface) continue;
front_n++;
if (p.fix_proj == LOGO) front_ok_fix++;
if (p.old_proj == LOGO) front_ok_old++;
}
// March cells across a SIDE face (+X, x ≈ +2), interior.
int side_ok_fix = 0, side_bad_fix = 0, side_n = 0;
for (float y = -1.5f; y <= 1.5f; y += 0.5f)
for (float z = -1.5f; z <= 1.5f; z += 0.5f) {
V3 c{2.0f - voxel * 0.5f, y, z}; // just inside the +X face
Pick p = pick_cell(box, c, T, METAL);
if (!p.on_surface) continue;
side_n++;
if (p.fix_proj == METAL) side_ok_fix++;
if (p.fix_proj == LOGO) side_bad_fix++;
}
printf("Per-face projection selection oracle (logo face on a metal-dominant brush):\n");
printf("front face cells=%2d FIX->logo=%2d OLD->logo=%2d\n", front_n, front_ok_fix, front_ok_old);
printf("side face cells=%2d FIX->metal=%2d FIX->logo(bad)=%2d\n", side_n, side_ok_fix, side_bad_fix);
bool pass = front_n > 0 && side_n > 0
&& front_ok_fix == front_n // every front cell picks the logo
&& front_ok_old == 0 // brush-dominant NEVER reaches the logo
&& side_ok_fix == side_n // every side cell stays metal
&& side_bad_fix == 0;
printf("\n%s: FIX selects the per-face logo on the front and metal on the side;\n"
" brush-dominant (OLD) can never show the logo.\n", pass ? "PASS" : "FAIL");
return pass ? 0 : 1;
}