import { ShaderMaterial, Mesh, Effect, VertexData, Vector3, Scalar } from '../loader/babylonjs-import';
import { Icon } from './Icon';
import { IconSizeCalculator } from './IconSizeCalculator';
import { StopWatch } from '../loader/stopwatch';
import { ChangeRecorder } from '../loader/change-recorder';
import { VisibleSelectableType } from '../loader/Selectables';
import { BimIfcClass } from '../loader/bim-ifc-class';
/**
 * Determine the operations {@link IconHandler} has to perform before rendering.
 * */
var BeforeRenderOptions;
(function (BeforeRenderOptions) {
    BeforeRenderOptions[BeforeRenderOptions["None"] = 0] = "None";
    BeforeRenderOptions[BeforeRenderOptions["RepopulateVertexBuffer"] = 1] = "RepopulateVertexBuffer";
    BeforeRenderOptions[BeforeRenderOptions["RecalculateVertexBufferOrder"] = 2] = "RecalculateVertexBufferOrder";
})(BeforeRenderOptions || (BeforeRenderOptions = {}));
/**
 * Icon Handler is a class for handling a set of Icon:s.
 * Make a instance and setIconAtlas with a texture.
 * Then addIcons to provide a array of Icon[].
 * Each Icon in the handler can be updated using forEach().
 * Followed by update().
 */
