import type { Mat4 } from "../math/Mat4"

/** The six view-frustum planes packed as (a, b, c, d) each, normal pointing
 *  inward: a point is inside a plane when a*x + b*y + c*z + d >= 0. */
export type Frustum = Float32Array

export namespace Frustum {
  /** Extract the planes from a view-projection matrix (Gribb-Hartmann). Our Mat4
   *  is column-major (`m[col*4 + row]`), so a clip-space row `i` gathers the
   *  `i`-th entry of every column. Left/right/bottom/top/near/far are the row
   *  sums/differences with the w-row. */
  export function fromViewProj(m: Mat4): Frustum {
    const rx = [m[0], m[1], m[2], m[3]]
    const ry = [m[4], m[5], m[6], m[7]]
    const rz = [m[8], m[9], m[10], m[11]]
    const rw = [m[12], m[13], m[14], m[15]]
    // Row i of the clip matrix = (rx[i], ry[i], rz[i], rw[i]).
    const row = (i: number): [number, number, number, number] => [rx[i], ry[i], rz[i], rw[i]]
    const [x0, y0, z0, w0] = row(0)
    const [x1, y1, z1, w1] = row(1)
    const [x2, y2, z2, w2] = row(2)
    const [x3, y3, z3, w3] = row(3)
    const f = new Float32Array(24)
    plane(f, 0, x3 + x0, y3 + y0, z3 + z0, w3 + w0) // left
    plane(f, 1, x3 - x0, y3 - y0, z3 - z0, w3 - w0) // right
    plane(f, 2, x3 + x1, y3 + y1, z3 + z1, w3 + w1) // bottom
    plane(f, 3, x3 - x1, y3 - y1, z3 - z1, w3 - w1) // top
    plane(f, 4, x3 + x2, y3 + y2, z3 + z2, w3 + w2) // near
    plane(f, 5, x3 - x2, y3 - y2, z3 - z2, w3 - w2) // far
    return f
  }

  /** True if the axis-aligned box might be visible. Conservative: tests the box
   *  corner farthest along each plane normal; the box is culled only if that
   *  corner is still outside some plane, so nothing visible is ever dropped. */
  export function intersectsAabb(
    f: Frustum,
    minX: number,
    minY: number,
    minZ: number,
    maxX: number,
    maxY: number,
    maxZ: number,
  ): boolean {
    for (let p = 0; p < 24; p += 4) {
      const a = f[p]
      const b = f[p + 1]
      const c = f[p + 2]
      const px = a >= 0 ? maxX : minX
      const py = b >= 0 ? maxY : minY
      const pz = c >= 0 ? maxZ : minZ
      if (a * px + b * py + c * pz + f[p + 3] < 0) {
        return false
      }
    }
    return true
  }

  function plane(f: Frustum, i: number, a: number, b: number, c: number, d: number): void {
    const inv = 1 / Math.hypot(a, b, c)
    f[i * 4] = a * inv
    f[i * 4 + 1] = b * inv
    f[i * 4 + 2] = c * inv
    f[i * 4 + 3] = d * inv
  }
}
