"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Tiles
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readPntsColors = readPntsColors;
exports.readPointCloudTileContent = readPointCloudTileContent;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const FrontendLoggerCategory_1 = require("../../common/FrontendLoggerCategory");
const MeshPrimitives_1 = require("../../common/internal/render/MeshPrimitives");
function readPntsColors(stream, dataOffset, pnts) {
    const nPts = pnts.POINTS_LENGTH;
    const nComponents = 3 * nPts;
    if (pnts.RGB)
        return new Uint8Array(stream.arrayBuffer, dataOffset + pnts.RGB.byteOffset, nComponents);
    if (pnts.RGBA) {
        // ###TODO We currently don't support transparency for point clouds, so convert RGBA to RGB by stripping out the alpha channel.
        const rgb = new Uint8Array(nComponents);
        const rgba = new Uint8Array(stream.arrayBuffer, dataOffset + pnts.RGBA.byteOffset, 4 * nPts);
        for (let i = 0; i < nPts; i++) {
            rgb[i * 3 + 0] = rgba[i * 4 + 0];
            rgb[i * 3 + 1] = rgba[i * 4 + 1];
            rgb[i * 3 + 2] = rgba[i * 4 + 2];
        }
        return rgb;
    }
    else if (pnts.RGB565) {
        // Each color is 16 bits: 5 red, 6 green, 5 blue.
        const crgb = new Uint16Array(stream.arrayBuffer, dataOffset + pnts.RGB565.byteOffset, nPts);
        const rgb = new Uint8Array(nComponents);
        for (let i = 0; i < nPts; i++) {
            const c = crgb[i];
            rgb[i + 0] = (c >> 11) & 0x1f;
            rgb[i + 1] = (c >> 5) & 0x3f;
            rgb[i + 2] = c & 0x1f;
        }
        return rgb;
    }
    return undefined;
}
function readPnts(stream, dataOffset, pnts) {
    const nPts = pnts.POINTS_LENGTH;
    let params;
    let points;
    if (pnts.POSITION_QUANTIZED) {
        const qpos = pnts.POSITION_QUANTIZED;
        const offset = pnts.QUANTIZED_VOLUME_OFFSET;
        const scale = pnts.QUANTIZED_VOLUME_SCALE;
        const qOrigin = new core_geometry_1.Point3d(offset[0], offset[1], offset[2]);
        const qScale = new core_geometry_1.Point3d(core_common_1.Quantization.computeScale(scale[0]), core_common_1.Quantization.computeScale(scale[1]), core_common_1.Quantization.computeScale(scale[2]));
        params = core_common_1.QParams3d.fromOriginAndScale(qOrigin, qScale);
        points = new Uint16Array(stream.arrayBuffer, dataOffset + qpos.byteOffset, 3 * nPts);
    }
    else {
        const qOrigin = new core_geometry_1.Point3d(0, 0, 0);
        const qScale = new core_geometry_1.Point3d(1, 1, 1);
        params = core_common_1.QParams3d.fromOriginAndScale(qOrigin, qScale);
        points = new Float32Array(stream.arrayBuffer, dataOffset + pnts.POSITION.byteOffset, 3 * nPts);
    }
    const colors = readPntsColors(stream, dataOffset, pnts);
    return { params, points, colors };
}
async function decodeDracoPointCloud(buf) {
    try {
        const dracoLoader = (await Promise.resolve().then(() => __importStar(require("@loaders.gl/draco")))).DracoLoader;
        const mesh = await dracoLoader.parse(buf, {});
        if (mesh.topology !== "point-list")
            return undefined;
        const pos = mesh.attributes.POSITION?.value;
        if (!pos || (pos.length % 3) !== 0)
            return undefined;
        let colors = mesh.attributes.RGB?.value ?? mesh.attributes.COLOR_0?.value;
        if (!colors) {
            // ###TODO support point cloud transparency.
            const rgba = mesh.attributes.RGBA?.value;
            if (rgba && (rgba.length % 4) === 0) {
                // We currently don't support alpha channel for point clouds - strip it.
                colors = new Uint8Array(3 * rgba.length / 4);
                let j = 0;
                for (let i = 0; i < rgba.length; i += 4) {
                    colors[j++] = rgba[i];
                    colors[j++] = rgba[i + 1];
                    colors[j++] = rgba[i + 2];
                }
            }
        }
        let posRange;
        const bbox = mesh.header?.boundingBox;
        if (bbox) {
            posRange = core_geometry_1.Range3d.createXYZXYZ(bbox[0][0], bbox[0][1], bbox[0][2], bbox[1][0], bbox[1][1], bbox[1][2]);
        }
        else {
            posRange = core_geometry_1.Range3d.createNull();
            for (let i = 0; i < pos.length; i += 3)
                posRange.extendXYZ(pos[i], pos[i + 1], pos[i + 2]);
        }
        const params = core_common_1.QParams3d.fromRange(posRange);
        const pt = core_geometry_1.Point3d.createZero();
        const qpt = core_common_1.QPoint3d.create(pt, params);
        const points = new Uint16Array(pos.length);
        for (let i = 0; i < pos.length; i += 3) {
            pt.set(pos[i], pos[i + 1], pos[i + 2]);
            qpt.init(pt, params);
            points[i] = qpt.x;
            points[i + 1] = qpt.y;
            points[i + 2] = qpt.z;
        }
        return { points, params, colors: colors instanceof Uint8Array ? colors : undefined };
    }
    catch (err) {
        core_bentley_1.Logger.logWarning(FrontendLoggerCategory_1.FrontendLoggerCategory.Render, "Failed to decode draco-encoded point cloud");
        core_bentley_1.Logger.logError(FrontendLoggerCategory_1.FrontendLoggerCategory.Render, err);
        return undefined;
    }
}
/** Deserialize a point cloud tile and return it as a RenderGraphic.
 */
