/** * @fileoverview Differentiable 3D->2D Gaussian PROJECTION (EWA splatting) — STAGE 2 of the * sovereign HoloScript 3DGS trainer. Composes with GaussianTrainer2D (the gradient-checked * alpha-blend core): this module maps real 3D gaussian parameters to the 2D raster * representation and back, so the optimizer can train means / scales / quaternions directly * from posed camera views — no Python gsplat, no remote cloud. * * forward3D : {mean3d, scale, quat, opacity, color} + camera -> Gaussian2D {posx,posy,a,b,c,...} * backward3D: dL/d Gaussian2D (from backward2D) -> dL/d{mean3d, scale, quat} (op,color pass through) * * The projection MIRRORS shaders/splat-compress.wgsl computeCov2D EXACTLY (quat->R, M=R*diag(scale), * Sigma=M Mᵀ, T=V Sigma Vᵀ, J, cov2d, 0.3px low-pass dilation, conic=inv(cov2d)). This is the CPU * parity spec the eventual WGSL trainer kernels are checked against. * * The backward uses standard matrix-calculus identities under the "independent-entry" convention * (off-diagonal scalar gradient split half onto each mirror at the conic→cov boundary): * Y = A X Aᵀ (X sym) => dL/dX = Aᵀ (dL/dY) A , dL/dA = (dL/dY + dL/dYᵀ) A X * which avoids the symmetric-matrix factor-of-2 traps that hand-rolled coefficient lists invite. * Verified against finite differences (worst rel err ~3e-6) in GaussianTrainer3D.test.ts. * * Stage 1 = GaussianTrainer2D (alpha-blend autodiff). Stage 2 = this (projection chain). * Stage 3 = WGSL port of the hot path onto the forward GaussianSplatSorter. * * LIMITATION (per the 2026-06-20 /critic review): color is flat per-gaussian RGB — there is NO * spherical-harmonics / view-dependent appearance. Color passes through each view unchanged, so this * cannot fit specular / view-dependent effects that real 3DGS uses SH (degree 3) for. A multi-view * fit against targets rendered by this same forward pass cannot expose this; real captures will. */ import { type Gaussian2D, type Gaussian2DGrad } from './GaussianTrainer2D'; /** SoA buffers for N 3D gaussians (raw quat — NOT assumed normalized, matching the shader). */ export interface Gaussian3D { N: number; x: Float64Array; y: Float64Array; z: Float64Array; sx: Float64Array; sy: Float64Array; sz: Float64Array; qr: Float64Array; qx: Float64Array; qy: Float64Array; qz: Float64Array; op: Float64Array; r: Float64Array; gr: Float64Array; bl: Float64Array; } /** Per-parameter gradient buffers for the 3D parameters. */ export interface Gaussian3DGrad { x: Float64Array; y: Float64Array; z: Float64Array; sx: Float64Array; sy: Float64Array; sz: Float64Array; qr: Float64Array; qx: Float64Array; qy: Float64Array; qz: Float64Array; op: Float64Array; r: Float64Array; gr: Float64Array; bl: Float64Array; } /** Pinhole camera: Vrow = 3x3 view rotation (row-major), t = view translation, fx/fy focal px. */ export interface SplatCamera { Vrow: readonly number[]; t: readonly [number, number, number]; fx: number; fy: number; cx?: number; cy?: number; } /** Cached forward intermediates needed by backward3D. */ export interface ProjectIntermediates { camx: Float64Array; camy: Float64Array; camz: Float64Array; tx: Float64Array; ty: Float64Array; J00: Float64Array; J02: Float64Array; J11: Float64Array; J12: Float64Array; T: number[][]; M: number[][]; R: number[][]; sx: Float64Array; sy: Float64Array; sz: Float64Array; cov00: Float64Array; cov01: Float64Array; cov11: Float64Array; det: Float64Array; Vrow: readonly number[]; fx: number; fy: number; } export interface Forward3DResult { g2: Gaussian2D; I: ProjectIntermediates; } /** Rotation matrix (row-major R[3*row+col]) from RAW quat (r,x,y,z) — EXACT match to the shader. */ export declare function quatToR(r: number, x: number, y: number, z: number): number[]; /** * Forward project N 3D gaussians to the 2D raster representation (feed g2 to forward2D). * Returns the Gaussian2D plus cached intermediates for backward3D. */ export declare function forward3D(G3: Gaussian3D, cam: SplatCamera, W: number, H: number): Forward3DResult; /** * Backward: dG2 (= backward2D over the raster) -> dL/d{3D params}. Opacity + color gradients * pass straight through (they ARE the same buffers). mean3d gets BOTH the direct mean2d term and * the J-through-mean coupling that cov2d induces (the EWA `dL_dmean` contribution). */ export declare function backward3D(G3: Gaussian3D, _cam: SplatCamera, _W: number, _H: number, I: ProjectIntermediates, dG2: Gaussian2DGrad): Gaussian3DGrad; //# sourceMappingURL=GaussianTrainer3D.d.ts.map