// Stage 2 helper: cubemap -> equirect (NEAREST, for binary masks). // // The exact inverse of `equirect_to_cubemap.wgsl`'s `xyzcube` geometry, using a // gnomonic (tangent) projection: the cube-surface coordinate for a face is // 0.5 * (in-plane component / face-normal component), which inverts the linear // cube-surface grid used by e2c. For each equirect output pixel we compute the // 3D direction from (lon, lat), pick the dominant-axis cube face (F/R/B/L/U/D), // invert that face's `xyzcube` mapping to a face pixel (j, i), and nearest- // sample the matching face mask texture. This is the consistent inverse of our // e2c kernel (round-trip e2c -> c2e reproduces the source within edge loss), // and is close to py360convert.c2e(..., mode="nearest"). // // Bindings 1..6 are the six face mask textures in FACES order [F,R,B,L,U,D], // each `face_n x face_n`, RGBA8 (mask value replicated across RGB). struct Params { out_w: u32, out_h: u32, face_n: u32, pad: u32, } @group(0) @binding(0) var params: Params; @group(0) @binding(1) var faceF: texture_2d; @group(0) @binding(2) var faceR: texture_2d; @group(0) @binding(3) var faceB: texture_2d; @group(0) @binding(4) var faceL: texture_2d; @group(0) @binding(5) var faceU: texture_2d; @group(0) @binding(6) var faceD: texture_2d; @group(0) @binding(7) var out_tex: texture_storage_2d; const PI: f32 = 3.141592653589793; fn sample_face(id: u32, j: i32, i: i32) -> vec4 { let n = i32(params.face_n); let cj = clamp(j, 0, n - 1); let ci = clamp(i, 0, n - 1); switch id { case 0u: { return textureLoad(faceF, vec2(cj, ci), 0); } case 1u: { return textureLoad(faceR, vec2(cj, ci), 0); } case 2u: { return textureLoad(faceB, vec2(cj, ci), 0); } case 3u: { return textureLoad(faceL, vec2(cj, ci), 0); } case 4u: { return textureLoad(faceU, vec2(cj, ci), 0); } case 5u: { return textureLoad(faceD, vec2(cj, ci), 0); } default: { return vec4(0.0); } } } @compute @workgroup_size(8, 8, 1) fn main(@builtin(global_invocation_id) gid: vec3) { let w = params.out_w; let h = params.out_h; if (gid.x >= w || gid.y >= h) { return; } let px = f32(gid.x); let py = f32(gid.y); let u_ = ((px + 0.5) / f32(w) - 0.5) * 2.0 * PI; // lon let v_ = -((py + 0.5) / f32(h) - 0.5) * PI; // lat let cv = cos(v_); let dx = cv * sin(u_); let dy = sin(v_); let dz = cv * cos(u_); let ax = abs(dx); let ay = abs(dy); let az = abs(dz); let nm1 = f32(params.face_n - 1u); var face_id: u32; var jf: f32; var if_: f32; if (ay >= ax && ay >= az) { if (dy > 0.0) { // U: j=0.5*(x/y+1)*nm1, i=0.5*(z/y+1)*nm1 face_id = 4u; jf = 0.5 * (dx / dy + 1.0) * nm1; if_ = 0.5 * (dz / dy + 1.0) * nm1; } else { // D: j=0.5*(1-x/y)*nm1, i=0.5*(1+z/y)*nm1 face_id = 5u; jf = 0.5 * (1.0 - dx / dy) * nm1; if_ = 0.5 * (1.0 + dz / dy) * nm1; } } else if (ax >= az) { if (dx > 0.0) { // R: j=0.5*(1-z/x)*nm1, i=0.5*(1-y/x)*nm1 face_id = 1u; jf = 0.5 * (1.0 - dz / dx) * nm1; if_ = 0.5 * (1.0 - dy / dx) * nm1; } else { // L: j=0.5*(1-z/x)*nm1, i=0.5*(1+y/x)*nm1 face_id = 3u; jf = 0.5 * (1.0 - dz / dx) * nm1; if_ = 0.5 * (1.0 + dy / dx) * nm1; } } else { if (dz > 0.0) { // F: j=0.5*(x/z+1)*nm1, i=0.5*(1-y/z)*nm1 face_id = 0u; jf = 0.5 * (dx / dz + 1.0) * nm1; if_ = 0.5 * (1.0 - dy / dz) * nm1; } else { // B: j=0.5*(x/z+1)*nm1, i=0.5*(1+y/z)*nm1 face_id = 2u; jf = 0.5 * (dx / dz + 1.0) * nm1; if_ = 0.5 * (1.0 + dy / dz) * nm1; } } let color = sample_face(face_id, i32(round(jf)), i32(round(if_))); textureStore(out_tex, vec2(gid.xy), vec4(color.rgb, 1.0)); }