var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { COORDINATE_SYSTEM, Layer, picking, project32 } from "@deck.gl/core";
import { GL } from "@luma.gl/constants";
import { Geometry, Model } from "@luma.gl/engine";
import { lighting } from "@luma.gl/shadertools";
import { phongMaterial } from "../shader_modules/phong-lighting/phong-material";
import { precisionForTests } from "../shader_modules/test-precision/precisionForTests";
import { createPropertyData } from "../utils/layerTools";
import { getImageData, } from "../utils/colormapTools";
import { decodeNormalizedValueWithNaNFromRGB, utilities, } from "../shader_modules";
import { isTypedArray, loadDataArray, toNormalizedColor, toTypedArray, } from "../../utils";
import fsShader from "./triangle.fs.glsl";
import vsShader from "./triangle.vs.glsl";
import fsTexShader from "./texTriangle.fs.glsl";
import vsTexShader from "./texTriangle.vs.glsl";
import fsLineShader from "./triangleLine.fs.glsl";
import vsLineShader from "./triangleLine.vs.glsl";
const defaultMaterial = {
    ambient: 0.35,
    diffuse: 0.9,
    shininess: 32,
    specularColor: [38, 38, 38],
};
const defaultColormapSetup = {
    valueRange: [0, 1],
    clampRange: undefined,
    clampColor: [0, 255, 0, 200], // green color for clamped values
    undefinedValue: Number.NaN,
    undefinedColor: [255, 0, 0, 200], // red color for undefined values
    smooth: true,
};
const defaultProps = {
    "@@type": "GpglValueMappedSurfaceLayer",
    name: "Value Mapped Surface Layer",
    id: "value-mapped-surface",
    data: ["dummy"],
    showMesh: false,
    colormap: {
        colormapName: "seismic",
    },
    colormapSetup: defaultColormapSetup,
    color: [100, 100, 255],
    coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
    material: true, // default material
    smoothShading: false,
    depthTest: true,
    ZIncreasingDownwards: true,
    // deck.gl default props
    visible: true,
    pickable: true,
};
/**
 * GpglValueMappedSurfaceLayer is a layer handling surfaces textured by a value map.
 * These values are converted to color using a colormap and associated setup.
 *
 * A primary use case is to display seismic data.
 * The surfaces are expected to be small compared to the ones handled by the triangles layer.
 */