export class IconHandler {
    api;
    static _iconHandlerNextId = 0;
    static _defaultAlphaSortInterval = 500;
    static _defaultIconMaxSize = 16;
    _icons = new Set();
    _iconsById = new Map();
    _iconBaseSize = new IconSizeCalculator();
    _material;
    _vertexData = new VertexData();
    _pointMesh;
    _textureHasAlpha = false;
    _numberOfIconsInARow = 16;
    _numberOfIconsInStyle = 1;
    _pixelsPerIcon = 32;
    _iconMaxSize = IconHandler._defaultIconMaxSize;
    _beforeRenderObserver;
    _stopWatch = {
        occlusionCull: new StopWatch(),
        alphaSort: new StopWatch()
    };
    _beforeRenderOptions = BeforeRenderOptions.None;
    /**
     * Dirty icons
     * @hidden
     * @internal
     */
    _dirtyIcons = new Set();
    /** Unique id of {@link IconHandler}. */
    id = IconHandler._iconHandlerNextId++;
    /**
     * Defines how often the {@link Icon}s in the {@link IconHandler} are
     * occlusion culled in milliseconds.
     * - If 0 then occlusion culling runs every frame. Very expensive. Avoid
     * - If > 0 then occlusion culling runs only when the interval is exceeded.
     * - If < 0 occlusion culling is disabled.
     * - Defaults to -1
     */
    occlusionCullingInterval = -1;
    /**
     * Defines how often the {@link Icon}s in the {@link IconHandler} are
     * alpha sorted (back to front) relative the camera if icons are transparent.
     * - If 0 then alpha sort  runs every frame. Very expensive. Avoid
     * - If > 0 then alpha sort culling runs only when the interval is exceeded.
     * - If < 0 alpha sort is disabled.
     * - Defaults to 500 ms.
     */
    alphaSortInterval = IconHandler._defaultAlphaSortInterval;
    /**
     * A Icon Handler holds and handle interaction of icons and it´s gpu representation.
     * @param api The API is required for accessing current scene.
     * @param options Here you can provide a everything you need to set the icon texture atlas.
     * @example
     * ```typescript
     * // Create an IconHandler instance.
     * const iconHandler = new IconHandler(api);
     * // Add a icon atlas to use for all icons. Provide the babylonTextre, the total icon count, the amount of icons in a group and if the texture use the alpha channel.
     * iconHandler.setIconAtlas({
     *     texture: (await getTexture('./icon.png')),
     *     totalNumberOfIconsAndStyles: 100,
     *     numberOfIconsInAStyle: 1,
     *     hasAlpha: true
     * });
     * // Add a icon to be renderd.
     * iconHandler.attach(new Icon(
     *    "someId",
     *    Math.floor(Math.random() * 100), // Use a random icon form the 10X10 icon atlas.
     *    0, // Use a style index, in this case there is no groups of icons in the texture atlas, so we use the first icon.
     *    1, // Size
     *    new Vector3(0, 0, 0), // Add a position for the icon. (at origin in this case).
     *    new Color4(Math.random(), Math.random(), Math.random(), 1) // Add a random color to the icon, and the max size to the icon. (1).
     * ));
     * ```
     */
    constructor(api, options) {
        this.api = api;
        IconHandler.addShaders();
        // TODO: Replace with a single material for all IconHandlers. We can use getEffect instead.
        this._material = new ShaderMaterial(`iconShader_${this.id}`, this.api.viewer.scene, {
            vertex: 'icon',
            fragment: 'icon'
        }, {
            attributes: ['position', 'color', 'normal'],
            uniforms: [
                'worldViewProjection',
                'u_atlasSizePow',
                'u_pixelsPerIcon',
                'u_iconsInAtlasRow',
                'u_baseSize',
                'u_useGpuColor'
            ],
            samplers: ['textureSampler']
        });
        this._material.pointsCloud = true;
        this._material.metadata = { handlesGpuPicking: true };
        this._pointMesh = new Mesh(`iconMesh_${this.id}`, this.api.viewer.scene);
        this._pointMesh.setEnabled(false);
        this._pointMesh.material = this._material;
        this._vertexData = new VertexData();
        if (options) {
            // If icon atlas information is added on instancing of this class, set it up!
            this.setIconAtlas(options);
        }
        const targetSize = { width: 0, height: 0 };
        this._beforeRenderObserver = this.api.viewer.scene.onBeforeRenderObservable.add(() => {
            if (!this._stopWatch.alphaSort.isRunning)
                this._stopWatch.alphaSort.resetAndStart();
            if (!this._stopWatch.occlusionCull.isRunning)
                this._stopWatch.occlusionCull.resetAndStart();
            this.setOrResetRelativeIconSize(this._viewCamera, this.api.viewer.engine.twinfinity.getRenderSizeToRef(targetSize));
            this.applyPendingOperationsBeforeRender();
        });
    }
    /**
     * Sets the render group id 0-4 for all icons.
     * Use this with a larger value to render it on top of other geometry in the engine.
     */
    set renderGroup(i) {
        // clamp number between 0-4
        this._pointMesh.renderingGroupId = Scalar.Clamp(i, 0, 4);
    }
    /** Render group icons are rendered in.*/
    get renderGroup() {
        return this._pointMesh.renderingGroupId;
    }
    /** Number if icons. */
    get iconCount() {
        return this._icons.size;
    }
    /** Icons currently attached to {@link IconHandler}. */
    icons() {
        return this._icons.keys();
    }
    /***
     * Get Icon by id.
     * @param id Id of icon to get.
     * @return {@link Icon} or `undefined` if no {@link Icon} was found.
     */
    getIconById(id) {
        return this._iconsById.get(id);
    }
    /**
     * Returns the internal mesh.
     * @hidden
     * @internal
     */
    get _mesh() {
        return this._pointMesh;
    }
    get _viewCamera() {
        return this.api.viewer.camera.activeCamera;
    }
    /**
     * This function adds a texture and metadata about it to the icon handler.
     * Changes are automatically reflected in rendering.
     * @param o - Option in the form of IconAtlasOptions.
     */
    setIconAtlas(o) {
        const { iconMaxSize, hasAlpha, totalNumberOfIconsAndStyles, numberOfIconsInAStyle, iconAtlasTexture } = o;
        if (o.hasAlpha) {
            this.alphaSortInterval = o.alphaSortIntervalInMs ?? IconHandler._defaultAlphaSortInterval;
        }
        this.occlusionCullingInterval = o.occlusionCullingIntervalMs ?? -1;
        this._iconMaxSize = iconMaxSize ?? IconHandler._defaultIconMaxSize;
        this._textureHasAlpha = hasAlpha;
        this._numberOfIconsInARow = Math.sqrt(totalNumberOfIconsAndStyles);
        this._numberOfIconsInStyle = numberOfIconsInAStyle;
        const iconAtlasWidth = iconAtlasTexture.getBaseSize().width;
        this._pixelsPerIcon = Math.floor(iconAtlasWidth / Math.sqrt(totalNumberOfIconsAndStyles));
        iconAtlasTexture.hasAlpha = this._textureHasAlpha;
        this._material.setTexture('textureSampler', iconAtlasTexture);
        if (this._textureHasAlpha) {
            this._material.alpha = 0.9999;
        }
        this._material.setFloat('u_atlasSizePow', iconAtlasWidth);
        this._material.setFloat('u_pixelsPerIcon', this._pixelsPerIcon);
        this._material.setFloat('u_iconsInAtlasRow', this._numberOfIconsInARow);
        this._material.setInt('u_useGpuColor', 0);
        // If we have icons attached and change options we have to recalculate and possibly
        // resort all icons.
        if (this._icons.size > 0) {
            this._beforeRenderOptions |= BeforeRenderOptions.RepopulateVertexBuffer;
            if (this._textureHasAlpha) {
                this._beforeRenderOptions |= BeforeRenderOptions.RecalculateVertexBufferOrder;
            }
        }
    }
    /**
     * This function insert icons to the handler.
     * @param icons A array of icons or a icon to render in scene.
     */
    attach(icons) {
        // NOTE do not add a forcerender or similiar in here. It will be detrimental to performance
        // as applyPendingOperationsBeforeRender() will run for every batch of icon that is added.
        // if only one icon, convert it to array
        if (icons instanceof Icon) {
            icons = [icons];
        }
        this.throwIfIconAttachedToOtherIconHandler(icons);
        for (const icon of icons) {
            if (!icon._internal) {
                icon._internal = {
                    iconHandler: this,
                    // When we add an icon the render order is always the last icon.
                    // this will be recalculated if we have transparent icons later on.
                    vertexBufferPosition: this._icons.size,
                    distanceToCameraSquared: 0,
                    isOccluded: false,
                    textureObjectId: 0
                };
                this.api.selectables.attach(icon);
                icon._internal.textureObjectId = this.api.selectables.idOf(icon);
                // If we have no icons then we use inital icon as mesh position. Icon
                // positions in vertexbuffer will be relative this position. This is required to handle
                // cases where icons are located far from the origin (0,0,0). Otherwise we get really bad precision issues
                // where icons are "jumping" around on the screen when camera moves.
                if (this._icons.size === 0) {
                    this._mesh.position.copyFrom(icon.position);
                }
                this._icons.add(icon);
                this._iconsById.set(icon.id, icon);
                // If icons have no alpha then we do not need to bother resorting the vertex buffer order
                // if they are transparent we have to resort though
                this._beforeRenderOptions |= BeforeRenderOptions.RepopulateVertexBuffer;
                if (this._textureHasAlpha) {
                    this._beforeRenderOptions |= BeforeRenderOptions.RecalculateVertexBufferOrder;
                }
            }
        }
    }
    /**
     * This function detach icons from the handler and remove it from picking if it´s clickable.
     * @param icons A array of icons or a icon to remove from scene.
     */
    detach(icons) {
        // NOTE do not add a forcerender or similiar in here. It will be detrimental to performance
        // as applyPendingOperationsBeforeRender() will run for every batch of icon that is detached.
        // if only one icon, convert it to array
        if (icons instanceof Icon) {
            icons = [icons];
        }
        this.throwIfIconAttachedToOtherIconHandler(icons);
        for (const icon of icons) {
            if (this._icons.delete(icon)) {
                this.api.selectables.detach(icon);
                icon._internal = undefined;
                this._iconsById.delete(icon.id);
                this._dirtyIcons.delete(icon);
                this._beforeRenderOptions |=
                    BeforeRenderOptions.RecalculateVertexBufferOrder | BeforeRenderOptions.RepopulateVertexBuffer;
            }
        }
    }
    /**
     * Clears icon handler of all currently attached icons. Attached icons will
     * be detached.
     */
    clear() {
        this.detach([...this.icons()]);
        this._icons.clear();
        this._iconsById.clear();
        this._dirtyIcons.clear();
    }
    /**
     * Disposes icon handler. Call when it is no longer in use.
     * After call the icon handler cannot be used again.
     */
    dispose() {
        this.api.viewer.scene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
        this.clear();
        this._mesh.dispose();
        this._material.dispose();
    }
    /**
     * Iterate over each icon in the handler. See also  {@link icons}.
     * @param action This method will run on each icon in the handler. This is a easy way for updating its position or color.
     */
    forEach(action) {
        for (const icon of this.icons()) {
            action(icon);
        }
    }
    /**
     * Only for internal use. {@link GpuSelectables} calls this method
     * right before initiating a GPU picking render operation. The method
     * sets the {@link IconHandler} to GPU picking render mode and resets
     * it when {@link SelectablesRenderOperation.done} is called on the returned value
     * @hidden
     * @internal
     * @param camera GPU picking camera
     * @param targetSize GPU picking render target texture size
     * @returns {@link SelectablesRenderOperation}. {@link SelectablesRenderOperation.done} is called on it when
     * {@link GpuSelectables} has finished the GPU pick render pass.
     */
    _beginGpuPickingOperation(camera, targetSize) {
        this.setOrResetRelativeIconSize(camera, targetSize);
        // Applies any pending operations such as recalculating the vertex buffer positions, occlusion culling, resorting
        // transparent icons
        this.applyPendingOperationsBeforeRender();
        this._material.setInt('u_useGpuColor', 1);
        return {
            done: () => {
                this._material.setInt('u_useGpuColor', 0);
                this.setOrResetRelativeIconSize(this._viewCamera, this.api.viewer.engine.twinfinity.getRenderSizeToRef({ width: 0, height: 0 }));
            }
        };
    }
    applyPendingOperationsBeforeRender() {
        // Ensure that applyPendingIconChanges cannot be triggered when it is already running.
        // othwerwise it would be triggered when occlusion culling is enabled and
        // getvisiblesInSight() is called because of that. Because getVisiblesInsight will
        // trigger _beginGpuPickingOperation which in turn triggers applyPendingIconChanges again.
        const realBeforeRenderMethod = IconHandler.prototype.applyPendingOperationsBeforeRender;
        IconHandler.prototype.applyPendingOperationsBeforeRender = () => { };
        try {
            if (this._icons.size < 1) {
                // If there are no icons then the mesh cannot be used and must be disabled.
                // since there is nothing to render we can just return directly.
                this._pointMesh.setEnabled(false);
                this._beforeRenderOptions = BeforeRenderOptions.None;
                return;
            }
            this._pointMesh.setEnabled(true); // 1 or more icons. Enable the mesh
            // Store iterator for all dirty icons. That way we do not
            // pay for iteration until we really do it (lazy eval).
            let dirtyIcons = this._dirtyIcons.values();
            const reallocateVertexBuffer = !(this._vertexData.positions && this._vertexData.colors && this._vertexData.normals) ||
                this._vertexData.positions.length !== this._icons.size * 3;
            if (reallocateVertexBuffer) {
                this._vertexData.positions = new Float32Array(this._icons.size * 3);
                this._vertexData.colors = new Float32Array(this._icons.size * 4);
                this._vertexData.normals = new Float32Array(this._icons.size * 3);
                // vertex data is empty and has to be repopulated from scratch from all icons.
                dirtyIcons = this._icons.values();
                this._beforeRenderOptions |= BeforeRenderOptions.RepopulateVertexBuffer;
            }
            // We need to recalculate all icon positions in the vertex buffer if
            // 1. Renderoptions tells us to (most likely detach have been called)
            // 2. Icons are transparent and the alphaSort interval has expired
            let shallRecalculateIconPositionsInVertexBuffer = (this._beforeRenderOptions & BeforeRenderOptions.RecalculateVertexBufferOrder) > 0;
            if (shallRecalculateIconPositionsInVertexBuffer) {
                dirtyIcons = this._icons.values();
            }
            const shallTransparencySortIcons = this._textureHasAlpha &&
                this.alphaSortInterval >= 0 &&
                (this._stopWatch.alphaSort.elapsed > this.alphaSortInterval ||
                    shallRecalculateIconPositionsInVertexBuffer);
            if (shallTransparencySortIcons) {
                this.api.viewer.scene.onAfterRenderObservable.addOnce(() => {
                    this._stopWatch.alphaSort.resetAndStart();
                });
                dirtyIcons = this.sortIconsBackToFront(this.icons(), this._viewCamera.position).values();
                shallRecalculateIconPositionsInVertexBuffer = true;
            }
            // Set vertex data for all dirty icons
            const shallRepopulateVertexBuffer = (this._beforeRenderOptions & BeforeRenderOptions.RepopulateVertexBuffer) > 0;
            this.copyIconsToVertexBufferAndApplyToMesh(dirtyIcons, shallRecalculateIconPositionsInVertexBuffer);
            this._dirtyIcons.clear();
            this._beforeRenderOptions = BeforeRenderOptions.None;
            // Icons now have correct vertexData and can now be occlusion culled if occlusion culling is enabled
            // (which will also update vertex data for all icons that change culling state)
            const shallOcclusionCull = this.occlusionCullingInterval >= 0 &&
                (this._stopWatch.occlusionCull.elapsed > this.occlusionCullingInterval || shallRepopulateVertexBuffer);
            if (shallOcclusionCull) {
                this.api.viewer.scene.onAfterRenderObservable.addOnce(() => {
                    // Reset stopWatch after frame has been rendered so frame rendering time
                    // does not affect the occlusion cull time interval.
                    this._stopWatch.occlusionCull.resetAndStart();
                });
                this.occlusionCullIcons({
                    camera: this._viewCamera,
                    isDistanceCullingEnabled: true
                });
            }
        }
        finally {
            IconHandler.prototype.applyPendingOperationsBeforeRender = realBeforeRenderMethod;
        }
    }
    copyIconsToVertexBufferAndApplyToMesh(icons, shallRecalculateIconPositionsInVertexBuffer) {
        if (!this._vertexData.positions)
            throw new Error('No vertex data positions!');
        if (!this._vertexData.colors)
            throw new Error('No vertex data colors!');
        if (!this._vertexData.normals)
            throw new Error('No vertex data normals!');
        let updatedIconCount = 0;
        for (const icon of icons) {
            if (shallRecalculateIconPositionsInVertexBuffer && icon._internal) {
                icon._internal.vertexBufferPosition = updatedIconCount;
            }
            this.copyToPositions(icon, this._vertexData.positions);
            this.copyToColors(icon, this._vertexData.colors);
            this.copyToNormals(icon, this._vertexData.normals);
            updatedIconCount++;
        }
        if (updatedIconCount > 0) {
            this._vertexData.applyToMesh(this._mesh, true);
        }
        return updatedIconCount;
    }
    /**
     * Get all icons that are not occluded if seeen from the camera. Icons cannot be occluded by windows.
     * Warning. This is a very expensive operation. Do not call on every frame if scene is complex or there
     * is a lot of icons.
     */
    occlusionCullIcons(o) {
        if (!this._vertexData.normals)
            throw new Error('Error no normals');
        if (!this._vertexData.colors)
            throw new Error('Error no colors.');
        const { normals, colors } = this._vertexData;
        let wasIfcWindowVisibilityChanged = false;
        let iconStateChangedCount = 0;
        const iconsInSight = new Set();
        const previouslyOccludedIcons = new Set();
        const changeRecorder = new ChangeRecorder();
        try {
            // turn on all icons and render in same render group as most of the geometry
            // icons will therefore be occluded if there is geometry in front of it.
            changeRecorder.set(this._mesh, 'renderingGroupId', 0);
            // Ensure all icons are marked as pickable and not occluded so we can recalculate the
            // occusion.
            for (const icon of this._icons) {
                if (this.ensureIconIsPickableAndNotOccluded(icon, previouslyOccludedIcons, changeRecorder, colors, normals)) {
                    iconStateChangedCount++;
                }
            }
            // get all (renderable) IFC windows. Icons should not be blocked by windows
            // NOTE We could in the future exlude all objects that are transparent instead
            const ifcWindowClass = BimIfcClass.getOrAdd('IfcWindow');
            const allIfcWindowsWithGeometry = this.api.ifc.addOrGetIfcProductCache('allIfcWindows', (o) => o.class === ifcWindowClass && o.hasGeometry);
            // Make all visible windows meshes invisible (don't touch anything that is already invisible)
            for (const ifcWindow of allIfcWindowsWithGeometry) {
                for (const ifcMesh of ifcWindow.productMeshes) {
                    wasIfcWindowVisibilityChanged =
                        this.ensureWindowIsInvisible(changeRecorder, ifcMesh) || wasIfcWindowVisibilityChanged;
                }
            }
            // Prepare rendering of changed icons and ifc products (windows)
            if (iconStateChangedCount > 0) {
                this._vertexData.applyToMesh(this._mesh, true);
            }
            // get all visible objects from camera view
            const visiblesInsight = this.api.selectables.getVisiblesInSight(o);
            for (const visibleSelectable of visiblesInsight) {
                if (visibleSelectable.type === VisibleSelectableType.Icon) {
                    iconsInSight.add(visibleSelectable.icon);
                }
            }
        }
        finally {
            // Order is important here. Let change recorder restore all properties on all ifcWindows and ifc products
            // first.
            // before calling this._vertexData.applyToMesh again to restore previous state.
            changeRecorder.restoreAll();
        }
        // Calculate the new set of icons that are currently occlusion culled.
        let occludedStateChangeCount = 0;
        for (const icon of this._icons) {
            // Mark all icons that really changed occlusion status as dirty
            // that way they will be updated in vertex data before they are rendered next time
            if (this.updateIsOccludedState(iconsInSight, icon, previouslyOccludedIcons, colors, normals)) {
                occludedStateChangeCount++;
            }
        }
        // Apply vertex data if we either need to restore icons to state before occlusionCullIcons was called
        // or if we actually had Icons that changes their isOccluded state.
        if (iconStateChangedCount > 0 || occludedStateChangeCount > 0) {
            this._vertexData.applyToMesh(this._mesh, true);
        }
    }
    updateIsOccludedState(iconsInSight, icon, previouslyOccludedIcons, colors, normals) {
        if (!icon._internal)
            throw new Error('icon._internal not set. Icon not properly attached to IconHandler.');
        const newIsOccluded = !iconsInSight.has(icon);
        const prevIsOccluded = previouslyOccludedIcons.has(icon);
        if (prevIsOccluded === newIsOccluded) {
            return false;
        }
        icon._internal.isOccluded = newIsOccluded;
        this.copyToColors(icon, colors);
        this.copyToNormals(icon, normals);
        return true;
    }
    ensureWindowIsInvisible(changeRecorder, ifcWindowMesh) {
        // Hide all visible gpu pickable windows
        if (ifcWindowMesh.isOnGpu && ifcWindowMesh.visible) {
            changeRecorder.set(ifcWindowMesh, 'visible', false);
            return true;
        }
        return false;
    }
    ensureIconIsPickableAndNotOccluded(icon, previouslyOccludedIcons, changeRecorder, colors, normals) {
        if (!icon._internal)
            throw new Error('icon._internal not set. Icon not properly attached to IconHandler.');
        let iconWithChangedState = undefined;
        if (icon.isOccluded) {
            previouslyOccludedIcons.add(icon);
            // No icon is counted as occluded any longer as this is what we are recalculating.
            changeRecorder.set(icon._internal, 'isOccluded', false);
            iconWithChangedState = icon;
        }
        if (!icon.isPickable) {
            // Force rendering of icon with GPU picking on so getVisiblesInSight() can detect it
            changeRecorder.set(icon, 'isPickable', true);
            iconWithChangedState = icon;
        }
        // Icon changed either _isOccluded or isPickable therefore vertexdata needs
        // to be updated.
        if (iconWithChangedState) {
            this.copyToColors(icon, colors);
            this.copyToNormals(icon, normals);
            // Restore icon pickability back to invisibility. Runs after isOccluded and isPickable
            // has been restored by changeRecorder
            changeRecorder.onRestore(iconWithChangedState, (icon) => {
                // Reset rendering of icon with GPU picking (it was not on to begin with)
                this.copyToColors(icon, colors);
                this.copyToNormals(icon, normals);
            });
        }
        return iconWithChangedState !== undefined;
    }
    copyToPositions(icon, positions) {
        const indexP = icon._internal.vertexBufferPosition * 3;
        positions[indexP] = icon.position.x - this._mesh.position.x;
        positions[indexP + 1] = icon.position.y - this._mesh.position.y;
        positions[indexP + 2] = icon.position.z - this._mesh.position.z;
    }
    copyToColors(icon, colors) {
        const indexC = icon._internal.vertexBufferPosition * 4;
        colors[indexC] = icon.color.r;
        colors[indexC + 1] = icon.color.g;
        colors[indexC + 2] = icon.color.b;
        const shallRenderIcon = icon.visible && icon.size > 0 && !icon.isOccluded;
        if (shallRenderIcon) {
            colors[indexC + 3] = this._textureHasAlpha ? icon.color.a : Math.sign(icon.color.a);
        }
        else {
            colors[indexC + 3] = 0;
        }
    }
    copyToNormals(icon, normals) {
        const indexT = icon._internal.vertexBufferPosition * 3;
        // Icon has to be visible, have a size > 0 and not be occluded in order to render
        // not rendering is the same thing as setting alpha 0.
        // The alpha component represents the size of the icon
        const shallRenderIcon = icon.visible && icon.size > 0 && !icon.isOccluded;
        const shallGpuRenderIcon = shallRenderIcon && icon.isPickable;
        normals[indexT] = icon.iconId * this._numberOfIconsInStyle + icon.styleId;
        // normal.y is GpuPick id. Only valid if we actually render the icon
        normals[indexT + 1] = shallGpuRenderIcon ? icon._internal.textureObjectId : 0.0; // index converted to pixel color in shader
        // normal.z stores the size of the icon. Not valid if we dont render the icon.
        normals[indexT + 2] = shallRenderIcon ? icon.size : 0.0;
    }
    /**
     * Sorts and update icons based on icon distance to position.
     * Sort order is automatically reflected in rendering.
     * @param position position to measure icon distance to.
     */
    sortIconsBackToFront(icons, position) {
        const arr = [...icons];
        // We shall sort icons back to front by distance to position. To optimize the sort operation we first iterate
        // the array to caculate the distance squared for each icon to the camera. If not done it must instead be done
        // multiple times for each icon in in arr.sort() instead. This gives a x3 speedup.
        // TODO We could also detect whether we really need to perform a sort at all here.
        // if icons already are in back to front order then no sorting is required
        for (const icon of arr) {
            icon._internal.distanceToCameraSquared = Vector3.DistanceSquared(icon.position, position);
        }
        const backToFront = (a, b) => {
            return b._internal.distanceToCameraSquared - a._internal.distanceToCameraSquared;
        };
        return arr.sort(backToFront);
    }
    /**
     * Sets relative icon size, based on camera and render target texture size.
     * If no parameters are provided as arguments, the icon size will be reset based on default Pivot Target Camera.
     * @param camera Camera to use for compute relative icon size. If not provided, use twinfinity default Pivot Target Camera as view camera.
     * @param targetTextureWidth Width of target texture to be render. If not provided, do not calculate relative icon size to target texture.
     * @param targetTextureHeight Height of target texture to be render. If not provided, do not calculate relative icon size to target texture.
     */
    setOrResetRelativeIconSize(camera, targetSize) {
        const iconSize = this._iconBaseSize.get(this._iconMaxSize, camera, targetSize);
        this._material.setFloat('u_baseSize', iconSize);
    }
    throwIfIconAttachedToOtherIconHandler(icons) {
        for (const icon of icons) {
            if (icon._internal && icon._internal.iconHandler !== this) {
                throw new Error(`Icon ${icon.id} is not attached to this IconHandler. Call detach on IconHandler ${icon._internal.iconHandler.id}.`);
            }
        }
    }
    // Adds the custom icon shader to the shader store.
    static addShaders() {
        if (Effect.ShadersStore['iconVertexShader']) {
            return;
        }
        Effect.ShadersStore['iconVertexShader'] = `
            #ifdef GL_ES
                precision highp float;
            #endif
            // Attributes
            attribute vec3 position;
            attribute vec4 color;
            attribute vec3 normal;
            // Uniforms
            uniform mat4 worldViewProjection;
            uniform float u_atlasSizePow;
            uniform float u_pixelsPerIcon;
            uniform float u_iconsInAtlasRow;
            uniform float u_baseSize;
            uniform float u_renderTargetHeightInPixels;
            uniform highp int u_useGpuColor;
            
            // Varying
            varying vec4 v_color;
            varying vec2 v_offset;
            
            const float leftShift8 = 256.0;
            const float leftShift16 = leftShift8 * leftShift8;
            const float rightShift16 = 1.0 / leftShift16;
            const float rightShift8 = 1.0 / leftShift8;
            vec3 unpackColor(float f){
                vec3 color;
                color.b = floor(f * rightShift16);
                float k = f - color.b * leftShift16;
                color.g = floor(k * rightShift8);
                color.r = floor(k - color.g * leftShift8);
                return color / 255.0;
            }
            
            void main(void) {
                float scale = u_pixelsPerIcon / u_atlasSizePow;
                float row = floor(normal.x / u_iconsInAtlasRow);
                float u = (normal.x - (u_iconsInAtlasRow * row)) * scale;
                float v = row * scale;
                float iconSize = u_baseSize * normal.z;
                v_offset = vec2(u,v);
                if (u_useGpuColor == 1) {
                    v_color = vec4(unpackColor(normal.y), sign(normal.y) * sign(color.a));
                } else{
                    v_color = color;
                }
                
                gl_Position = worldViewProjection * vec4(position, 1.0);
                gl_PointSize = iconSize /  gl_Position.w;
            } 
        `;
        Effect.ShadersStore['iconFragmentShader'] = `
            uniform float u_atlasSizePow;
            uniform float u_pixelsPerIcon;
            uniform highp int u_useGpuColor;
            varying vec2 v_offset;
            varying vec4 v_color;
            uniform sampler2D textureSampler;
            void main(void) {
                float scale = u_pixelsPerIcon / u_atlasSizePow;
                vec4 texture = texture2D(textureSampler, gl_PointCoord * vec2(scale,scale) + v_offset); 
                float textureAlpha = texture.a;
                if (u_useGpuColor == 1){
                    texture.r = 1.0;
                    texture.g = 1.0;
                    texture.b = 1.0;
                    textureAlpha = sign(textureAlpha);
                }
                if(sign(v_color.a) * sign(textureAlpha) < 0.001){
                    discard;
                }
                gl_FragColor = vec4(vec3(texture) * vec3(v_color), clamp( textureAlpha * sign(v_color.a),0.0,1.0)); // sign returns 1.0 if alpha is not 0.0
            }
        `;
    }
}
//# sourceMappingURL=IconHandler.js.map