import { setMax, setMin } from '../math';
import { BoundingInfo, Vector3 } from './babylonjs-import';
import { BimIfcMesh } from './bim-ifc-mesh';
import { IfcMaterialRenderProperty } from './CustomBabylonMaterials/IfcMaterial';
import { Uint32Bits } from '../Uint32Bits';
import { ModelProductClass } from '@twinfinity/t8-client-model';
/**
 * Represents the mesh of an IFC product. An IFC product may
 * consist of `0..N` meshes. A door is often represented as two meshes: a
 * rectangle and a door handle.
 */
export class IfcProductMesh {
    ifcProduct;
    mi;
    r;
    si;
    wt;
    static _tmp = {
        vectorA: Vector3.Zero(),
        vectorB: Vector3.Zero(),
        vectorC: Vector3.Zero()
    };
    colorTexturePixelIndex = -1;
    /** Determine which babylonjs mesh this ifc product mesh shall belong to. */
    mergeId = '';
    /** @inheritDoc */
    cullingDistance = -1;
    /** @inheritDoc */
    edges;
    /** Center of the cube, this mesh will be a part of, in IFC space (not worldspace) */
    centerOfParentIfcCube;
    _renderPropertyInfo;
    _loaderElement;
    constructor(ifcProduct, mi, r, si, wt) {
        this.ifcProduct = ifcProduct;
        this.mi = mi;
        this.r = r;
        this.si = si;
        this.wt = wt;
        this._loaderElement = ifcProduct.ifcLoaderElement;
    }
    static from(o, dto) {
        // TODO: Fix better override of space colors. Currently we know that the last element of
        // o.ifcLoaderElement.styles is a specially created style that should be used for all
        // IFC spaces. The only reason is that requirements stated that they should be green, which they
        // are not always in the IFC files.
        const si = o.class === ModelProductClass.predefined.space ? o.ifcLoaderElement.styles.length - 1 : dto.si;
        return new IfcProductMesh(o, dto.mi ?? 0, dto.r ?? 0, si ?? 0, dto.wt ?? 0);
    }
    get vertexCount() {
        return this.descriptor.p;
    }
    get indexCount() {
        return this.descriptor.i;
    }
    getVertexData() {
        return Promise.resolve(this.vertexData);
    }
    async getEdges() {
        const cached = this.edges;
        if (cached)
            return cached;
        await this._loaderElement.loadGeometricFeatures();
        const resolved = this._loaderElement.geometricFeaturesRepository.resolveEdgesForMesh(this);
        return resolved ?? [];
    }
    get isOnGpu() {
        // Empty string mergeid means that mesh is not on gpu
        return !!this.mergeId;
    }
    get visible() {
        return this.renderProperties.has(IfcMaterialRenderProperty.visibility);
    }
    set visible(visible) {
        this.renderProperties.set(IfcMaterialRenderProperty.visibility, visible);
    }
    get outline() {
        return this.renderProperties.has(IfcMaterialRenderProperty.outline);
    }
    set outline(value) {
        this.renderProperties.set(IfcMaterialRenderProperty.outline, value);
    }
    get highlight() {
        return this.renderProperties.getNumber(IfcMaterialRenderProperty.highlight, 2);
    }
    set highlight(value) {
        this.renderProperties.setNumber(IfcMaterialRenderProperty.highlight, 2, value);
    }
    get ignoreClipPlanes() {
        return this.materials.renderPropertiesFor(this).has(IfcMaterialRenderProperty.ignoreClipPlanes);
    }
    set ignoreClipPlanes(value) {
        this.materials.renderPropertiesFor(this).set(IfcMaterialRenderProperty.ignoreClipPlanes, value);
    }
    get depthWriteIgnoresVisibility() {
        return this.renderProperties.has(IfcMaterialRenderProperty.depthWriteIgnoresVisibility);
    }
    set depthWriteIgnoresVisibility(value) {
        this.renderProperties.set(IfcMaterialRenderProperty.depthWriteIgnoresVisibility, value);
    }
    get ghostOutline() {
        return this.depthWriteIgnoresVisibility && !this.visible;
    }
    set ghostOutline(enabled) {
        // NOTE: I believe this is a bug. ghostOutline should only be a getter.
        // It is depthWriteIgnoresVisibility that controls if a non-visible mesh is rendered as a ghost outline or not.
        // We should probably remove ghostOutline altogether and rename depthWriteIgnoresVisibility to ghostOutline.
        // Perhaps call it ghost visibility or something?
        let renderProperty = this.renderProperties.value;
        renderProperty = Uint32Bits.set(renderProperty, IfcMaterialRenderProperty.depthWriteIgnoresVisibility, enabled);
        // this.renderProperties._onChanged is only executed once even if we set two properties.
        this.renderProperties.value = Uint32Bits.set(renderProperty, IfcMaterialRenderProperty.visibility, !enabled);
    }
    get descriptor() {
        return this.loaderElement.index.meshDescriptors[this.mi];
    }
    get style() {
        return this.loaderElement.styles[this.si];
    }
    get ifc() {
        return this.loaderElement.ifc;
    }
    get loaderElement() {
        return this._loaderElement;
    }
    get vertexData() {
        return this.loaderElement.getVertexData(this.descriptor);
    }
    get renderProperties() {
        return this.materials.renderPropertiesFor(this);
    }
    aabb(min, max, noCache = true) {
        const tmpTransform = this.loaderElement.transformsRepository.getTransform(this, noCache);
        const tmpBbox = this.descriptor.bbox;
        const tmpVector = IfcProductMesh._tmp.vectorA;
        tmpTransform.transformInPlaceVector3(tmpBbox[0], tmpBbox[1], tmpBbox[2], tmpVector);
        min.minimizeInPlace(tmpVector);
        max.maximizeInPlace(tmpVector);
        tmpTransform.transformInPlaceVector3(tmpBbox[3], tmpBbox[4], tmpBbox[5], tmpVector);
        min.minimizeInPlace(tmpVector);
        max.maximizeInPlace(tmpVector);
    }
    _clearMergeId() {
        if (!this.mergeId) {
            return false;
        }
        this.mergeId = '';
        this.cullingDistance = -1;
        this.centerOfParentIfcCube = undefined;
        this._renderPropertyInfo = undefined;
        return true;
    }
    // Divides the world into aabbs with a set side length. Basically, we subdivide IFC space into
    // aabbs (cubes) with equal length. We then look at each IFC mesh and assign it to one of the aabbs.
    // All meshes which end up in the aabb cube will be merged for performance reasons. We actually
    // have two types of aabbs: one for large objects and one for small objects. Aabbs containing
    // small objects are distance culled, i.e., they are not visible if the camera is further away than
    // N units. This can give a large performance boost (fps) since small objects can be very complex.
    // NOTE: Cube sizes should probably not be hardcoded if possible.
    // NOTE: Empirical studies show that we get good performance with around 2000 meshes.
    // We should attempt to mesh so we get a maximum of that. If we look at the total number of
    // triangles, we would get an indication of how many triangles we should attempt to put in each mesh.
    _setMergeId(mergeId) {
        this.mergeId = mergeId.id;
        this.cullingDistance = mergeId.cullingDistance;
        this.centerOfParentIfcCube = mergeId.cubeCellCenter;
        return true;
    }
    boundingInfo(bI, noCache = true) {
        const tmpMin = IfcProductMesh._tmp.vectorB;
        const tmpMax = IfcProductMesh._tmp.vectorC;
        if (bI === undefined) {
            this.aabb(setMax(tmpMin), setMin(tmpMax), noCache);
            return new BoundingInfo(tmpMin, tmpMax);
        }
        this.aabb(tmpMin.copyFrom(bI.minimum), tmpMax.copyFrom(bI.maximum), noCache);
        bI.reConstruct(tmpMin, tmpMax);
        return bI;
    }
    defaultColor() {
        return this.setColor(this.style.color);
    }
    transform(noCache = true) {
        return this.loaderElement.transformsRepository.getMatrix(this, noCache);
    }
    fastTransform(noCache = true) {
        return this.loaderElement.transformsRepository.getTransform(this, noCache);
    }
    mesh(noCache = true) {
        return new BimIfcMesh(this.vertexData, this.transform(noCache), 1.0 / this.loaderElement.index.modelFactors.oneMeter);
    }
    copyColorTo(dst, dstOffset = 0) {
        return this.materials.copyColorTo(this, dst, dstOffset);
    }
    setColor(color) {
        return this.materials.setColor(this, color);
    }
    getColorComponent(colorComponent) {
        return this.materials.getColorComponent(this, colorComponent);
    }
    get materials() {
        return this.loaderElement.materials;
    }
}
//# sourceMappingURL=IfcProductMesh.js.map