export class GpglValueMappedSurfaceLayer extends Layer {
    setShaderModuleProps(args) {
        // If material is a boolean, convert it to the default material
        // We need to set a different default material than in the shader module
        if (typeof args["phongMaterial"] === "boolean" &&
            args["phongMaterial"] === true) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            args["phongMaterial"] = defaultMaterial;
        }
        super.setShaderModuleProps(Object.assign(Object.assign({}, args), { lighting: Object.assign(Object.assign({}, args["lighting"]), { enabled: this.props.material !== false }) }));
    }
    initializeState(context) {
        const gl = context.device;
        this._createModels(gl).then(([triangleModels, meshModels]) => {
            this.setState({
                triangleModels: triangleModels,
                meshModels: meshModels,
            });
        });
    }
    updateState(params) {
        const rebuild = params.props.valueMappedTriangles !==
            params.oldProps.valueMappedTriangles ||
            params.props.triangleMeshes !== params.oldProps.triangleMeshes ||
            params.props.showMesh !== params.oldProps.showMesh ||
            params.props.colormap !== params.oldProps.colormap;
        super.updateState(params);
        if (rebuild) {
            this.initializeState(params.context);
        }
    }
    _consolidateColormap(colormap) {
        var _a;
        if (colormap instanceof Uint8Array) {
            // If colormapProps is a Uint8Array, return it directly
            return colormap;
        }
        if (!colormap || "colormapName" in colormap) {
            return {
                colormapName: (_a = colormap === null || colormap === void 0 ? void 0 : colormap.colormapName) !== null && _a !== void 0 ? _a : "seismic",
                colorTables: this.context.userData
                    .colorTables,
            };
        }
        return colormap;
    }
    _createColormapTexture(device, colormap) {
        const textureProps = {
            sampler: {
                addressModeU: "clamp-to-edge",
                addressModeV: "clamp-to-edge",
                minFilter: "linear",
                magFilter: "linear",
            },
            width: 256,
            height: 1,
            format: "rgb8unorm-webgl",
        };
        return device.createTexture(Object.assign(Object.assign({}, textureProps), { data: getImageData(this._consolidateColormap(colormap)) }));
    }
    _createValuesTexture(device, valueMap) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c;
            if ((valueMap === null || valueMap === void 0 ? void 0 : valueMap.values) === undefined) {
                return undefined;
            }
            // fetch data
            const propertiesData = yield loadDataArray(valueMap.values, Float32Array);
            if (!propertiesData) {
                return undefined;
            }
            if (!((_a = this.props.colormapSetup) === null || _a === void 0 ? void 0 : _a.valueRange)) {
                let min = Infinity;
                let max = -Infinity;
                for (const x of propertiesData) {
                    min = x < min ? x : min;
                    max = x > max ? x : max;
                }
                this.setState(Object.assign(Object.assign({}, this.state), { valueRange: [min, max] }));
            }
            const smooth = (_c = (_b = this.props.colormapSetup) === null || _b === void 0 ? void 0 : _b.smooth) !== null && _c !== void 0 ? _c : defaultColormapSetup.smooth;
            const textureProps = {
                sampler: {
                    addressModeU: "clamp-to-edge",
                    addressModeV: "clamp-to-edge",
                    minFilter: smooth ? "linear" : "nearest",
                    magFilter: smooth ? "linear" : "nearest",
                },
                width: valueMap.width,
                height: valueMap.height,
                format: "r32float",
            };
            return device.createTexture(Object.assign(Object.assign({}, textureProps), { width: valueMap.width, height: valueMap.height, data: propertiesData }));
        });
    }
    _createModels(device) {
        const _super = Object.create(null, {
            getShaders: { get: () => super.getShaders }
        });
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c;
            const models = [];
            const meshModels = [];
            // Collect promises for all valueMappedTriangles
            const triangleModelPromises = (_b = (_a = this.props.valueMappedTriangles) === null || _a === void 0 ? void 0 : _a.map((texturedTrgs, i) => __awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c;
                const trglGeometry = {
                    topology: texturedTrgs.topology,
                    attributes: {
                        positions: {
                            value: yield loadDataArray(texturedTrgs.vertices, Float32Array),
                            size: 3,
                        },
                        normals: {
                            value: yield loadDataArray((_a = texturedTrgs.normals) !== null && _a !== void 0 ? _a : [], Float32Array),
                            size: 3,
                        },
                        texCoords: {
                            value: yield loadDataArray((_b = texturedTrgs.texCoords) !== null && _b !== void 0 ? _b : [], Float32Array),
                            size: 2,
                        },
                    },
                    vertexCount: texturedTrgs.vertexIndices.size,
                    indices: {
                        value: yield loadDataArray(texturedTrgs.vertexIndices.value, Uint32Array),
                        size: 1,
                    },
                };
                const colormap = texturedTrgs.valueMap
                    ? this._createColormapTexture(device, this.props.colormap)
                    : undefined;
                const floatValues = texturedTrgs.valueMap
                    ? yield this._createValuesTexture(device, texturedTrgs.valueMap)
                    : undefined;
                const bindings = colormap && floatValues
                    ? { colormap, valueTexture: floatValues }
                    : undefined;
                const fs = bindings ? fsTexShader : fsShader;
                const vs = bindings ? vsTexShader : vsShader;
                const uniforms = bindings
                    ? texTrianglesUniforms
                    : trianglesUniforms;
                const model = new Model(device, Object.assign(Object.assign({ id: `${this.props.id}-trgl-${i}` }, _super.getShaders.call(this, {
                    vs: vs,
                    fs: fs,
                    modules: [
                        project32,
                        picking,
                        lighting,
                        phongMaterial,
                        uniforms,
                        precisionForTests,
                        utilities,
                    ],
                })), { bufferLayout: this.getAttributeManager().getBufferLayouts(), geometry: new Geometry(trglGeometry), bindings: bindings, isInstanced: false }));
                // default mesh model if none if provided
                let meshModel = undefined;
                if (!((_c = this.props.triangleMeshes) === null || _c === void 0 ? void 0 : _c.length) && this.props.showMesh) {
                    const indices = computeMeshIndices(texturedTrgs.vertexIndices.value, texturedTrgs.topology);
                    const meshGeometry = {
                        topology: meshTopology(texturedTrgs.topology),
                        attributes: {
                            positions: {
                                value: toTypedArray(texturedTrgs.vertices, Float32Array),
                                size: 3,
                            },
                        },
                        vertexCount: indices.length,
                        indices: {
                            value: toTypedArray(indices, Uint32Array),
                            size: 1,
                        },
                    };
                    meshModel = new Model(device, Object.assign(Object.assign({ id: `${this.props.id}-mesh-line-${i}` }, _super.getShaders.call(this, {
                        vs: vsLineShader,
                        fs: fsLineShader,
                        modules: [
                            project32,
                            picking,
                            triangleMeshUniforms,
                            precisionForTests,
                        ],
                    })), { bufferLayout: this.getAttributeManager().getBufferLayouts(), geometry: new Geometry(meshGeometry), isInstanced: false }));
                }
                return { model, meshModel };
            }))) !== null && _b !== void 0 ? _b : [];
            // Await all promises and push to models/meshModels arrays
            const triangleResults = yield Promise.all(triangleModelPromises);
            triangleResults.forEach(({ model, meshModel }) => {
                models.push(model);
                if (meshModel) {
                    meshModels.push(meshModel);
                }
            });
            (_c = this.props.triangleMeshes) === null || _c === void 0 ? void 0 : _c.forEach((mesh, i) => {
                var _a, _b;
                const meshGeometry = {
                    topology: mesh.topology,
                    attributes: {
                        positions: {
                            value: toTypedArray((_a = mesh.vertices) !== null && _a !== void 0 ? _a : (_b = this.props.valueMappedTriangles[i]) === null || _b === void 0 ? void 0 : _b.vertices, Float32Array),
                            size: 3,
                        },
                    },
                    vertexCount: mesh.vertexIndices.size,
                    indices: {
                        value: toTypedArray(mesh.vertexIndices.value, Uint32Array),
                        size: 1,
                    },
                };
                meshModels.push(new Model(device, Object.assign(Object.assign({ id: `${this.props.id}-mesh-line-${i}` }, _super.getShaders.call(this, {
                    vs: vsLineShader,
                    fs: fsLineShader,
                    modules: [
                        project32,
                        picking,
                        triangleMeshUniforms,
                        precisionForTests,
                    ],
                })), { bufferLayout: this.getAttributeManager().getBufferLayouts(), geometry: new Geometry(meshGeometry), isInstanced: false })));
            });
            return [models, meshModels];
        });
    }
    getModels() {
        var _a, _b;
        const triangleModels = ((_a = this.state["triangleModels"]) !== null && _a !== void 0 ? _a : []);
        const meshModels = ((_b = this.state["meshModels"]) !== null && _b !== void 0 ? _b : []);
        return [...triangleModels, ...meshModels];
    }
    _getClampColors(color) {
        if (isTypedArray(color)) {
            // If clampColor is a Uint8Array, return it directly
            if (color.length >= 8) {
                return [
                    toNormalizedColor(color.slice(0, 4)),
                    toNormalizedColor(color.slice(4, 8)),
                ];
            }
            else if (color.length === 6) {
                return [
                    toNormalizedColor(color.slice(0, 3)),
                    toNormalizedColor(color.slice(3, 6)),
                ];
            }
            const col = toNormalizedColor(color);
            return [col, col];
        }
        if (Array.isArray(color) && color.length === 2) {
            return [toNormalizedColor(color[0]), toNormalizedColor(color[1])];
        }
        const col = color ? toNormalizedColor(color) : undefined;
        return [col, col];
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    draw(args) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        if (!this.state["triangleModels"] && !this.state["meshModels"]) {
            return;
        }
        const triangleModels = this.state["triangleModels"];
        const meshModels = this.state["meshModels"];
        const { gl } = args.context;
        if (!this.props.depthTest) {
            gl.disable(GL.DEPTH_TEST);
        }
        gl.enable(GL.POLYGON_OFFSET_FILL);
        gl.polygonOffset(1, 1);
        //
        const [lowClampColors, highClampColor] = this._getClampColors((_b = (_a = this.props.colormapSetup) === null || _a === void 0 ? void 0 : _a.clampColor) !== null && _b !== void 0 ? _b : defaultColormapSetup.clampColor);
        const colormapRange = (_e = (_d = (_c = this.props.colormapSetup) === null || _c === void 0 ? void 0 : _c.valueRange) !== null && _d !== void 0 ? _d : this.state["valueRange"]) !== null && _e !== void 0 ? _e : defaultColormapSetup.valueRange;
        const clampRange = ((_f = this.props.colormapSetup) === null || _f === void 0 ? void 0 : _f.clampRange) === null
            ? null
            : ((_h = (_g = this.props.colormapSetup) === null || _g === void 0 ? void 0 : _g.clampRange) !== null && _h !== void 0 ? _h : colormapRange);
        // render all the triangle surfaces
        triangleModels === null || triangleModels === void 0 ? void 0 : triangleModels.forEach((model) => {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j;
            model.shaderInputs.setProps(Object.assign(Object.assign({}, args.uniforms), { triangles: {
                    colormapRange,
                    clampRange,
                    useClampColors: ((_a = this.props.colormapSetup) === null || _a === void 0 ? void 0 : _a.clampColor) !== null &&
                        ((_b = this.props.colormapSetup) === null || _b === void 0 ? void 0 : _b.clampRange) !== null,
                    lowClampColor: lowClampColors,
                    highClampColor: highClampColor,
                    undefinedValue: (_d = (_c = this.props.colormapSetup) === null || _c === void 0 ? void 0 : _c.undefinedValue) !== null && _d !== void 0 ? _d : defaultColormapSetup.undefinedValue,
                    undefinedColor: toNormalizedColor((_f = (_e = this.props.colormapSetup) === null || _e === void 0 ? void 0 : _e.undefinedColor) !== null && _f !== void 0 ? _f : defaultColormapSetup.undefinedColor),
                    // Normalize color to [0,1] range.
                    uColor: toNormalizedColor((_g = this.props.color) !== null && _g !== void 0 ? _g : defaultProps.color),
                    smoothShading: (_h = this.props.smoothShading) !== null && _h !== void 0 ? _h : defaultProps.smoothShading,
                    ZIncreasingDownwards: (_j = this.props.ZIncreasingDownwards) !== null && _j !== void 0 ? _j : defaultProps.ZIncreasingDownwards,
                } }));
            model.draw(args.context.renderPass);
        });
        gl.disable(GL.POLYGON_OFFSET_FILL);
        // render all the triangle meshes
        if (this.props.showMesh) {
            meshModels === null || meshModels === void 0 ? void 0 : meshModels.forEach((meshModel) => {
                var _a;
                meshModel.shaderInputs.setProps(Object.assign(Object.assign({}, args.uniforms), { triangleMesh: {
                        ZIncreasingDownwards: (_a = this.props.ZIncreasingDownwards) !== null && _a !== void 0 ? _a : defaultProps.ZIncreasingDownwards,
                    } }));
                meshModel.draw(args.context.renderPass);
            });
        }
        if (!this.props.depthTest) {
            gl.enable(GL.DEPTH_TEST);
        }
    }
    decodePickingColor() {
        return 0;
    }
    getPickingInfo({ info }) {
        var _a, _b, _c, _d;
        if (!info.color) {
            return info;
        }
        const layer_properties = [];
        const zScale = this.props.modelMatrix ? this.props.modelMatrix[10] : 1;
        if (typeof ((_a = info.coordinate) === null || _a === void 0 ? void 0 : _a[2]) !== "undefined") {
            const depth = (this.props.ZIncreasingDownwards
                ? -info.coordinate[2]
                : info.coordinate[2]) / Math.max(0.001, zScale);
            layer_properties.push(createPropertyData("Depth", depth));
        }
        // Note these colors are in the  0-255 range.
        const [r, g, b] = info.color;
        const normalizedValue = decodeNormalizedValueWithNaNFromRGB([r, g, b]);
        const valueRange = (_d = (_c = (_b = this.props.colormapSetup) === null || _b === void 0 ? void 0 : _b.valueRange) !== null && _c !== void 0 ? _c : this.state["valueRange"]) !== null && _d !== void 0 ? _d : defaultColormapSetup.valueRange;
        const value = valueRange[0] + normalizedValue * (valueRange[1] - valueRange[0]);
        layer_properties.push(createPropertyData("Property", value));
        return Object.assign(Object.assign({}, info), { properties: layer_properties });
    }
}
GpglValueMappedSurfaceLayer.layerName = "GpglValueMappedSurfaceLayer";
GpglValueMappedSurfaceLayer.defaultProps = defaultProps;
const triangleMeshUniformsBlock = `\
uniform triangleMeshUniforms {
    bool ZIncreasingDownwards;
} triangleMesh;
`;
// NOTE: this must exactly the same name than in the uniform block
const triangleMeshUniforms = {
    name: "triangleMesh",
    vs: triangleMeshUniformsBlock,
    fs: undefined,
    uniformTypes: {
        ZIncreasingDownwards: "f32",
    },
};
const triangleUniformsBlock = /*glsl*/ `\
uniform trianglesUniforms {
    vec4 uColor;
    bool smoothShading;
    bool ZIncreasingDownwards;
} triangles;
`;
// NOTE: this must exactly the same name than in the uniform block
const trianglesUniforms = {
    name: "triangles",
    vs: triangleUniformsBlock,
    fs: triangleUniformsBlock,
    uniformTypes: {
        uColor: "vec4<f32>",
        smoothShading: "u32",
        ZIncreasingDownwards: "u32",
    },
};
const texTriangleUniformsBlock = /*glsl*/ `\
uniform trianglesUniforms {
    vec2 colormapRange;
    vec2 clampRange;
    bool useClampColors;
    vec4 lowClampColor;
    vec4 highClampColor;
    float undefinedValue;
    vec4 undefinedColor;
    bool smoothShading;
    bool ZIncreasingDownwards;
} triangles;
`;
// NOTE: this must exactly the same name than in the uniform block
const texTrianglesUniforms = {
    name: "triangles",
    vs: texTriangleUniformsBlock,
    fs: texTriangleUniformsBlock,
    uniformTypes: {
        colormapRange: "vec2<f32>",
        clampRange: "vec2<f32>",
        useClampColors: "u32",
        lowClampColor: "vec4<f32>",
        highClampColor: "vec4<f32>",
        undefinedValue: "f32",
        undefinedColor: "vec4<f32>",
        smoothShading: "u32",
        ZIncreasingDownwards: "u32",
    },
};
// Helper functions
function meshTopology(topology) {
    switch (topology) {
        case "triangle-list":
            return "line-list";
        case "triangle-strip":
            return "line-strip";
        default:
            throw new Error(`Unknown topology: ${topology}`);
    }
}
function computeMeshIndices(trglIndices, topology) {
    if (topology === "triangle-list") {
        // non optimal, and some edges might be missing
        // non trivial to fix that :/
        return trglIndices;
    }
    if (trglIndices.length === 4) {
        // special case for a rectangle
        return [
            trglIndices[2],
            trglIndices[0],
            trglIndices[1],
            trglIndices[2],
            trglIndices[3],
            trglIndices[1],
        ];
    }
    // triangle strip.
    // missing edges must be added
    if (Array.isArray(trglIndices) && typeof trglIndices[0] === "number") {
        const indices1 = trglIndices
            .filter((_v, index) => index !== 0 && index % 2 === 0)
            .reverse();
        const indices2 = trglIndices.filter((_v, index) => index !== trglIndices.length - 1 && index % 2 === 1);
        return indices1.concat(trglIndices, indices2);
    }
    else {
        const idxArray = trglIndices;
        const indices1 = idxArray
            .filter((_v, index) => index !== 0 && index % 2 === 0)
            .reverse();
        const indices2 = idxArray.filter((_v, index) => index !== trglIndices.length - 1 && index % 2 === 1);
        const mergedIndices = new Uint32Array(idxArray.length + indices1.length + indices2.length);
        mergedIndices.set(indices1, 0);
        mergedIndices.set(idxArray, indices1.length);
        mergedIndices.set(indices2, indices1.length + idxArray.length);
        return mergedIndices;
    }
}
//# sourceMappingURL=gpglValueMappedSurfaceLayer.js.map