import untypedStars from './data/stars.json';
import untypedConstellations from './data/constellations.json';
import { Stats } from 'stats.ts';

const shaderCode = `
struct VertexOut {
    @builtin(position) position: vec4f,
    @location(0) color: vec4f
}
struct Star {
    pos: vec3f,
    lum: f32
}
struct Uniform {
    _View: mat4x4f,
    _Projection: mat4x4f
}

@group(0) @binding(0) var<uniform> uniforms: Uniform;
@group(0) @binding(1) var<storage, read> stars: array<Star>;

@vertex
fn vertex_main(@location(0) position: vec3f, @builtin(instance_index) instance_index: u32) -> VertexOut
{
    var star: Star = stars[instance_index];
    var output: VertexOut;

    var star_pos = uniforms._View * vec4f(star.pos, 1.0);
    var billboard_pos = star_pos + vec4f(position, 0.0);

    output.position = uniforms._Projection * billboard_pos;
    output.color = vec4(star.lum, star.lum, star.lum, 1);

    return output;
}

@fragment
fn fragment_main(fragData: VertexOut) -> @location(0) vec4f
{
    //var dist = distance(fragData.uv, vec2f(0.5, 0.5));
    //return vec4f(1 - dist, 1 - dist, 1 - dist, dist);
    return vec4f(1.0, 1.0, 1.0, 1.0);
}
`;

type StarID = number;
type Star = {
    id: StarID;
    x: number;
    y: number;
    z: number;
    ci: number;
    lum: number;
};
type Constellation = {
    con: string;
    con_id: string;
    lines: StarID[][];
    certainty: string | null;
    description: string | null;
    semantics: string[] | null;
};
type Position = {
    x: number;
    y: number;
    z: number;
}

class Matrix4x4
{
    private _buffer: ArrayBuffer;
    private _view: Float32Array;
    constructor()
    {
        this._view = new Float32Array(16);
        this._buffer = this._view.buffer as ArrayBuffer;
    }

    static translation(x: number, y: number, z: number): Matrix4x4
    {
        const mat = new Matrix4x4();

        mat.view[ 0] = 1;   mat.view[ 1] = 0;   mat.view[ 2] = 0;   mat.view[ 3] = 0;
        mat.view[ 4] = 0;   mat.view[ 5] = 1;   mat.view[ 6] = 0;   mat.view[ 7] = 0;
        mat.view[ 8] = 0;   mat.view[ 9] = 0;   mat.view[10] = 1;   mat.view[11] = 0;
        mat.view[12] = x;   mat.view[13] = y;   mat.view[14] = z;   mat.view[15] = 1;

        return mat;
    }
    static rotationX(angle: number): Matrix4x4
    {
        const c = Math.cos(angle);
        const s = Math.sin(angle);

        const mat = new Matrix4x4();

        mat.view[ 0] = 1;  mat.view[ 1] = 0;   mat.view[ 2] = 0;  mat.view[ 3] = 0;
        mat.view[ 4] = 0;  mat.view[ 5] = c;   mat.view[ 6] = s;  mat.view[ 7] = 0;
        mat.view[ 8] = 0;  mat.view[ 9] = -s;  mat.view[10] = c;  mat.view[11] = 0;
        mat.view[12] = 0;  mat.view[13] = 0;   mat.view[14] = 0;  mat.view[15] = 1;

        return mat;
    }
    static rotationY(angle: number): Matrix4x4
    {
        const c = Math.cos(angle);
        const s = Math.sin(angle);

        const mat = new Matrix4x4();

        mat.view[ 0] = c;  mat.view[ 1] = 0;  mat.view[ 2] = -s;  mat.view[ 3] = 0;
        mat.view[ 4] = 0;  mat.view[ 5] = 1;  mat.view[ 6] = 0;   mat.view[ 7] = 0;
        mat.view[ 8] = s;  mat.view[ 9] = 0;  mat.view[10] = c;   mat.view[11] = 0;
        mat.view[12] = 0;  mat.view[13] = 0;  mat.view[14] = 0;   mat.view[15] = 1;
        
        return mat;
    }
    static rotationZ(angle: number): Matrix4x4
    {
        const c = Math.cos(angle);
        const s = Math.sin(angle);

        const mat = new Matrix4x4();

        mat.view[ 0] = c;   mat.view[ 1] = s;  mat.view[ 2] = 0;  mat.view[ 3] = 0;
        mat.view[ 4] = -s;  mat.view[ 5] = c;  mat.view[ 6] = 0;  mat.view[ 7] = 0;
        mat.view[ 8] = 0;   mat.view[ 9] = 0;  mat.view[10] = 1;  mat.view[11] = 0;
        mat.view[12] = 0;   mat.view[13] = 0;  mat.view[14] = 0;  mat.view[15] = 1;
        
        return mat;
    }
    static scaling(sx: number, sy: number, sz: number): Matrix4x4
    {
        const mat = new Matrix4x4();

        mat.view[ 0] = sx;  mat.view[ 1] = 0;   mat.view[ 2] = 0;    mat.view[ 3] = 0;
        mat.view[ 4] = 0;   mat.view[ 5] = sy;  mat.view[ 6] = 0;    mat.view[ 7] = 0;
        mat.view[ 8] = 0;   mat.view[ 9] = 0;   mat.view[10] = sz;   mat.view[11] = 0;
        mat.view[12] = 0;   mat.view[13] = 0;   mat.view[14] = 0;    mat.view[15] = 1;
        
        return mat;
    }

