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 { project32 } from "@deck.gl/core";
import { BitmapLayer } from "@deck.gl/layers";
import * as png from "@vivaxy/png";
import { getModelMatrix } from "../utils/layerTools";
import { getImageData, } from "../utils/colormapTools";
import { decodeRGB } from "../utils/propertyMapTools";
import fsColormap from "./colormap.fs.glsl";
import { precisionForTests } from "../shader_modules/test-precision/precisionForTests";
const defaultProps = {
    "@@type": "ColormapLayer",
    name: "Property map",
    id: "colormap-layer",
    pickable: true,
    visible: true,
    valueRange: { type: "array", value: [0, 1] },
    colorMapRange: { type: "array" },
    valueDecoder: {
        rgbScaler: [1, 1, 1],
        // By default, scale the [0, 256*256*256-1] decoded values to [0, 1]
        floatScaler: 1.0 / (256.0 * 256.0 * 256.0 - 1.0),
        offset: 0,
        step: 0,
    },
    rotDeg: 0,
    colorMapName: "Rainbow",
    contours: false,
    hillshading: false,
    contourReferencePoint: 0,
    contourInterval: 50, // 50 meter by default
    heightMapUrl: "",
};
export default class ColormapLayer extends BitmapLayer {
    initializeState() {
        super.initializeState();
        const createPropertyTexture = () => __awaiter(this, void 0, void 0, function* () {
            const response = yield fetch(this.props.heightMapUrl);
            if (!response.ok) {
                console.error("Could not load ", this.props.heightMapUrl);
            }
            else {
                const blob = yield response.blob();
                const contentType = response.headers.get("content-type");
                const isPng = contentType === "image/png";
                if (isPng) {
                    const heightMapTexture = yield new Promise((resolve) => {
                        const fileReader = new FileReader();
                        fileReader.readAsArrayBuffer(blob);
                        fileReader.onload = () => {
                            const arrayBuffer = fileReader.result;
                            const imgData = png.decode(arrayBuffer);
                            const w = imgData.width;
                            const h = imgData.height;
                            const data = imgData.data; // array of int's
                            const n = data.length;
                            const buffer = new ArrayBuffer(n);
                            const view = new DataView(buffer);
                            for (let i = 0; i < n; i++) {
                                view.setUint8(i, data[i]);
                            }
                            const array = new Uint8Array(buffer);
                            const propertyTexture = this.context.device.createTexture({
                                sampler: {
                                    addressModeU: "clamp-to-edge",
                                    addressModeV: "clamp-to-edge",
                                    minFilter: "linear",
                                    magFilter: "linear",
                                },
                                width: w,
                                height: h,
                                format: "rgba8unorm",
                                data: array,
                            });
                            resolve(propertyTexture);
                        };
                    });
                    this.setState(Object.assign(Object.assign({}, this.state), { heightMapTexture }));
                }
            }
        });
        createPropertyTexture();
    }
    setShaderModuleProps(...props) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
        if (!this.isLoaded) {
            if (typeof this.props.reportBoundingBox !== "undefined") {
                const xMin = this.props.bounds[0];
                const yMin = this.props.bounds[1];
                const zMin = 1;
                const xMax = this.props.bounds[2];
                const yMax = this.props.bounds[3];
                const zMax = -1;
                this.props.reportBoundingBox({
                    layerBoundingBox: [xMin, yMin, zMin, xMax, yMax, zMax],
                });
            }
        }
        // Set property for modelMatrix.
        const m = getModelMatrix(this.props.rotDeg, this.props.bounds[0], // Rotate around upper left corner of bounds
        this.props.bounds[3]);
        for (const model of this.getModels()) {
            const isDefined = ((_b = (_a = props[0]) === null || _a === void 0 ? void 0 : _a.project) === null || _b === void 0 ? void 0 : _b.modelMatrix) !== undefined;
            if (isDefined) {
                props[0]["project"].modelMatrix =
                    m;
            }
            model.shaderInputs.setProps(...props);
        }
        // Properties for local shader module.
        const valueRangeMin = (_c = this.props.valueRange[0]) !== null && _c !== void 0 ? _c : 0.0;
        const valueRangeMax = (_d = this.props.valueRange[1]) !== null && _d !== void 0 ? _d : 1.0;
        // If specified, color map will extend from colormapRangeMin to colormapRangeMax.
        // Otherwise it will extend from valueRangeMin to valueRangeMax.
        const colormapRangeMin = (_f = (_e = this.props.colorMapRange) === null || _e === void 0 ? void 0 : _e[0]) !== null && _f !== void 0 ? _f : valueRangeMin;
        const colormapRangeMax = (_h = (_g = this.props.colorMapRange) === null || _g === void 0 ? void 0 : _g[1]) !== null && _h !== void 0 ? _h : valueRangeMax;
        const colormapTexture = this.context.device.createTexture({
            width: 256,
            height: 1,
            format: "rgb8unorm-webgl",
            data: getImageData((_j = this.props.colorMapFunction) !== null && _j !== void 0 ? _j : {
                colormapName: this.props.colorMapName,
                colorTables: this.context.userData
                    .colorTables,
            }),
        });
        // eslint-disable-next-line
        // @ts-ignore
        let heightMapTexture = this.state.heightMapTexture;
        const isHeightMapTextureDefined = typeof heightMapTexture !== "undefined";
        if (!isHeightMapTextureDefined) {
            // Create a dummy texture
            heightMapTexture = this.context.device.createTexture({
                sampler: {
                    addressModeU: "clamp-to-edge",
                    addressModeV: "clamp-to-edge",
                    minFilter: "linear",
                    magFilter: "linear",
                },
                width: 1,
                height: 1,
                format: "rgba8unorm",
                data: new Uint8Array([1, 1, 1, 1]),
            });
        }
        const heightValueRangeMin = (_l = (_k = this.props.heightValueRange) === null || _k === void 0 ? void 0 : _k[0]) !== null && _l !== void 0 ? _l : valueRangeMin;
        const heightValueRangeMax = (_o = (_m = this.props.heightValueRange) === null || _m === void 0 ? void 0 : _m[1]) !== null && _o !== void 0 ? _o : valueRangeMax;
        (_p = this.state.model) === null || _p === void 0 ? void 0 : _p.setBindings({ colormapTexture, heightMapTexture });
        const bitmapResolution = this.props.image
            ? [
                this.props.image.width,
                this.props.image.height,
            ]
            : [1, 1];
        const contours = this.props.contours;
        const hillshading = this.props.hillshading;
        const contourReferencePoint = this.props.contourReferencePoint;
        const contourInterval = this.props.contourInterval;
        const isClampColor = this.props.colorMapClampColor !== undefined &&
            this.props.colorMapClampColor !== true &&
            this.props.colorMapClampColor !== false;
        let colormapClampColor = isClampColor
            ? this.props.colorMapClampColor
            : [0, 0, 0];
        colormapClampColor = colormapClampColor.map((x) => (x !== null && x !== void 0 ? x : 0) / 255);
        const isColormapClampColorTransparent = this.props.colorMapClampColor === false;
        super.setShaderModuleProps({
            map: {
                valueRangeMin,
                valueRangeMax,
                colormapRangeMin,
                colormapRangeMax,
                contours,
                hillshading,
                bitmapResolution,
                contourReferencePoint,
                contourInterval,
                isClampColor,
                colormapClampColor,
                isColormapClampColorTransparent,
                isHeightMapTextureDefined,
                heightValueRangeMin,
                heightValueRangeMax,
                rgbScaler: this.props.valueDecoder.rgbScaler,
                floatScaler: this.props.valueDecoder.floatScaler,
                offset: this.props.valueDecoder.offset,
                step: this.props.valueDecoder.step,
            },
        });
    }
    getShaders() {
        const parentShaders = super.getShaders();
        // use object.assign to make sure we don't overwrite existing fields like `vs`, `modules`...
        return Object.assign({}, parentShaders, {
            fs: fsColormap,
            modules: [
                ...parentShaders.modules,
                project32,
                map2DUniforms,
                precisionForTests,
            ],
        });
    }
    getPickingInfo({ info, }) {
        if (!info.color) {
            return info;
        }
        const mergedDecoder = Object.assign(Object.assign({}, defaultProps.valueDecoder), this.props.valueDecoder);
        // The picked color is the one in raw image, not the one after colormapping.
        // We just need to decode that RGB color into a property float value.
        const val = decodeRGB(info.color, mergedDecoder, this.props.valueRange);
        if (info.coordinate) {
            info.coordinate[2] = NaN; // disable z value in 2D map;
        }
        return Object.assign(Object.assign({}, info), { 
            // Picking color doesn't represent object index in this layer.
            // For more details, see https://deck.gl/docs/developer-guide/custom-layers/picking
            index: 0, propertyValue: val });
    }
    getLegendData() {
        var _a, _b, _c, _d, _e, _f;
        const valueRangeMin = (_a = this.props.valueRange[0]) !== null && _a !== void 0 ? _a : 0.0;
        const valueRangeMax = (_b = this.props.valueRange[1]) !== null && _b !== void 0 ? _b : 1.0;
        // If specified color map will extend from colormapRangeMin to colormapRangeMax.
        // Otherwise it will extend from valueRangeMin to valueRangeMax.
        const min = (_d = (_c = this.props.colorMapRange) === null || _c === void 0 ? void 0 : _c[0]) !== null && _d !== void 0 ? _d : valueRangeMin;
        const max = (_f = (_e = this.props.colorMapRange) === null || _e === void 0 ? void 0 : _e[1]) !== null && _f !== void 0 ? _f : valueRangeMax;
        return {
            discrete: false,
            valueRange: [min, max],
            colorName: this.props.colorMapName,
            title: "PropertyMapLayer",
            colorMapFunction: this.props.colorMapFunction,
        };
    }
}
ColormapLayer.layerName = "ColormapLayer";
ColormapLayer.defaultProps = defaultProps;
// local shader module for the uniforms
const map2DUniformsBlock = /*glsl*/ `\
uniform mapUniforms {
    float valueRangeMin;
    float valueRangeMax;
    float colormapRangeMin;
    float colormapRangeMax;
    vec2 bitmapResolution;
    bool contours;
    bool hillshading;
    float contourReferencePoint;
    float contourInterval;
    bool isClampColor;
    vec3 colormapClampColor;
    bool isColormapClampColorTransparent;
    bool isHeightMapTextureDefined;
    float heightValueRangeMin;
    float heightValueRangeMax;
    vec3 rgbScaler;    // r, g and b multipliers
    float floatScaler; // value multiplier
    float offset;      // translation of the r, g, b sum
    float step;        // discretize the value in a number of
} map;

float decode_rgb2float(vec3 rgb) {
  rgb *= map.rgbScaler * vec3(16711680.0, 65280.0, 255.0); //255*256*256, 255*256, 255
  float value = (rgb.r + rgb.g + rgb.b + map.offset) * map.floatScaler;

  // Value must be in [0, 1] and step in (0, 1]
  if (map.step > 0.0) {
    value = floor(value / map.step + 0.5) * map.step;
  }

  return value;
}
`;
// NOTE: this must exactly the same name as in the uniform block
const map2DUniforms = {
    name: "map",
    vs: map2DUniformsBlock,
    fs: map2DUniformsBlock,
    uniformTypes: {
        valueRangeMin: "f32",
        valueRangeMax: "f32",
        colormapRangeMin: "f32",
        colormapRangeMax: "f32",
        bitmapResolution: "vec2<f32>",
        contours: "u32",
        hillshading: "u32",
        contourReferencePoint: "f32",
        contourInterval: "f32",
        isClampColor: "u32",
        colormapClampColor: "vec3<f32>",
        isColormapClampColorTransparent: "u32",
        isHeightMapTextureDefined: "u32",
        heightValueRangeMin: "f32",
        heightValueRangeMax: "f32",
        rgbScaler: "vec3<f32>",
        floatScaler: "f32",
        offset: "f32",
        step: "f32",
    },
};
//# sourceMappingURL=colormapLayer.js.map