import { GLSL3, RawShaderMaterial } from "three";


// Unlit voxel-impostor shader.
//
// Per-instance camera-facing billboard quad, sized in WORLD space. The
// quad expansion happens in view space — and since the view matrix is
// rigid (rotation + translation, no scale), distances in view space
// equal distances in world. So a corner offset of `world_diameter * 0.5`
// added to the splat centre's view-space position gives the right
// world-aligned footprint, and perspective projection scales it with
// distance automatically — close in, the splat covers many pixels;
// far away, it shrinks below a pixel.
//
// uPointDiameter is in OBJECT space (bake space); the shader multiplies
// by the entity's uniform scale (length(modelMatrix[0])) to bring it
// into world space at draw time. This decouples the impostor data from
// the runtime entity scale.
const shader_vx = `
    in vec3 position;
    in vec2 uv;

    in vec3 iPosition;
    in vec3 iNormal;
    in vec3 iAlbedo;
    in vec3 iORM;

    out vec2 vUv;
    flat out vec3 vNormalView;
    flat out vec3 vAlbedo;
    flat out vec3 vORM;

    uniform mat4 modelMatrix;
    uniform mat4 modelViewMatrix;
    uniform mat4 projectionMatrix;
    uniform mat3 normalMatrix;
    uniform float uPointDiameter;

    void main() {
        vUv = uv;
        vAlbedo = iAlbedo;
        vORM = iORM;
        vNormalView = normalize(normalMatrix * iNormal);

        float model_scale = length(modelMatrix[0].xyz);
        float world_diameter = uPointDiameter * model_scale;
        float radius_OS = uPointDiameter * 0.5;

        // Inward-shift along the surface normal so the splat's OUTER
        // edge (= centre + radius along normal) lands at the original
        // surface position (iPosition is the un-shifted surface
        // centroid the voxeliser produced). Without the shift, a
        // camera-facing quad of diameter D centred at the surface
        // extends D/2 past it on silhouette voxels, inflating the
        // outline. With the shift, silhouette voxels render edge-on
        // exactly at the mesh outline; inside voxels are pushed
        // slightly below the surface (sub-pixel at design distance).
        vec3 centre_OS = iPosition - iNormal * radius_OS;

        vec4 centre_view = modelViewMatrix * vec4(centre_OS, 1.0);
        vec2 corner = position.xy * world_diameter;
        vec4 pos_view = centre_view + vec4(corner, 0.0, 0.0);

        gl_Position = projectionMatrix * pos_view;
    }
`;

const shader_fg = `
    precision highp float;
    precision highp int;

    in vec2 vUv;
    flat in vec3 vNormalView;
    flat in vec3 vAlbedo;
    flat in vec3 vORM;

    out vec4 color_out;

    uniform bool uDiscMask;

    void main() {
        if (uDiscMask) {
            vec2 c = vUv - 0.5;
            if (dot(c, c) > 0.25) {
                discard;
            }
        }
        color_out = vec4(vAlbedo, 1.0);
    }
`;


/**
 * Unlit voxel impostor material. Each voxel renders as a camera-facing
 * billboard sized in world space, so the on-screen footprint scales
 * naturally with viewing distance.
 *
 * Pair with {@link ShadedGeometry.from}(geometry, material) — the
 * default DrawMode.Triangles is correct, since the splat is a quad.
 */
export class VoxelImpostorShaderV0 extends RawShaderMaterial {
    constructor() {
        super({
            vertexShader: shader_vx,
            fragmentShader: shader_fg,
            glslVersion: GLSL3,
            uniforms: {
                /**
                 * World-space diameter of each splat, in OBJECT-space
                 * units. Multiplied by the entity's modelMatrix scale
                 * at draw time. For tight coverage set to the voxel
                 * cell size (see VoxelImpostorDescription.voxel_size).
                 */
                uPointDiameter: {
                    value: 0.05
                },
                /**
                 * If true, discards fragments outside the inscribed
                 * disc — turns square splats into round ones.
                 */
                uDiscMask: {
                    value: false
                }
            }
        });
    }
}
