/*---------------------------------------------------------------------------------------------
* 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, compareNumbers, compareStrings, Id64, IndexedValue, IndexMap, } from "@itwin/core-bentley";
import { GeometryClass } from "./GeometryParams";
import { PackedFeatureTable } from "./internal/PackedFeatureTable";
/** Describes a discrete entity within a batched [RenderGraphic]($frontend) that can be
 * grouped with other such entities in a [[FeatureTable]].
 * Features roughly correlate to elements: a [Tile]($frontend)'s graphics combines geometry from every
 * [GeometricElement]($backend) that intersects the tile's volume, so each element produces at least one feature.
 * However, an element's geometry stream can contain geometry belonging to multiple different combinations of [SubCategory]($backend) and
 * [[GeometryClass]], so an individual element may produce more than one feature.
 * @see [[FeatureOverrides]] for customizing the appearance of individual features.
 * @public
 */
export class Feature {
    elementId;
    subCategoryId;
    geometryClass;
    constructor(elementId = Id64.invalid, subCategoryId = Id64.invalid, geometryClass = GeometryClass.Primary) {
        this.elementId = elementId;
        this.subCategoryId = subCategoryId;
        this.geometryClass = geometryClass;
    }
    get isDefined() { return !Id64.isInvalid(this.elementId) || !Id64.isInvalid(this.subCategoryId) || this.geometryClass !== GeometryClass.Primary; }
    get isUndefined() { return !this.isDefined; }
    /** Returns true if this feature is equivalent to the supplied feature. */
    equals(other) { return 0 === this.compare(other); }
    /** Performs ordinal comparison of this feature with another.
     * @param rhs The feature to compare with.
     * @returns zero if the features are equivalent, a negative value if this feature compares as "less than" `rhs`, or a positive value if this feature compares "greater than" `rhs`.
     */
    compare(rhs) {
        if (this === rhs)
            return 0;
        let cmp = compareNumbers(this.geometryClass, rhs.geometryClass);
        if (0 === cmp) {
            cmp = compareStrings(this.elementId, rhs.elementId);
            if (0 === cmp) {
                cmp = compareStrings(this.subCategoryId, rhs.subCategoryId);
            }
        }
        return cmp;
    }
}
/** @public */
export var ModelFeature;
(function (ModelFeature) {
    /** Create a ModelFeature of [[GeometryClass.Primary]] with all invalid Ids.
     * This is primarily useful for creating a `result` argument for [[RenderFeatureTable.findFeature]] and [[RenderFeatureTable.getFeature]].
     */
    function create() {
        return {
            modelId: Id64.invalid,
            elementId: Id64.invalid,
            subCategoryId: Id64.invalid,
            geometryClass: GeometryClass.Primary,
        };
    }
    ModelFeature.create = create;
    /** Returns `true` if any of `feature`'s properties differ from the defaults (invalid Ids and [[GeometryClass.Primary]]). */
    function isDefined(feature) {
        return !Id64.isInvalid(feature.modelId) || !Id64.isInvalid(feature.elementId) || !Id64.isInvalid(feature.subCategoryId) || feature.geometryClass !== GeometryClass.Primary;
    }
    ModelFeature.isDefined = isDefined;
    /** @alpha */
    function unpack(packed, result, unpackedModelId) {
        result.modelId = unpackedModelId ?? Id64.fromUint32PairObject(packed.modelId);
        result.elementId = Id64.fromUint32PairObject(packed.elementId);
        result.subCategoryId = Id64.fromUint32PairObject(packed.subCategoryId);
        result.geometryClass = packed.geometryClass;
        return result;
    }
    ModelFeature.unpack = unpack;
})(ModelFeature || (ModelFeature = {}));
/** @public */
export var PackedFeature;
(function (PackedFeature) {
    /** Create a PackedFeature of [[GeometryClass.Primary]] with all invalid Ids.
     * This is primarily useful for creating a `result` argument for [[RenderFeatureTable.getPackedFeature]].
     */
    function create() {
        const pair = { upper: 0, lower: 0 };
        return {
            modelId: { ...pair },
            elementId: { ...pair },
            subCategoryId: { ...pair },
            geometryClass: GeometryClass.Primary,
            animationNodeId: 0,
        };
    }
    PackedFeature.create = create;
    /** Create a PackedFeatureWithIndex of [[GeometryClass.Primary]] with all invalid Ids and an index of zero.
     * This is primarily useful for creating a reusable `output` argument for [[RenderFeatureTable.iterable]].
     */
    function createWithIndex() {
        const result = create();
        result.index = 0;
        return result;
    }
    PackedFeature.createWithIndex = createWithIndex;
})(PackedFeature || (PackedFeature = {}));
/** Describes the type of a 'batch' of graphics representing multiple [[Feature]]s.
 * The most commonly-encountered batches are Tiles, which can be of either Primary or
 * Classifier type.
 * @public
 * @extensions
 */
