/*---------------------------------------------------------------------------------------------
* 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 { Range3d, Transform } from "@itwin/core-geometry";
import { addPrimitiveTransferables } from "../../imdl/ImdlModel";
import { GraphicType } from "../../render/GraphicType";
import { GraphicAssembler } from "../../render/GraphicAssembler";
import { PackedFeatureTable, QPoint3dList } from "@itwin/core-common";
import { assert, TransientIdSequence } from "@itwin/core-bentley";
import { createPointStringParams } from "./PointStringParams";
import { createPolylineParams } from "./PolylineParams";
import { createMeshParams } from "./VertexTableBuilder";
import { edgeParamsToImdl } from "../../imdl/ParseImdlDocument";
import { _accumulator, _implementationProhibited } from "../Symbols";
import { WorkerMaterial, WorkerTexture } from "./GraphicDescriptionContextImpl";
export class GraphicDescriptionBuilderImpl extends GraphicAssembler {
    [_implementationProhibited] = undefined;
    _computeChordTolerance;
    _context;
    _viewIndependentOrigin;
    constructor(options) {
        const type = options.type;
        const placement = options.placement ?? Transform.createIdentity();
        const wantEdges = options.generateEdges ?? type === GraphicType.Scene;
        const wantNormals = wantEdges || type === GraphicType.Scene;
        const preserveOrder = type === GraphicType.ViewOverlay || type === GraphicType.WorldOverlay || type === GraphicType.ViewBackground;
        super({
            ...options,
            [_implementationProhibited]: undefined,
            type,
            placement,
            wantEdges,
            wantNormals,
            preserveOrder,
        });
        this._computeChordTolerance = options.computeChordTolerance;
        this._viewIndependentOrigin = options.viewIndependentOrigin?.clone();
        this._context = options.context;
        if (!(this._context.transientIds instanceof TransientIdSequence)) {
            throw new Error("Invalid WorkerGraphicDescriptionContext");
        }
    }
    finish() {
        const description = {
            [_implementationProhibited]: undefined,
            type: this.type,
            primitives: [],
        };
        if (this[_accumulator].isEmpty) {
            return description;
        }
        const tolerance = this._computeChordTolerance({ builder: this, computeRange: () => this[_accumulator].geometries.computeRange() });
        const meshes = this[_accumulator].toMeshes(this, tolerance, this.pickable);
        if (meshes.length === 0) {
            return description;
        }
        // If the meshes contain quantized positions, they are all quantized to the same range. If that range is small relative to the distance
        // from the origin, quantization errors can produce display artifacts. Remove the translation from the quantization parameters and apply
        // it in the transform instead.
        // If the positions are not quantized, they have already been transformed to be relative to the center of the meshes' range.
        // Apply the inverse translation to put them back into model space.
        let transformOrigin;
        let meshesRangeOffset = false;
        for (const mesh of meshes) {
            const verts = mesh.points;
            if (!transformOrigin) {
                // This is the first mesh we've processed.
                if (verts instanceof QPoint3dList) {
                    transformOrigin = verts.params.origin.clone();
                    verts.params.origin.setZero();
                }
                else {
                    // In this case we need to modify the qOrigin of the graphic that will get created later since we have translated the origin.
                    // We can't modify it directly, but if we temporarily modify the range of the mesh used to create it the qOrigin will get created properly.
                    // Range is shared (not cloned) by all meshes and the mesh list itself, so modifying the range of the meshlist will modify it for all meshes.
                    // This will become the range in local coordinates of the batch, if we are creating a batch.
                    transformOrigin = verts.range.center;
                    if (!meshesRangeOffset) {
                        meshes.range?.low.subtractInPlace(transformOrigin);
                        meshes.range?.high.subtractInPlace(transformOrigin);
                        meshesRangeOffset = true;
                    }
                }
            }
            else {
                if (verts instanceof QPoint3dList) {
                    assert(transformOrigin.isAlmostEqual(verts.params.origin));
                    verts.params.origin.setZero();
                }
                else {
                    assert(verts.range.center.isAlmostZero);
                }
            }
            const primitive = this.createPrimitive(mesh);
            if (primitive) {
                const origin = this._viewIndependentOrigin;
                if (origin) {
                    primitive.modifier = {
                        type: "viewIndependentOrigin",
                        origin: { x: origin.x, y: origin.y, z: origin.z },
                    };
                }
                description.primitives.push(primitive);
            }
        }
        this[_accumulator].clear();
        if (transformOrigin) {
            description.translation = { x: transformOrigin.x, y: transformOrigin.y, z: transformOrigin.z };
        }
        const featureTable = this.pickable && meshes.features?.anyDefined ? meshes.features : undefined;
        if (featureTable) {
            assert(undefined !== this.pickable);
            const features = PackedFeatureTable.pack(featureTable);
            const range = meshes.range ?? new Range3d();
            description.batch = {
                ...this.pickable,
                range: range.toJSON(),
                modelId: this.pickable.modelId ?? this.pickable.id,
                featureTable: {
                    multiModel: false,
                    data: features.data,
                    numFeatures: features.numFeatures,
                    animationNodeIds: features.animationNodeIds,
                },
            };
        }
        return description;
    }
    createPrimitive(mesh) {
        const meshArgs = mesh.toMeshArgs();
        if (meshArgs) {
            return this.createMeshPrimitive(meshArgs);
        }
        const polylineArgs = mesh.toPolylineArgs();
        if (!polylineArgs) {
            return undefined;
        }
        return polylineArgs.flags.isDisjoint ? this.createPointStringPrimitive(polylineArgs) : this.createPolylinePrimitive(polylineArgs);
    }
    createMeshPrimitive(args) {
        const params = createMeshParams(args, this._context.constraints.maxTextureSize, true);
        let material;
        const mat = params.surface.material;
        if (mat) {
            assert(mat.isAtlas === false && mat.material instanceof WorkerMaterial);
            material = mat.material.toImdl();
        }
        let textureMapping;
        const tex = params.surface.textureMapping;
        if (tex) {
            assert(tex.texture instanceof WorkerTexture);
            textureMapping = { alwaysDisplayed: false, texture: tex.texture.index.toString(10) };
        }
        return {
            type: "mesh",
            params: {
                ...params,
                vertices: convertVertexTable(params.vertices),
                auxChannels: params.auxChannels?.toJSON(),
                edges: params.edges ? edgeParamsToImdl(params.edges) : undefined,
                surface: {
                    ...params.surface,
                    indices: params.surface.indices.data,
                    material,
                    textureMapping,
                },
            },
        };
    }
    createPolylinePrimitive(args) {
        const params = createPolylineParams(args, this._context.constraints.maxTextureSize);
        if (!params) {
            return undefined;
        }
        return {
            type: "polyline",
            params: {
                ...params,
                vertices: convertVertexTable(params.vertices),
                polyline: {
                    indices: params.polyline.indices.data,
                    prevIndices: params.polyline.prevIndices.data,
                    nextIndicesAndParams: params.polyline.nextIndicesAndParams,
                },
            },
        };
    }
    createPointStringPrimitive(args) {
        const params = createPointStringParams(args, this._context.constraints.maxTextureSize);
        if (!params) {
            return undefined;
        }
        return {
            type: "point",
            params: {
                indices: params.indices.data,
                vertices: convertVertexTable(params.vertices),
                weight: params.weight,
            },
        };
    }
    resolveGradient(gradient) {
        return this._context.createGradientTexture(gradient);
    }
}
function convertVertexTable(src) {
    return {
        ...src,
        qparams: src.qparams.toJSON(),
        uvParams: src.uvParams?.toJSON(),
        uniformColor: src.uniformColor?.toJSON(),
    };
}
export function isGraphicDescription(description) {
    const descr = description;
    return "object" === typeof descr && Array.isArray(descr.primitives) && "number" === typeof descr.type;
}
export function collectGraphicDescriptionTransferables(xfers, description) {
    if (!isGraphicDescription(description)) {
        throw new Error("Invalid GraphicDescription");
    }
    for (const primitive of description.primitives) {
        addPrimitiveTransferables(xfers, primitive);
    }
}
//# sourceMappingURL=GraphicDescriptionBuilderImpl.js.map