/** @internal NOTE: Internal APIs. Subject to change. Use of these APIs in production applications is not supported. */
import { BimIfcClass } from './bim-ifc-class';
import { setMax, setMin } from '../math';
import { BoundingInfo, Vector3 } from './babylonjs-import';
import { VertexDataMerge, reservedPrimitiveCount } from './vertex-data-merge';
import { GeometryBuffer } from './GeometryBuffer';
import { IfcMaterialHighlightIndex } from './CustomBabylonMaterials/IfcMaterial';
import { _bimIfcObjectIntersect } from './bim-ifc-object-intersect';
import { ModelProductClass } from '@twinfinity/t8-client-model';
const emptyObject = Object.freeze({});
const emptyRecord = Object.freeze({});
const noMeshes = Object.freeze([]);
const tmpMin = Vector3.Zero();
const tmpMax = Vector3.Zero();
export function DefaultBimIfcObjectForEachPredicate(o, recursionOptions) {
    return true;
}
/**
 * This class contains the structure of an IFC Object
 */
export class BimIfcObject {
    entityLabelInIfc;
    ifcLoaderElement;
    parents;
    enclosingFloor;
    static _noChildren = Object.freeze([]);
    _children = BimIfcObject._noChildren;
    rawProduct;
    /**
     * Returns true if object is visible
     */
    visible;
    propertySets = emptyRecord;
    computedPropertySets = emptyRecord;
    quantitySets = emptyRecord;
    /**
     * Returns the class object for IFC-product
     */
    class;
    hasGeometry;
    constructor(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor) {
        this.entityLabelInIfc = entityLabelInIfc;
        this.ifcLoaderElement = ifcLoaderElement;
        this.parents = parents;
        this.enclosingFloor = enclosingFloor;
        rawProduct.c = rawProduct.c ?? 0;
        rawProduct.cl = rawProduct.cl ?? emptyObject;
        rawProduct.d = rawProduct.d ?? ifcLoaderElement.index.texts.length - 1;
        rawProduct.m = rawProduct.m ?? noMeshes;
        rawProduct.n = rawProduct.n ?? ifcLoaderElement.index.texts.length - 1;
        rawProduct.pd = rawProduct.pd ?? emptyObject;
        this.rawProduct = rawProduct;
        let hasGeometry = this.productMeshes.length > 0;
        if (hasGeometry && this.ifcLoaderElement.loadOptions?.ifcObjectHasGeometryPredicate) {
            hasGeometry = this.ifcLoaderElement.loadOptions.ifcObjectHasGeometryPredicate(this);
        }
        if (!hasGeometry) {
            this.visible = BimIfcObject.neverVisible;
            rawProduct.m = noMeshes; // Trim away mesh objects we cannot show anyway.
        }
        else {
            this.visible = this.possiblyVisible;
        }
        this.hasGeometry = hasGeometry;
        this.class = BimIfcClass.getOrAdd(this.ifcLoaderElement.index.classes[this.rawProduct.c]);
    }
    /** Creates a geometry consisting of all specified IFC objects (or specific meshes.).
     * @param items All objects that will be combined. It is possible to specify multiple {@link BimProductMesh}
     * to be used on each IFC object. If none are specified, then all meshes in the IFC objects are used.
     * When {@link BimIfcObject.isOnGpu} === `false`, the geometry will automatically be loaded and included in the final
     * {@link Geometry3d}.
     * @returns A {@link Geometry3d} instance that represents all geometry. Note that the geometry is in IFC space and not BabylonJS worldspace.
     */
    static async createGeometryFromAsync(items) {
        const { usedLoaderElements, vdm } = BimIfcObject.initializeCreateGeometryFrom(items, true);
        const vertexDataPromises = [];
        for (const lE of usedLoaderElements) {
            vertexDataPromises.push(lE.ensureVertexDataLoaded());
        }
        await Promise.all(vertexDataPromises);
        return vdm.merge();
    }
    /** Creates a geometry consisting of all specified IFC objects (or specific meshes.).
     * @param items All objects that will be combined. It is possible to optionally specify exactly which {@link BimProductMesh} that are
     * to be used on each IFC object. If none is specified, then all meshes in the IFC objects are used.
     * Geom. Items where {@link BimIfcObject.isOnGpu} === `false` will not be included in the final
     * {@link Geometry3d}.
     * @returns A {@link Geometry3d} instance that represents all geometry. Note that the geometry is in IFC space and not BabylonJS worldspace.
     */
    static createGeometryFrom(items) {
        const { vdm } = BimIfcObject.initializeCreateGeometryFrom(items, false);
        return vdm.merge();
    }
    static create(ifcLoaderElement) {
        const p = ifcLoaderElement.index.project;
        return new BimIfcObject('project', ifcLoaderElement, p, []);
    }
    /**
     * Calculates the min and max value in the coordinate system.
     * @param min Minimum bounding vector3
     * @param max Maximum bounding vector3
     * @param predicate ?
     * @param noCache If `true`, transform caches will not be used.
     */
    aabb(min, max, predicate = DefaultBimIfcObjectForEachPredicate, noCache = true) {
        let predicateResult = false;
        this.foreach((o, recursionOptions) => {
            predicateResult = predicate(o, recursionOptions);
            if (predicateResult && o.hasGeometry) {
                for (const m of o.productMeshes) {
                    m.aabb(min, max, noCache);
                }
            }
            return predicateResult;
        });
    }
    isOnGpu(meshes) {
        const _meshes = meshes ?? this.productMeshes;
        const len = _meshes.length;
        let onGpu = len > 0;
        for (let i = 0; i < len && onGpu; ++i) {
            onGpu = _meshes[i].isOnGpu;
        }
        return onGpu;
    }
    get ifc() {
        return this.ifcLoaderElement.ifc;
    }
    get gid() {
        return this.rawProduct.gid;
    }
    get globalId() {
        return this.rawProduct.gid;
    }
    /**
     * @deprecated Use {@link boundingInfo} or {@link aabb}.
     */
    get bbox() {
        setMax(tmpMin);
        setMin(tmpMax);
        this.aabb(tmpMin, tmpMax);
        return [tmpMin.x, tmpMin.y, tmpMin.z, tmpMax.x, tmpMax.y, tmpMax.z];
    }
    boundingInfo(bI, predicate = DefaultBimIfcObjectForEachPredicate, noCache = true) {
        if (bI === undefined) {
            setMax(tmpMin);
            setMin(tmpMax);
            this.aabb(tmpMin, tmpMax, predicate, noCache);
            return new BoundingInfo(tmpMin, tmpMax);
        }
        tmpMin.copyFrom(bI.minimum);
        tmpMax.copyFrom(bI.maximum);
        this.aabb(tmpMin, tmpMax, predicate, noCache);
        bI.reConstruct(tmpMin, tmpMax);
        return bI;
    }
    get childCount() {
        if (this._children !== BimIfcObject._noChildren) {
            return this._children.length;
        }
        if (this.rawProduct.cl) {
            return Object.keys(this.rawProduct.cl).length;
        }
        return 0;
    }
    get productMeshes() {
        return this.rawProduct.m;
    }
    meshDescriptor(pM) {
        return pM.descriptor;
    }
    /**
     * Returns the discipline class for this IFC-product
     */
    get discipline() {
        return this.ifcLoaderElement.discipline;
    }
    style(m) {
        return m.style;
    }
    vertexData(m) {
        return m.vertexData;
    }
    transform(m, noCache = true) {
        return m.transform(noCache);
    }
    mesh(m, noCache = true) {
        return m.mesh(noCache);
    }
    /**
     * Returns name of IFC-product
     */
    get name() {
        return this.ifcLoaderElement.index.texts[this.rawProduct.n];
    }
    get description() {
        return this.ifcLoaderElement.index.texts[this.rawProduct.d];
    }
    get type() {
        if (this.rawProduct.t == null) {
            return undefined;
        }
        return this.ifcLoaderElement.typeObjectRepository.get(this.rawProduct.t);
    }
    get localId() {
        return this.rawProduct.e;
    }
    get className() {
        return this.ifcLoaderElement.index.classes[this.rawProduct.c];
    }
    get data() {
        return this.rawProduct.pd;
    }
    get meshes() {
        return this.rawProduct.m;
    }
    setComputedPropertySet(name, set) {
        const current = this.computedPropertySets;
        if (Object.isFrozen(current)) {
            this.computedPropertySets = { ...current, [name]: set };
        }
        else {
            current[name] = set;
        }
    }
    deleteComputedPropertySet(name) {
        const current = this.computedPropertySets;
        if (!(name in current)) {
            return false;
        }
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const { [name]: _, ...rest } = current;
        this.computedPropertySets = rest;
        return true;
    }
    defaultColor(meshes) {
        let wasChanged = false;
        const _meshes = meshes ?? this.productMeshes;
        const len = _meshes.length;
        for (let i = 0; i < len; ++i) {
            wasChanged = _meshes[i].defaultColor() || wasChanged;
        }
        return wasChanged;
    }
    setColors(src) {
        let wasChanged = false;
        for (const [m, c] of src) {
            wasChanged = m.setColor(c) || wasChanged;
        }
        return wasChanged;
    }
    setColor(color, meshes) {
        let wasChanged = false;
        const _meshes = meshes ?? this.productMeshes;
        const len = _meshes.length;
        for (let i = 0; i < len; ++i) {
            wasChanged = _meshes[i].setColor(color) || wasChanged;
        }
        return wasChanged;
    }
    /**
     * Highlights the object
     * @param meshes The meshes to highlight
     * @param highlightIndex The highlight color index to use, setting it to zero means the mesh will not be highlighted
     * @returns a boolean indicating if the object was changed from the highlighting
     */
    highlight(highlightIndex = IfcMaterialHighlightIndex.One, meshes) {
        let wasChanged = false;
        meshes = meshes ?? this.productMeshes;
        const len = meshes.length;
        for (let i = 0; i < len; ++i) {
            const prev = meshes[i].highlight;
            meshes[i].highlight = highlightIndex;
            wasChanged ||= prev !== highlightIndex;
        }
        return wasChanged;
    }
    /**
     * Makes the object give an ghosty outline when either SSAO or line outlines are enabled. It does this by making the object invisible
     * but also flag it to still write to the depth buffer used by the lines and SSAO
     * @param enabled If true the object will be hidden but will also be set to still write to the depth buffer
     * @param meshes The specific meshes of the object to apply this method on
     * @returns A bool indicating if any of the objects meshes was changed as a result of this method call
     */
    ghostOutline(enabled, meshes) {
        let anyChanged = false;
        const _meshes = meshes ?? this.productMeshes;
        for (let i = 0; i < _meshes.length; i++) {
            const mesh = _meshes[i];
            const prev = mesh.ghostOutline;
            mesh.ghostOutline = enabled;
            anyChanged ||= prev !== enabled;
        }
        return anyChanged;
    }
    /**
     * Aggregates a bbox from current objects meshes.
     * This does not include related children.
     * This is alpha. Expect changes. Will probably be replaced by
     * one of the other bounding info functions.
     * @deprecated Use {@link boundingInfo} or {@link aabb}.
     * @alpha
     */
    computeObjectBboxInWorldSpace() {
        const bound = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
        const temp = new Vector3();
        for (const m of this.productMeshes) {
            const thisMatrix = this.ifcLoaderElement.transformsRepository.getMatrix(m);
            const bbox = this.meshDescriptor(m).bbox;
            Vector3.TransformCoordinatesToRef(temp.set(bbox[0], bbox[1], bbox[2]), thisMatrix, temp);
            if (temp.x < bound[0]) {
                bound[0] = temp.x;
            }
            if (temp.y < bound[1]) {
                bound[1] = temp.y;
            }
            if (temp.z < bound[2]) {
                bound[2] = temp.z;
            }
            Vector3.TransformCoordinatesToRef(temp.set(bbox[3], bbox[4], bbox[5]), thisMatrix, temp);
            if (temp.x > bound[3]) {
                bound[3] = temp.x;
            }
            if (temp.y > bound[4]) {
                bound[4] = temp.y;
            }
            if (temp.z > bound[5]) {
                bound[5] = temp.z;
            }
        }
        return bound;
    }
    // TODO Probaby remove and use operations on BoundingInfo instead.
    simpleBoundingIntersection(containingBound, objectBound) {
        // test bounding sphere
        const objectMin = [objectBound[0], objectBound[1], objectBound[2]];
        const objectMax = [objectBound[3], objectBound[4], objectBound[5]];
        const objectX = objectMax[0] - objectMin[0];
        const objectY = objectMax[1] - objectMin[1];
        const objectZ = objectMax[2] - objectMin[2];
        const objectRadius = Math.max(objectX, objectY, objectZ) * 0.5;
        const objectCenter = [objectX / 2 + objectMin[0], objectY / 2 + objectMin[1], objectZ / 2 + objectMin[2]];
        const containingMin = [containingBound[0], containingBound[1], containingBound[2]];
        const containingMax = [containingBound[3], containingBound[4], containingBound[5]];
        const containingX = containingMax[0] - containingMin[0];
        const containingY = containingMax[1] - containingMin[1];
        const containingZ = containingMax[2] - containingMin[2];
        const containingRadius = Math.max(containingX, containingY, containingZ) * 0.5;
        const containingCenter = [
            containingX / 2 + containingMin[0],
            containingY / 2 + containingMin[1],
            containingZ / 2 + containingMin[2]
        ];
        const a = containingCenter[0] - objectCenter[0];
        const b = containingCenter[1] - objectCenter[1];
        const c = containingCenter[2] - objectCenter[2];
        const distance = Math.sqrt(a * a + b * b + c * c);
        if (distance - containingRadius - objectRadius < 0) {
            return true;
        }
        // test bounding box
        const corners = [
            objectMin,
            [objectBound[3], objectBound[1], objectBound[2]],
            [objectBound[0], objectBound[1], objectBound[5]],
            [objectBound[3], objectBound[1], objectBound[5]],
            [objectBound[0], objectBound[4], objectBound[2]],
            [objectBound[3], objectBound[4], objectBound[2]],
            [objectBound[0], objectBound[4], objectBound[5]],
            objectMax
        ];
        for (const corner of corners) {
            if (corner[0] > containingBound[0] && corner[0] < containingBound[3]) {
                if (corner[1] > containingBound[1] && corner[1] < containingBound[4]) {
                    if (corner[2] > containingBound[2] && corner[2] < containingBound[5]) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    /**
     * Checks intersections of an object (containing object) against an array of other objects.
     * Only for internal use. Subject to change. Use of these method in production applications is not supported. If used, it is at your own risk.
     * @alpha
     * @internal
     * @hidden
     * */
    _geometricallyIntersects(objects) {
        // Before this method goes into production, it needs to be tested, optimized and probably refactored.
        // The intent is to replace geometricallyIntersect at some point. HOWEVER ITS CURRENT SIGNATURE WILL MOST LIKELY NOT
        // BE THE FINAL VERSION. IT WILL PROBABLY BE REPLACED BY CORRESPONDING METHODS ON IfcProductMesh.ts instad (which this method will call).
        // It will also instances of the BimProductMesh interface (ie IfcProductMesh instances).
        return _bimIfcObjectIntersect(this, objects);
    }
    /**
     * Checks intersections of an object (containing object) against an array of other objects.
     * @param objects Array of objects to test intersection against.
     * @param includeEnclosedObjects Default is true, it includes geometries completely geometrically on the inside, in the returned result.
     * @param includeIntersectingObjects Default is true, it includes geometries that intersects on border surface, in the returned result.
     * If both enclosedObjects and intersectingObjects are false, the function will make a quick rough test based on bbox and return all objects intersecting and enclosing on bounding box level.
     */
    geometricallyIntersects(objects, includeEnclosedObjects = true, includeIntersectingObjects = true) {
        if (!this.hasGeometry) {
            return [];
        }
        const firstSelectionFastRoughTest = []; // Will contain all objects that intersects with this BoundingInfo
        const cachedBounds = [];
        const containingBound = this.computeObjectBboxInWorldSpace();
        // this function tests all bounding boxes
        for (const object of objects) {
            if (!object.hasGeometry) {
                continue;
            }
            const objectBound = object.computeObjectBboxInWorldSpace();
            if (this.simpleBoundingIntersection(containingBound, objectBound)) {
                firstSelectionFastRoughTest.push(object);
                cachedBounds.push(objectBound);
            }
        }
        const objectsCompletelyInsideGeometry = []; // will contain objects bounding box are completely inside this geometry
        const secondTestNotCompletelyInside = []; // will contain the rest
        const boundIndex = [];
        // This loop test which objects are completely inside the bounding box.
        for (let i = 0; i < firstSelectionFastRoughTest.length; i++) {
            const object = firstSelectionFastRoughTest[i];
            let isInside = false;
            for (const m of this.productMeshes) {
                const containingMesh = this.mesh(m);
                isInside = containingMesh.geometricallyContainsBoundingBox(cachedBounds[i]);
                if (isInside) {
                    objectsCompletelyInsideGeometry.push(object);
                    break;
                }
            }
            if (!isInside) {
                secondTestNotCompletelyInside.push(object);
                boundIndex.push(cachedBounds[i]);
            }
        }
        // this loop checks if object bbox intersects with containing mesh
        const objectsBboxIntersects = [];
        for (let i = 0; i < secondTestNotCompletelyInside.length; i++) {
            const object = secondTestNotCompletelyInside[i];
            for (const m of this.productMeshes) {
                const containingMesh = this.mesh(m);
                const intersecting = containingMesh.geometricallyIntersectingBoundingBox(boundIndex[i]);
                if (intersecting) {
                    objectsBboxIntersects.push(object);
                    break;
                }
            }
        }
        // if not interested in enclosed or intersecting on hi resolution test, just return the first bbox test result
        if (!includeEnclosedObjects && !includeIntersectingObjects) {
            for (const obj of objectsCompletelyInsideGeometry) {
                objectsBboxIntersects.push(obj);
            }
            return objectsBboxIntersects;
        }
        const objectsGeometricallyIntersects = []; // will contain only objects that intersects with containing geometry surface.
        const objectsBboxNotInsideAndNotGeometriclyIntersecting = []; // will contain the rest. Can be completely on inside geometry, but bbox intersects, and not geometry.
        // This loop tests witch object got vertices on both inside and outside of containing geometry.
        for (let i = 0; i < objectsBboxIntersects.length; i++) {
            const object = objectsBboxIntersects[i];
            let onTheBorder = false;
            for (const m of object.productMeshes) {
                const objectMesh = object.mesh(m);
                for (const m of this.productMeshes) {
                    const containingMesh = this.mesh(m);
                    onTheBorder = containingMesh.geometricallyIntersects(objectMesh);
                    if (onTheBorder) {
                        objectsGeometricallyIntersects.push(object);
                        break;
                    }
                }
                if (onTheBorder) {
                    break;
                }
            }
            if (!onTheBorder) {
                objectsBboxNotInsideAndNotGeometriclyIntersecting.push(object);
            }
        }
        // This loop test witch object is completely inside containing geometry.
        if (includeEnclosedObjects) {
            for (const object of objectsBboxNotInsideAndNotGeometriclyIntersecting) {
                let allInside = true;
                for (const m of object.productMeshes) {
                    const objectMesh = object.mesh(m);
                    let isInside = false;
                    for (const m of this.productMeshes) {
                        const containingMesh = this.mesh(m);
                        isInside = containingMesh.geometricallyContains(objectMesh);
                        if (isInside) {
                            break;
                        }
                    }
                    if (!isInside) {
                        allInside = false;
                        break;
                    }
                }
                if (allInside) {
                    objectsCompletelyInsideGeometry.push(object);
                }
            }
        }
        const objectsToReturn = [];
        if (includeIntersectingObjects) {
            for (const obj of objectsGeometricallyIntersects) {
                objectsToReturn.push(obj);
            }
        }
        if (includeEnclosedObjects) {
            for (const obj of objectsCompletelyInsideGeometry) {
                objectsToReturn.push(obj);
            }
        }
        return objectsToReturn;
    }
    /**
     * @deprecated No longer does anything.
     */
    applyColor() { }
    // Copy color of all meshes in object to dst
    copyColorsTo(dst) {
        for (const m of this.productMeshes) {
            const rgba = dst.get(m) ?? [0, 0, 0, 0];
            m.copyColorTo(rgba);
            dst.set(m, rgba);
        }
    }
    copyColorTo(m, dst, dstOffset = 0) {
        return m.copyColorTo(dst, dstOffset);
    }
    getColorComponent(m, colorComponent) {
        return m.getColorComponent(colorComponent);
    }
    /** Get the closest parent in the IFC hiearchy. Equivalent of
     * @example
     * ```typescript
     * const parent = bimIfcObject.parents[bimIfcObjects.parents.length -1];
     * ```
     */
    get parent() {
        return this.parents.length > 0 ? this.parents[this.parents.length - 1] : undefined;
    }
    /** Remove the IFC object and its children from the IFC hiearchy. */
    remove() {
        if (this.parent) {
            const cLen = this.parent._children?.length;
            if (this.parent._children !== BimIfcObject._noChildren && cLen) {
                for (let i = 0; i < cLen; ++i) {
                    if (this.parent._children[i] === this) {
                        this.parent._children?.splice(i, 1);
                        break;
                    }
                }
            }
            this.parents.length = 0;
            // TODO If we have removed the node then we can also delete a lot of data
            // from the .idx file. Such as no longer used transforms, meshes, meshdescriptors etc
        }
    }
    get hasStructuredRelationships() {
        return false;
    }
    get aggregates() {
        return BimIfcObject._noChildren;
    }
    get contains() {
        return BimIfcObject._noChildren;
    }
    /** All children IFC objects. */
    get children() {
        if (this._children !== BimIfcObject._noChildren) {
            return this._children;
        }
        const parents = [...this.parents, this];
        const children = [];
        const enclosingFloor = this instanceof BimIfcBuildingStorey ? this : this.enclosingFloor;
        for (const childId in this.rawProduct.cl) {
            children.push(this.createBimIfcObject(childId, this.ifcLoaderElement, this.rawProduct.cl[childId], parents, enclosingFloor));
        }
        // Only set this._children if we have children. this._children originally refers to the static _noChildren array.
        // If no children exist we continue to refer to it to save memory. Remember its the leafnodes
        // that will have no children. And there's way more of them than there are of the nodes that have children.
        if (children.length > 0) {
            this._children = children;
        }
        // Save some memory since we now have the info in a BimIfcProduct instance
        // anyway. Faster to set as undefined than doing delete and delete can also interfere
        // with compiler optimizations.
        this.rawProduct.cl = undefined;
        return this._children;
    }
    foreach(action) {
        const recursionOptions = {
            stopRecursion: false,
            abort: false
        };
        for (const product of this.entries()) {
            action(product, recursionOptions);
            if (recursionOptions.abort) {
                break;
            }
            else if (recursionOptions.stopRecursion) {
                recursionOptions.stopRecursion = false;
                continue; // Do not visit any more child nodes below this node
            }
        }
    }
    /**
     * Gets a iterator for all Ifc products currently loaded.
     */
    *entries() {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        let next = this;
        const stack = [next];
        while (stack.length > 0) {
            next = stack.pop();
            // reference children here to ensure that when next is returned then next._children array
            // is already built. Would prefer to use the ... operator
            // but that can cause the call stack size to exceed the limit because it
            // pushes every element of the array onto the call stack.
            for (const child of next.children) {
                stack.push(child);
            }
            yield next;
        }
    }
    get project() {
        return this.parents[0];
    }
    get site() {
        return this.parents[1];
    }
    get building() {
        return this.parents[2];
    }
    get facility() {
        return this.building;
    }
    get facilityPart() {
        return this.enclosingFloor;
    }
    static neverVisible() {
        return false;
    }
    static initializeCreateGeometryFrom(items, allowNotOnGpu) {
        const usedLoaderElements = new Set();
        const ret = [];
        for (const i of items) {
            if ('ifcObject' in i && (allowNotOnGpu || i.ifcObject.isOnGpu())) {
                usedLoaderElements.add(i.ifcObject.ifcLoaderElement);
                const meshes = i.meshes?.length > 0 ? i.meshes : i.ifcObject.productMeshes;
                for (const m of meshes) {
                    ret.push(m);
                }
            }
            else if (i instanceof BimIfcObject && (allowNotOnGpu || i.isOnGpu())) {
                usedLoaderElements.add(i.ifcLoaderElement);
                for (const m of i.productMeshes) {
                    ret.push(m);
                }
            }
        }
        // Slack from per-face-normal expansion / WASM dedup is reclaimed by VertexDataMerge.compact()
        // at the end of merge() since we pass compactAfterMerge: true below.
        let primitiveCount = 0;
        let indiceCount = 0;
        for (const mesh of ret) {
            primitiveCount += reservedPrimitiveCount(mesh, true);
            indiceCount += mesh.descriptor.i;
        }
        const geometryBuffer = new GeometryBuffer();
        geometryBuffer.maximizeLength(primitiveCount, indiceCount);
        const vdm = new VertexDataMerge('dummy', geometryBuffer, ret, {
            includeNormals: true,
            disableTransparencyAndMergeIdChecks: true,
            cullOnCameraMove: false,
            compactAfterMerge: true
        });
        return { usedLoaderElements, vdm };
    }
    possiblyVisible(visible, meshes) {
        if (visible === undefined) {
            return this.productMeshes.some((m) => m.visible);
        }
        meshes = meshes ?? this.productMeshes;
        const len = meshes.length;
        let isVisible = false;
        for (let i = 0; i < len; ++i) {
            isVisible = visible || isVisible;
            meshes[i].visible = visible;
        }
        return isVisible;
    }
    createBimIfcObject(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor) {
        const ifcClass = ModelProductClass.getOrAdd(this.ifcLoaderElement.index.classes[rawProduct.c ?? 0]);
        if (ifcClass === ModelProductClass.predefined.space) {
            return new BimIfcSpace(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor);
        }
        else if (ifcClass === ModelProductClass.predefined.buildingStorey) {
            return new BimIfcBuildingStorey(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor);
        }
        else if (ifcClass === ModelProductClass.predefined.door) {
            return new BimIfcDoor(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor);
        }
        else {
            return new BimIfcObject(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor);
        }
    }
}
export class BimIfcSpace extends BimIfcObject {
    constructor(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor) {
        super(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor);
    }
    get calculatedArea() {
        return this.rawProduct.pd['calculatedArea'];
    }
    get elevationWithFlooring() {
        return this.rawProduct.pd['elevationWithFlooring'];
    }
    get spaceType() {
        return this.rawProduct.pd['spacetype'];
    }
    get longname() {
        return this.rawProduct.pd['longname'];
    }
}
export class BimIfcBuildingStorey extends BimIfcObject {
    objectsWithGeometryCount = 0;
    constructor(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor) {
        super(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor);
    }
    get elevation() {
        return this.rawProduct.pd['elevation'];
    }
    get totalHeight() {
        return this.rawProduct.pd['totalHeight'];
    }
    static is(o) {
        return o.class === ModelProductClass.predefined.buildingStorey;
    }
}
export class BimIfcDoor extends BimIfcObject {
    constructor(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor) {
        super(entityLabelInIfc, ifcLoaderElement, rawProduct, parents, enclosingFloor);
    }
    get operationType() {
        return this.rawProduct.pd['operationType'];
    }
}
//# sourceMappingURL=bim-ifc-object.js.map