async function readPointCloudTileContent(stream, iModel, modelId, _is3d, tile, system) {
    let graphic;
    let rtcCenter;
    const header = new core_common_1.PntsHeader(stream);
    if (!header.isValid)
        return { graphic, rtcCenter };
    const range = tile.contentRange;
    const featureTableJsonOffset = stream.curPos;
    const featureStrData = stream.nextBytes(header.featureTableJsonLength);
    const featureStr = (0, core_bentley_1.utf8ToString)(featureStrData);
    const featureValue = JSON.parse(featureStr);
    if (undefined === featureValue)
        return { graphic, rtcCenter };
    let props;
    const dataOffset = featureTableJsonOffset + header.featureTableJsonLength;
    const draco = featureValue.extensions ? featureValue.extensions["3DTILES_draco_point_compression"] : undefined;
    if (draco) {
        try {
            const buf = new Uint8Array(stream.arrayBuffer, dataOffset + draco.byteOffset, draco.byteLength);
            props = await decodeDracoPointCloud(buf);
        }
        catch {
            //
        }
    }
    else {
        props = readPnts(stream, dataOffset, featureValue);
    }
    if (!props)
        return { graphic, rtcCenter };
    let batchRange = range;
    if (featureValue.RTC_CENTER) {
        rtcCenter = core_geometry_1.Point3d.fromJSON(featureValue.RTC_CENTER);
        batchRange = range.clone();
        batchRange.low.minus(rtcCenter, batchRange.low);
        batchRange.high.minus(rtcCenter, batchRange.high);
    }
    if (!props.colors) {
        // ###TODO we really should support uniform color instead of allocating an RGB value per point...
        props.colors = new Uint8Array(3 * featureValue.POINTS_LENGTH);
        const rgba = featureValue.CONSTANT_RGBA;
        if (rgba) {
            // ###TODO support point cloud transparency.
            for (let i = 0; i < featureValue.POINTS_LENGTH * 3; i += 3) {
                props.colors[i] = rgba[0];
                props.colors[i + 1] = rgba[1];
                props.colors[i + 2] = rgba[2];
            }
        }
        else {
            // Default to white.
            props.colors.fill(0xff, 0, props.colors.length);
        }
    }
    const featureTable = new core_common_1.FeatureTable(1, modelId, core_common_1.BatchType.Primary);
    const features = new MeshPrimitives_1.Mesh.Features(featureTable);
    features.add(new core_common_1.Feature(modelId), 1);
    let params = props.params;
    if (props.points instanceof Float32Array) {
        // we don't have a true range for unquantized points, so calc one here for voxelSize
        const rng = core_geometry_1.Range3d.createNull();
        for (let i = 0; i < props.points.length; i += 3)
            rng.extendXYZ(props.points[i], props.points[i + 1], props.points[i + 2]);
        params = core_common_1.QParams3d.fromRange(rng);
    }
    // 256 here is tile.maximumSize (on non-additive refinement tiles)
    // If additiveRefinement, set voxelSize to 0 which will cause it draw to with minPixelsPerVoxel, which defaults to 2
    // That way, it will draw as if in pixel mode, and voxelScale will still function
    // Checking across a variety of 10 point clouds, 2 to 4 seems to work well for pixel settings (depending on the
    // cloud), so 2 is a decent default
    // (If voxelSize is used normally in this case, it draws different size pixels for different tiles, and since
    // they can overlap ranges, no good way found to calculate a voxelSize)
    const voxelSize = tile.additiveRefinement ? 0 : params.rangeDiagonal.maxAbs() / 256;
    graphic = system.createPointCloud({
        positions: props.points,
        qparams: props.params,
        colors: props.colors,
        features: features.toFeatureIndex(),
        voxelSize,
        colorFormat: "rgb",
    }, iModel);
    graphic = system.createBatch(graphic, core_common_1.PackedFeatureTable.pack(featureTable), batchRange);
    return { graphic, rtcCenter };
}
//# sourceMappingURL=PntsReader.js.map