/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Rendering
 */
import { assert } from "@itwin/core-bentley";
import { AuxChannelDataType, Point3d, Range1d, Range3d, Vector3d, } from "@itwin/core-geometry";
import { OctEncodedNormal, QParams3d, QPoint3d, Quantization } from "@itwin/core-common";
import { computeDimensions } from "./VertexTable";
/** @alpha */
export class AuxChannel {
    constructor(props) {
        this.name = props.name;
        this.inputs = props.inputs;
        this.indices = props.indices;
    }
    toJSON() {
        return {
            name: this.name,
            inputs: this.inputs,
            indices: this.indices,
        };
    }
}
/** @alpha */
export class AuxDisplacementChannel extends AuxChannel {
    constructor(props) {
        super(props);
        this.qOrigin = Float32Array.from(props.qOrigin);
        this.qScale = Float32Array.from(props.qScale);
    }
    toJSON() {
        return {
            ...super.toJSON(),
            qOrigin: Array.from(this.qOrigin),
            qScale: Array.from(this.qScale),
        };
    }
}
/** @alpha */
export class AuxParamChannel extends AuxChannel {
    constructor(props) {
        super(props);
        this.qOrigin = props.qOrigin[0];
        this.qScale = props.qScale[0];
    }
    toJSON() {
        return {
            ...super.toJSON(),
            qOrigin: [this.qOrigin],
            qScale: [this.qScale],
        };
    }
}
/**
 * Represents one or more channels of auxiliary per-vertex data which can be used to animate and resymbolize a mesh in various ways.
 * Each channel holds a fixed number of bytes for each vertex (typically 2 bytes for normals and params, 6 bytes for displacements).
 * The channels are interleaved in a rectangular array such that the data for each vertex is stored contiguously; that is, if a displacement and
 * a normal channel exist, then the first vertex's displacement is followed by the first vertex's normal, which is followed by the second
 * vertex's displacement and normal; and so on.
 * @alpha
 */