export var BatchType;
(function (BatchType) {
    /** This batch contains graphics derived from a model's visible geometry. */
    BatchType[BatchType["Primary"] = 0] = "Primary";
    /**
     * This batch contains color volumes which are used to classify a model's visible geometry.
     * The graphics themselves are not rendered to the screen; instead they are rendered to the stencil buffer
     * to resymbolize the primary geometry.
     */
    BatchType[BatchType["VolumeClassifier"] = 1] = "VolumeClassifier";
    /**
     * This batch contains planar graphics which are used to classify a model's visible geometry.
     * The graphics themselves are not rendered to the screen; instead they are rendered to a texture buffer
     * to resymbolize the primary geometry.
     */
    BatchType[BatchType["PlanarClassifier"] = 2] = "PlanarClassifier";
})(BatchType || (BatchType = {}));
/** Defines a look-up table for [[Feature]]s within a batched [RenderGraphic]($frontend). Consecutive 32-bit
 * indices are assigned to each unique Feature. Primitives within the RenderGraphic can
 * use per-vertex indices to specify the distribution of Features within the primitive. The appearance of individual
 * features can be customized using [[FeatureOverrides]]. Typically a [Tile]($frontend) will contain a feature table
 * identifying the elements whose geometry appears within that tile.
 * @see [[FeatureOverrides]] for customizing the appearance of individual features.
 * @public
 */
export class FeatureTable extends IndexMap {
    modelId;
    type;
    /** Construct an empty FeatureTable. */
    constructor(maxFeatures, modelId = Id64.invalid, type = BatchType.Primary) {
        super((lhs, rhs) => lhs.compare(rhs), maxFeatures);
        this.modelId = modelId;
        this.type = type;
    }
    /** Returns the maximum number of [[Feature]]s this FeatureTable can contain. */
    get maxFeatures() { return this._maximumSize; }
    /** Returns true if this table contains at least one [[Feature]] with a valid element and/or subcategory Id. */
    get anyDefined() { return this.length > 1 || (1 === this.length && this._array[0].value.isDefined); }
    /** Returns true if this FeatureTable contains exactly one [[Feature]]. */
    get isUniform() { return 1 === this.length; }
    /** If this FeatureTable contains exactly one [[Feature]], returns that Feature; otherwise returns undefined. */
    get uniform() { return 1 === this.length ? this._array[0].value : undefined; }
    /** Returns true if this FeatureTable is associated with [[BatchType.VolumeClassifier]] geometry. */
    get isVolumeClassifier() { return BatchType.VolumeClassifier === this.type; }
    /** Returns true if this FeatureTable is associated with [[BatchType.PlanarClassifier]] geometry. */
    get isPlanarClassifier() { return BatchType.PlanarClassifier === this.type; }
    /** Returns the Feature corresponding to the specified index, or undefined if the index is not present. */
    findFeature(index) {
        for (const entry of this._array)
            if (entry.index === index)
                return entry.value;
        return undefined;
    }
    /** Inserts the specified [[Feature]] at the specified index. This is really only useful when reconstructing a previously-create feature table
     * for which you know the index assigned to each feature.
     */
    insertWithIndex(feature, index) {
        const bound = this.lowerBound(feature);
        assert(!bound.equal);
        assert(!this.isFull);
        const entry = new IndexedValue(feature, index);
        this._array.splice(bound.index, 0, entry);
    }
    /** Access the underlying array containing the table's current contents. */
    getArray() { return this._array; }
    /** Convert this feature table to a representation that can be supplied to [RenderSystem.createBatch]($frontend). */
    pack() {
        return PackedFeatureTable.pack(this);
    }
}
//# sourceMappingURL=FeatureTable.js.map