    identity(value: number = 1): this
    {
        this._view[0] = value;
        this._view[1] = 0;
        this._view[2] = 0;
        this._view[3] = 0;

        this._view[4] = 0;
        this._view[5] = value;
        this._view[6] = 0;
        this._view[7] = 0;

        this._view[8] = 0;
        this._view[9] = 0;
        this._view[10] = value;
        this._view[11] = 0;

        this._view[12] = 0;
        this._view[13] = 0;
        this._view[14] = 0;
        this._view[15] = value;

        return this;
    }
    translate(x: number, y: number, z: number): this
    {
        return this.multiply(this, Matrix4x4.translation(x, y, z));
    }
    rotateX(angle: number): this
    {
        return this.multiply(this, Matrix4x4.rotationX(angle));
    }
    rotateY(angle: number): this
    {
        return this.multiply(this, Matrix4x4.rotationY(angle));
    }
    rotateZ(angle: number): this
    {
        return this.multiply(this, Matrix4x4.rotationZ(angle));
    }
    scale(sx: number, sy: number, sz: number): this
    {
        return this.multiply(this, Matrix4x4.scaling(sx, sy, sz));
    }

    multiply(a: Matrix4x4, b: Matrix4x4): this
    {
        const b00 = b.view[0 * 4 + 0]!;
        const b01 = b.view[0 * 4 + 1]!;
        const b02 = b.view[0 * 4 + 2]!;
        const b03 = b.view[0 * 4 + 3]!;
        const b10 = b.view[1 * 4 + 0]!;
        const b11 = b.view[1 * 4 + 1]!;
        const b12 = b.view[1 * 4 + 2]!;
        const b13 = b.view[1 * 4 + 3]!;
        const b20 = b.view[2 * 4 + 0]!;
        const b21 = b.view[2 * 4 + 1]!;
        const b22 = b.view[2 * 4 + 2]!;
        const b23 = b.view[2 * 4 + 3]!;
        const b30 = b.view[3 * 4 + 0]!;
        const b31 = b.view[3 * 4 + 1]!;
        const b32 = b.view[3 * 4 + 2]!;
        const b33 = b.view[3 * 4 + 3]!;
        const a00 = a.view[0 * 4 + 0]!;
        const a01 = a.view[0 * 4 + 1]!;
        const a02 = a.view[0 * 4 + 2]!;
        const a03 = a.view[0 * 4 + 3]!;
        const a10 = a.view[1 * 4 + 0]!;
        const a11 = a.view[1 * 4 + 1]!;
        const a12 = a.view[1 * 4 + 2]!;
        const a13 = a.view[1 * 4 + 3]!;
        const a20 = a.view[2 * 4 + 0]!;
        const a21 = a.view[2 * 4 + 1]!;
        const a22 = a.view[2 * 4 + 2]!;
        const a23 = a.view[2 * 4 + 3]!;
        const a30 = a.view[3 * 4 + 0]!;
        const a31 = a.view[3 * 4 + 1]!;
        const a32 = a.view[3 * 4 + 2]!;
        const a33 = a.view[3 * 4 + 3]!;

        this._view[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30;
        this._view[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31;
        this._view[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32;
        this._view[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33;

        this._view[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30;
        this._view[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31;
        this._view[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32;
        this._view[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;

        this._view[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30;
        this._view[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31;
        this._view[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32;
        this._view[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33;

        this._view[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30;
        this._view[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31;
        this._view[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32;
        this._view[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33;

        return this;
    }
    invert()
    {
        const m00 = this._view[0 * 4 + 0]!;
        const m01 = this._view[0 * 4 + 1]!;
        const m02 = this._view[0 * 4 + 2]!;
        const m03 = this._view[0 * 4 + 3]!;
        const m10 = this._view[1 * 4 + 0]!;
        const m11 = this._view[1 * 4 + 1]!;
        const m12 = this._view[1 * 4 + 2]!;
        const m13 = this._view[1 * 4 + 3]!;
        const m20 = this._view[2 * 4 + 0]!;
        const m21 = this._view[2 * 4 + 1]!;
        const m22 = this._view[2 * 4 + 2]!;
        const m23 = this._view[2 * 4 + 3]!;
        const m30 = this._view[3 * 4 + 0]!;
        const m31 = this._view[3 * 4 + 1]!;
        const m32 = this._view[3 * 4 + 2]!;
        const m33 = this._view[3 * 4 + 3]!;
    
        const tmp0 = m22 * m33;
        const tmp1 = m32 * m23;
        const tmp2 = m12 * m33;
        const tmp3 = m32 * m13;
        const tmp4 = m12 * m23;
        const tmp5 = m22 * m13;
        const tmp6 = m02 * m33;
        const tmp7 = m32 * m03;
        const tmp8 = m02 * m23;
        const tmp9 = m22 * m03;
        const tmp10 = m02 * m13;
        const tmp11 = m12 * m03;
        const tmp12 = m20 * m31;
        const tmp13 = m30 * m21;
        const tmp14 = m10 * m31;
        const tmp15 = m30 * m11;
        const tmp16 = m10 * m21;
        const tmp17 = m20 * m11;
        const tmp18 = m00 * m31;
        const tmp19 = m30 * m01;
        const tmp20 = m00 * m21;
        const tmp21 = m20 * m01;
        const tmp22 = m00 * m11;
        const tmp23 = m10 * m01;
    
        const t0 = (tmp0 * m11 + tmp3 * m21 + tmp4 * m31) -
                (tmp1 * m11 + tmp2 * m21 + tmp5 * m31);
        const t1 = (tmp1 * m01 + tmp6 * m21 + tmp9 * m31) -
                (tmp0 * m01 + tmp7 * m21 + tmp8 * m31);
        const t2 = (tmp2 * m01 + tmp7 * m11 + tmp10 * m31) -
                (tmp3 * m01 + tmp6 * m11 + tmp11 * m31);
        const t3 = (tmp5 * m01 + tmp8 * m11 + tmp11 * m21) -
                (tmp4 * m01 + tmp9 * m11 + tmp10 * m21);
    
        const d = 1 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
    
        this._view[0] = d * t0;
        this._view[1] = d * t1;
        this._view[2] = d * t2;
        this._view[3] = d * t3;
    
        this._view[4] = d * ((tmp1 * m10 + tmp2 * m20 + tmp5 * m30) - (tmp0 * m10 + tmp3 * m20 + tmp4 * m30));
        this._view[5] = d * ((tmp0 * m00 + tmp7 * m20 + tmp8 * m30) - (tmp1 * m00 + tmp6 * m20 + tmp9 * m30));
        this._view[6] = d * ((tmp3 * m00 + tmp6 * m10 + tmp11 * m30) - (tmp2 * m00 + tmp7 * m10 + tmp10 * m30));
        this._view[7] = d * ((tmp4 * m00 + tmp9 * m10 + tmp10 * m20) - (tmp5 * m00 + tmp8 * m10 + tmp11 * m20));
    
        this._view[8] = d * ((tmp12 * m13 + tmp15 * m23 + tmp16 * m33) - (tmp13 * m13 + tmp14 * m23 + tmp17 * m33));
        this._view[9] = d * ((tmp13 * m03 + tmp18 * m23 + tmp21 * m33) - (tmp12 * m03 + tmp19 * m23 + tmp20 * m33));
        this._view[10] = d * ((tmp14 * m03 + tmp19 * m13 + tmp22 * m33) - (tmp15 * m03 + tmp18 * m13 + tmp23 * m33));
        this._view[11] = d * ((tmp17 * m03 + tmp20 * m13 + tmp23 * m23) - (tmp16 * m03 + tmp21 * m13 + tmp22 * m23));
    
        this._view[12] = d * ((tmp14 * m22 + tmp17 * m32 + tmp13 * m12) - (tmp16 * m32 + tmp12 * m12 + tmp15 * m22));
        this._view[13] = d * ((tmp20 * m32 + tmp12 * m02 + tmp19 * m22) - (tmp18 * m22 + tmp21 * m32 + tmp13 * m02));
        this._view[14] = d * ((tmp18 * m12 + tmp23 * m32 + tmp15 * m02) - (tmp22 * m32 + tmp14 * m02 + tmp19 * m12));
        this._view[15] = d * ((tmp22 * m22 + tmp16 * m02 + tmp21 * m12) - (tmp20 * m12 + tmp23 * m22 + tmp17 * m02));

        return this;
    }

    lookat(eye: Position, target: Position): this
    {
        const forward = { //zAxis
            x: eye.x - target.x,
            y: eye.y - target.y,
            z: eye.z - target.z
        };
        
        // Normalize
        const forwardLen = Math.sqrt(forward.x * forward.x + forward.y * forward.y + forward.z * forward.z);
        forward.x /= forwardLen;
        forward.y /= forwardLen;
        forward.z /= forwardLen;

        const right = { //xAxis
            x: 1 * forward.z - 0 * forward.y,
            y: 0 * forward.x - 0 * forward.z,
            z: 0 * forward.y - 1 * forward.x
        };
        
        // Normalize
        const rightLen = Math.sqrt(right.x * right.x + right.y * right.y + right.z * right.z);
        right.x /= rightLen;
        right.y /= rightLen;
        right.z /= rightLen;
        
        const up = { //yAxis
            x: forward.y * right.z - forward.z * right.y,
            y: forward.z * right.x - forward.x * right.z,
            z: forward.x * right.y - forward.y * right.x
        };

        // Normalize
        const upLen = Math.sqrt(up.x * up.x + up.y * up.y + up.z * up.z);
        up.x /= upLen;
        up.y /= upLen;
        up.z /= upLen;

        this._view[ 0] = right.x;
        this._view[ 1] = right.y;
        this._view[ 2] = right.z;
        this._view[ 3] = 0;

        this._view[ 4] = up.x;
        this._view[ 5] = up.y;
        this._view[ 6] = up.z;
        this._view[ 7] = 0;

        this._view[ 8] = forward.x;
        this._view[ 9] = forward.y;
        this._view[10] = forward.z;
        this._view[11] = 0;
        
        this._view[12] = eye.x;
        this._view[13] = eye.y;
        this._view[14] = eye.z;
        this._view[15] = 1;

        return this;
    }

    perspective(aspect: number, fovY: number, near: number, far: number): this
    {
        const f = Math.tan(Math.PI * 0.5 - 0.5 * fovY);
        const rangeInv = 1 / (near - far);

        this._view[0] = f / aspect;
        this._view[1] = 0;
        this._view[2] = 0;
        this._view[3] = 0;

        this._view[4] = 0;
        this._view[5] = f;
        this._view[6] = 0;
        this._view[7] = 0;

        this._view[8] = 0;
        this._view[9] = 0;
        this._view[10] = far * rangeInv;
        this._view[11] = -1;

        this._view[12] = 0;
        this._view[13] = 0;
        this._view[14] = near * far * rangeInv;
        this._view[15] = 0;

        return this;
    }

    get view()
    {
        return this._view;
    }
}
class Inputs
{
    static inputs: Record<string, boolean> = {};
    private static _movementX = 0;
    private static _movementY = 0;
    private static _zoom = 0;
    private static _dragging = false;
    static init(eventTarget: HTMLElement)
    {
        const dragstart = (e: MouseEvent) => {
            if(e.buttons & 1)
            {
                Inputs._dragging = true;
                window.addEventListener('mousemove', dragmove);
                window.addEventListener('mouseup', dragend);
            }
        };
        const dragmove = (e: MouseEvent) => {
            Inputs._movementX += e.movementX;
            Inputs._movementY += e.movementY;
        };
        const dragend = () => {
            Inputs._dragging = false;
            window.removeEventListener('mousemove', dragmove);
            window.removeEventListener('mouseup', dragend);
        };
        eventTarget.addEventListener('keydown', (e: KeyboardEvent) => {
            Inputs.inputs[e.key.toLowerCase()] = true;
        });
        eventTarget.addEventListener('keyup', (e: KeyboardEvent) => {
            Inputs.inputs[e.key.toLowerCase()] = false;
        });
        eventTarget.addEventListener('mousedown', dragstart);
        eventTarget.addEventListener('wheel', (e: WheelEvent) => Inputs._zoom += e.deltaY);
    }
    static get dragging()
    {
        return Inputs._dragging;
    }
    static get movementX()
    {
        const movement = Inputs._movementX;
        Inputs._movementX = 0;
        return movement;
    }
    static get movementY()
    {
        const movement = Inputs._movementY;
        Inputs._movementY = 0;
        return movement;
    }
    static get zoom()
    {
        const zoom = Inputs._zoom;
        Inputs._zoom = 0;
        return zoom;
    }
}
class Camera
{
    pitch = 0;
    yaw = 0;
    distance = 25
    view = new Matrix4x4();

    constructor(distance?: number, yaw?: number, pitch?: number)
    {
        this.pitch = pitch ?? 0;
        this.yaw = yaw ?? 0;
        this.distance = distance ?? 25;
    }
    update(elapsed: number, target: GPUBuffer)
    {
        elapsed /= 1 << 15;
        this.yaw -= Inputs.movementX * elapsed;
        this.pitch += Inputs.movementY * elapsed;

        this.yaw = (this.yaw + Math.PI * 2) % (Math.PI * 2);
        this.pitch = clamp(this.pitch, -Math.PI / 2, Math.PI / 2);

        const pitchCos = Math.cos(this.pitch), pitchSin = Math.sin(this.pitch);
        const yawCos = Math.cos(this.yaw), yawSin = Math.sin(this.yaw);

        this.distance = clamp(this.distance + (Inputs.zoom * elapsed * 1000), 1, 10000);
        
            const x = this.distance * yawSin * pitchCos;
            const y = this.distance * yawSin * pitchSin;
            const z = this.distance * yawCos;
        
        device!.queue.writeBuffer(target, 0, this.view.lookat({ x, y, z }, { x: 0, y: 0, z: 0 }).view.buffer);
    }
}

//@ts-ignore
const stars: Star[] = untypedStars;
const constellations: Constellation[] = untypedConstellations;
let canvas: HTMLCanvasElement, device: GPUDevice | undefined, pipeline: GPURenderPipeline, context: GPUCanvasContext | null, bindGroup: GPUBindGroup, viewport: DOMRect, uniforms: GPUBuffer, indirect: GPUBuffer;
let vertex: GPUBuffer, index: GPUBuffer;
let camera: Camera, projectionMatrix = new Matrix4x4(), lastTime: DOMHighResTimeStamp;
let stats: Stats, dom: HTMLElement;

function clamp(x: number, min: number, max: number): number
{
    if(x < min)
        return min;
    if(x > max)
        return max;
    return x;
}
function fillBuffer(buffer: ArrayBuffer)
{
    const array = new Float32Array(buffer);

    for(let i = 0; i < stars.length; i++)
    {
        array[i * 5 + 0] = stars[i]!.x;
        array[i * 5 + 1] = stars[i]!.y;
        array[i * 5 + 2] = stars[i]!.z;
        array[i * 5 + 3] = stars[i]!.lum;
        //array[i * 5 + 4] = stars[i]!.ci;
    }
}
function resize()
{
    viewport = document.body.getBoundingClientRect();
    canvas!.width = viewport.width; canvas!.height = viewport.height;
    device!.queue.writeBuffer(uniforms, 64, projectionMatrix.perspective(viewport.width / viewport.height, 60, 0.1, 2000000).view.buffer);
}

async function init()
{
    canvas = document.createElement("canvas");
    context = canvas.getContext('webgpu');

    Inputs.init(canvas);

    stats = new Stats();
    document.body.appendChild(stats.dom);

    const adapter = await navigator.gpu.requestAdapter({  });
    device = await adapter?.requestDevice({ label: 'Device' });

    if(device && context)
    {
        const shader = device.createShaderModule({
            code: shaderCode,
            label: 'default-shader'
        });
        
        const buffer = device.createBuffer({ size: stars.length * 4 * 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, label: 'instanced_buffer' });
        const arrayBuffer = new ArrayBuffer(buffer.size);
        fillBuffer(arrayBuffer);
        device.queue.writeBuffer(buffer, 0, arrayBuffer);
        
        uniforms = device.createBuffer({ size: 16 * 4 * 2, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, label: 'uniforms_buffer' });
        camera = new Camera();

        indirect = device.createBuffer({ size: 5 * 4, usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST, label: 'indirect_buffer' });
        const indirectArray = new Uint32Array([ 6, stars.length, 0, 0, 0 ]);
        const indirectSignedArray = new Int32Array(indirectArray.buffer);
        indirectSignedArray[3] = 0;
        device.queue.writeBuffer(indirect, 0, indirectArray.buffer);

        vertex = device.createBuffer({ size: 3 * 4 * 4, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, label: 'vertex_buffer' });
        device.queue.writeBuffer(vertex, 0, new Float32Array([ -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.5, 0.5, 0.0, -0.5, 0.5, 0.0, ]));

        index = device.createBuffer({ size: 6 * 2, usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, label: 'index_buffer' });
        device.queue.writeBuffer(index, 0, new Uint16Array([ 0, 1, 2, 0, 2, 3 ]));

        const bindGroupLayout = device.createBindGroupLayout({
            entries: [{
                binding: 0,
                visibility: GPUShaderStage.VERTEX,
                buffer: {
                    type: 'uniform',
                }
            }, {
                binding: 1,
                visibility: GPUShaderStage.VERTEX,
                buffer: {
                    type: 'read-only-storage',
                }
            }]
        });

        bindGroup = device.createBindGroup({
            layout: bindGroupLayout,
            entries: [{
                binding: 0,
                resource: { buffer: uniforms, }
            }, {
                binding: 1,
                resource: { buffer: buffer, }
            }]
        });

        pipeline = device.createRenderPipeline({
            layout: device.createPipelineLayout({
                bindGroupLayouts: [ bindGroupLayout ],
            }),
            vertex: {
                module: shader,
                entryPoint: 'vertex_main',
                buffers: [{
                    attributes: [{
                        format: 'float32x3',
                        offset: 0,
                        shaderLocation: 0
                    }],
                    arrayStride: 4 * 3,
                }]
            },
            fragment: {
                targets: [{ format: navigator.gpu.getPreferredCanvasFormat() }],
                module: shader,
                entryPoint: 'fragment_main'
            },
            primitive: {
                topology: 'triangle-list',
                cullMode: 'front',
                frontFace: 'cw'
            }
        });

        document.body.appendChild(canvas);
        resize();
        document.addEventListener('resize', resize);

        context.configure({
            device,
            format: navigator.gpu.getPreferredCanvasFormat(),
            alphaMode: 'premultiplied',
        });

        dom = document.createElement('pre');
        Object.assign(dom.style, {
            "position": "absolute",
            "right": "0px",
            "top": "0px",
            "color": "white"
        });
        document.body.append(dom);

        lastTime = performance.now();
        requestAnimationFrame(render);
    }
    else
    {
        document.body.append('Erreur de chargement (aucun GPU disponible)');
    }
}
function render(time: DOMHighResTimeStamp)
{
    const elapsed = time - lastTime;
    lastTime = time;
    stats.begin();
    const commandEncoder = device!.createCommandEncoder({ label: 'command-encoder' });
    const pass = commandEncoder.beginRenderPass({ label: 'render-pass', colorAttachments: [{
        loadOp: "clear",
        storeOp: "store",
        clearValue: { r: 0, g: 0, b: 0, a: 1 },
        view: context!.getCurrentTexture().createView({ label: 'render-target', usage: GPUTextureUsage.RENDER_ATTACHMENT }),
    }], });

    camera.update(elapsed, uniforms);
    dom.textContent = `Pitch: ${camera.pitch}\nYaw: ${camera.yaw}\nDistance: ${camera.distance}`;

    pass.setPipeline(pipeline!);
    pass.setBindGroup(0, bindGroup);
    pass.setVertexBuffer(0, vertex);
    pass.setIndexBuffer(index, 'uint16');
    pass.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, 0, 1);
    pass.drawIndexedIndirect(indirect, 0);
    pass.end();

    device!.queue.submit([ commandEncoder.finish() ]);

    stats.end();
    requestAnimationFrame(render);
}

document.addEventListener('DOMContentLoaded', init);