export class AuxChannelTable {
    constructor(props, displacements, normals, params) {
        this.data = props.data;
        this.width = props.width;
        this.height = props.height;
        this.numVertices = props.count;
        this.numBytesPerVertex = props.numBytesPerVertex;
        this.displacements = displacements;
        this.normals = normals;
        this.params = params;
    }
    static fromJSON(props) {
        let displacements;
        let normals;
        let params;
        if (undefined !== props.displacements && 0 < props.displacements.length) {
            displacements = [];
            for (const displacement of props.displacements)
                displacements.push(new AuxDisplacementChannel(displacement));
        }
        if (undefined !== props.normals && 0 < props.normals.length) {
            normals = [];
            for (const normal of props.normals)
                normals.push(new AuxChannel(normal));
        }
        if (undefined !== props.params && 0 < props.params.length) {
            params = [];
            for (const param of props.params)
                params.push(new AuxParamChannel(param));
        }
        return undefined !== displacements || undefined !== normals || undefined !== params ? new AuxChannelTable(props, displacements, normals, params) : undefined;
    }
    toJSON() {
        return {
            data: this.data,
            width: this.width,
            height: this.height,
            count: this.numVertices,
            numBytesPerVertex: this.numBytesPerVertex,
            displacements: this.displacements?.map((x) => x.toJSON()),
            normals: this.normals?.map((x) => x.toJSON()),
            params: this.params?.map((x) => x.toJSON()),
        };
    }
    static fromChannels(channels, numVertices, maxDimension) {
        return AuxChannelTableBuilder.buildAuxChannelTable(channels, numVertices, maxDimension);
    }
}
function invert(num) {
    if (0 !== num)
        num = 1 / num;
    return num;
}
class AuxChannelTableBuilder {
    constructor(props, numBytesPerVertex) {
        this._props = props;
        this._numBytesPerVertex = numBytesPerVertex;
        this._view = new DataView(props.data.buffer);
    }
    static buildAuxChannelTable(channels, numVertices, maxDimension) {
        const numBytesPerVertex = channels.reduce((accum, channel) => accum + computeNumBytesPerVertex(channel), 0);
        if (!numBytesPerVertex)
            return undefined;
        const nRgbaPerVertex = Math.floor((numBytesPerVertex + 3) / 4);
        const nUnusedBytesPerVertex = nRgbaPerVertex * 4 - numBytesPerVertex;
        assert(0 === nUnusedBytesPerVertex || 2 === nUnusedBytesPerVertex);
        // We don't want any unused bytes. If we've got 2 extra, make every other vertex's channel start in the middle of the first texel.
        let dimensions;
        if (0 !== nUnusedBytesPerVertex)
            dimensions = computeDimensions(Math.floor((numVertices + 1) / 2), numBytesPerVertex / 2, 0, maxDimension); // twice as many RGBA for half as many vertices.
        else
            dimensions = computeDimensions(numVertices, nRgbaPerVertex, 0, maxDimension);
        const data = new Uint8Array(dimensions.width * dimensions.height * 4);
        const props = {
            data,
            width: dimensions.width,
            height: dimensions.height,
            count: numVertices,
            numBytesPerVertex,
        };
        const builder = new AuxChannelTableBuilder(props, numBytesPerVertex);
        builder.build(channels);
        return AuxChannelTable.fromJSON(props);
    }
    build(channels) {
        let byteOffset = 0;
        for (const channel of channels) {
            if (AuxChannelDataType.Normal === channel.dataType)
                this.addNormals(channel, byteOffset);
            else if (AuxChannelDataType.Vector === channel.dataType)
                this.addDisplacements(channel, byteOffset);
            else
                this.addParams(channel, byteOffset);
            byteOffset += computeNumBytesPerVertex(channel);
        }
    }
    addNormals(channel, byteOffset) {
        const inputs = [];
        const indices = [];
        const normal = new Vector3d();
        for (let i = 0; i < channel.data.length; i++) {
            let byteIndex = byteOffset + i * 2; // 2 bytes per normal
            indices.push(byteIndex / 2); // indices aligned to 2-byte intervals
            const data = channel.data[i];
            inputs.push(data.input);
            for (let j = 0; j < data.values.length; j += 3) {
                normal.x = data.values[j];
                normal.y = data.values[j + 1];
                normal.z = data.values[j + 2];
                normal.normalizeInPlace();
                const encodedNormal = OctEncodedNormal.encode(normal);
                this._view.setUint16(byteIndex, encodedNormal, true);
                byteIndex += this._numBytesPerVertex;
            }
        }
        const normals = this._props.normals ?? (this._props.normals = []);
        normals.push({
            name: channel.name ?? "",
            inputs,
            indices,
        });
    }
    addParams(channel, byteOffset) {
        const inputs = [];
        const indices = [];
        const range = Range1d.createNull();
        for (const data of channel.data) {
            inputs.push(data.input);
            range.extendArray(data.values);
        }
        const qScale = Quantization.computeScale(range.high - range.low);
        for (let i = 0; i < channel.data.length; i++) {
            let byteIndex = byteOffset + i * 2; // 2 bytes per double
            indices.push(byteIndex / 2); // indices aligned to 2-byte intervals
            for (const value of channel.data[i].values) {
                const quantized = Quantization.quantize(value, range.low, qScale);
                this._view.setUint16(byteIndex, quantized, true);
                byteIndex += this._numBytesPerVertex;
            }
        }
        const params = this._props.params ?? (this._props.params = []);
        params.push({
            inputs,
            indices,
            name: channel.name ?? "",
            qOrigin: [range.low],
            qScale: [invert(qScale)],
        });
    }
    addDisplacements(channel, byteOffset) {
        const inputs = [];
        const indices = [];
        const point = new Point3d();
        const range = Range3d.createNull();
        for (const data of channel.data) {
            inputs.push(data.input);
            for (let i = 0; i < data.values.length; i += 3) {
                point.set(data.values[i], data.values[i + 1], data.values[i + 2]);
                range.extend(point);
            }
        }
        const qParams = QParams3d.fromRange(range);
        const qPoint = new QPoint3d();
        for (let i = 0; i < channel.data.length; i++) {
            let byteIndex = byteOffset + i * 6; // 2 bytes per coordinate
            indices.push(byteIndex / 2); // indices aligned to 2-byte intervals
            const data = channel.data[i];
            for (let j = 0; j < data.values.length; j += 3) {
                point.set(data.values[j], data.values[j + 1], data.values[j + 2]);
                qPoint.init(point, qParams);
                this._view.setUint16(byteIndex + 0, qPoint.x, true);
                this._view.setUint16(byteIndex + 2, qPoint.y, true);
                this._view.setUint16(byteIndex + 4, qPoint.z, true);
                byteIndex += this._numBytesPerVertex;
            }
        }
        const displacements = this._props.displacements ?? (this._props.displacements = []);
        displacements.push({
            inputs,
            indices,
            name: channel.name ?? "",
            qOrigin: qParams.origin.toArray(),
            qScale: qParams.scale.toArray().map((x) => invert(x)),
        });
    }
}
function computeNumBytesPerVertex(channel) {
    const nEntries = channel.data.length;
    switch (channel.dataType) {
        case AuxChannelDataType.Vector:
            return 6 * nEntries; // 3 16-bit quantized coordinate values per entry.
        case AuxChannelDataType.Normal:
        case AuxChannelDataType.Distance:
        case AuxChannelDataType.Scalar:
            return 2 * nEntries; // 1 16-bit quantized value per entry.
    }
}
//# sourceMappingURL=AuxChannelTable.js.map