import * as Cesium from "cesium";
import {VolumetricRenderLayer} from "./VolumetricRenderLayer.js";
import {ShaderLoader} from "@/modules/ShaderLoader.js";
import {MagoModeler} from "@/modules/common/MagoModeler.js";
import {MagoAABB} from "@/modules/common/geometry/MagoAABB.js";
import {MagoPoint3} from "@/modules/common/geometry/MagoPoint3.js";
import {RenderPrimitive} from "@/modules/common/RenderPrimitive.js";
export class VolumetricRenderer {
/**
* Constructor for the VolumetricRenderer class.
* @param viewer
* @param options
*/
constructor(viewer, options) {
Object.assign(this, options);
this.viewer = viewer;
this.shaderLoader = new ShaderLoader("/src/shaders/volumeRendering");
this.context = viewer.scene.context;
this.jsonIndex = options.jsonIndex;
this.pngsBinBlocksArray = options.pngsBinBlocksArray;
this.airPollutionLayers = [];
this.collection = this.primitiveCollection = new Cesium.PrimitiveCollection();
this.currentIdx = 0;
// make a map key = originalPngFileName, value = pngsBinBlock.***
this.map_pngOriginalFileName_pngsBinData = {};
let originalPngFileNamesCount = this.jsonIndex.pngsBinDataArray.length; // usually there are only one.***
for (let i = 0; i < originalPngFileNamesCount; i++) {
let pngsBinData = this.jsonIndex.pngsBinDataArray[i];
this.map_pngOriginalFileName_pngsBinData[pngsBinData.originalPngFileName] = pngsBinData;
}
// options.***
// timetables for shader uniforms.***
this.renderingColorType = 0; // 0 = rainbow, 1 = grayscale, 2 = colorLegend.
if (options.renderingColorType !== undefined) {
this.renderingColorType = options.renderingColorType; // 0 = rainbow, 1 = grayscale, 2 = colorLegend.
}
this.cuttingAAPlanePositionMC = undefined; // The position of the cutting plane in model coordinates.
if (options.cuttingAAPlanePositionMC !== undefined) {
this.cuttingAAPlanePositionMC = options.cuttingAAPlanePositionMC; // The position of the cutting plane in model coordinates.
}
this.cuttingAAPlaneNormalMC = undefined; // The normal of the plane. 0 = noApply, 1 = x, 2 = y, 3 = z, 4 = -x, 5 = -y, 6 = -z.
if (options.cuttingAAPlaneNormalMC !== undefined) {
this.cuttingAAPlaneNormalMC = options.cuttingAAPlaneNormalMC; // The normal of the plane. 0 = noApply, 1 = x, 2 = y, 3 = z, 4 = -x, 5 = -y, 6 = -z.
}
this.samplingsCount = 50;
if (options.samplingsCount !== undefined) {
this.samplingsCount = options.samplingsCount; // The number of samplings for the volumetric rendering.
}
this.colorLegends = [];
if (options.colorLegends !== undefined) {
this.colorLegends = options.colorLegends; // The color legends for the volumetric rendering.
}
this.legendValues = [];
if (options.legendValues !== undefined) {
this.legendValues = options.legendValues; // The legend values for the volumetric rendering.
}
this.legendValuesScale = 1.0; // Scale for the legend values.
if (options.legendValuesScale !== undefined) {
this.legendValuesScale = options.legendValuesScale; // Scale for the legend values.
}
}
async init() {
let layersCount = 1;
for (let i = 0; i < layersCount; i++) {
let options = {
pollutionVolumeOwner: this,
};
let layer = new VolumetricRenderLayer(this.viewer, this.context, this.jsonIndex, options);
this.airPollutionLayers.push(layer);
}
await this._prepareAirPollutionLayers();
await this.createRenderPrimitives();
}
addIndex() {
let maxIdx = this.jsonIndex.mosaicTexMetaDataJsonArray.length - 1;
this.currentIdx++;
if (this.currentIdx >= maxIdx) {
this.currentIdx = maxIdx;
} else if (this.currentIdx < 0) {
this.currentIdx = 0;
}
}
onChangeIdx(value) {
let maxIdx = this.jsonIndex.mosaicTexMetaDataJsonArray.length - 1;
let newIdx = Math.floor(value * maxIdx);
this.currentIdx = newIdx;
if (this.currentIdx >= maxIdx) {
this.currentIdx = maxIdx;
} else if (this.currentIdx < 0) {
this.currentIdx = 0;
}
}
onChangeCuttingPlanePositionMC(value) {
let aabb = this.getAABB();
let minPos = aabb.minPosition;
let maxPos = aabb.maxPosition;
let curPos = MagoMathUtils.mixVec3(minPos, maxPos, value);
let cuttingAAPlanePositionMC = this.#getCuttingAAPlanePositionMC();
cuttingAAPlanePositionMC.x = curPos.x;
cuttingAAPlanePositionMC.y = curPos.y;
cuttingAAPlanePositionMC.z = curPos.z;
this.cuttingAAPlanePositionMC = cuttingAAPlanePositionMC;
}
async createRenderPrimitives() {
let boxPrimitive = await this.#createRenderPrimitive();
this.collection.add(boxPrimitive);
}
async _prepareAirPollutionLayers() {
for (let i = 0; i < this.airPollutionLayers.length; i++) {
let layer = this.airPollutionLayers[i];
await layer._prepare();
}
}
getPrimitiveCollection() {
return this.primitiveCollection;
}
getBlobArrayBuffer(mosaicFileName) {
let pngsBinData = this.map_pngOriginalFileName_pngsBinData[mosaicFileName];
if (pngsBinData === undefined) {
return undefined;
}
let pngsBinBlocksCount = this.pngsBinBlocksArray.length;
for (let i = 0; i < pngsBinBlocksCount; i++) {
let pngsBinBlock = this.pngsBinBlocksArray[i];
if (pngsBinBlock.fileName === pngsBinData.pngsBinaryBlockDataFileName) {
let startIdx = pngsBinData.startByteIndex;
let endIdx = pngsBinData.endByteIndex;
let pngsBinBlockData = pngsBinBlock.dataArraybuffer;
return pngsBinBlockData.slice(startIdx, endIdx);
}
}
return undefined;
}
#getTexture3DSize() {
let someData = this.jsonIndex.mosaicTexMetaDataJsonArray[0];
let someDataSlice = someData.dataSlices[0];
let width = someDataSlice.width;
let height = someDataSlice.height;
let slicesCount = someData.dataSlices.length;
return [width, height, slicesCount];
}
#getAltitudeSlices(arrayLength) {
//**********************
// Uniform for shader.
//**********************
let someData = this.jsonIndex.mosaicTexMetaDataJsonArray[0];
let altitudeSlicesArray = [];
for (let i = 0; i < someData.dataSlices.length; i++) {
let dataSlice = someData.dataSlices[i];
let altitudeSlice = dataSlice.minAltitude;
altitudeSlicesArray.push(altitudeSlice);
}
if (altitudeSlicesArray.length < arrayLength) {
let diff = arrayLength - altitudeSlicesArray.length;
for (let i = 0; i < diff; i++) {
altitudeSlicesArray.push(0.0);
}
}
return altitudeSlicesArray;
}
#getMinMaxAltitudeSlices() {
//**********************
// Uniform for shader.
//**********************
let someData = this.jsonIndex.mosaicTexMetaDataJsonArray[0];
let minMaxAltitudeSlices = [];
for (let i = 0; i < 32; i++) {
if (i < someData.dataSlices.length) {
let dataSlice = someData.dataSlices[i];
let minMaxAltitudeSlice = new Cesium.Cartesian2(dataSlice.minAltitude, dataSlice.maxAltitude);
minMaxAltitudeSlices.push(minMaxAltitudeSlice);
} else {
let minMaxAltitudeSlice = new Cesium.Cartesian2(0.0, 0.0);
minMaxAltitudeSlices.push(minMaxAltitudeSlice);
}
}
return minMaxAltitudeSlices;
}
#getMosaicSize() {
//**********************
// Uniform for shader.
//**********************
let someData = this.jsonIndex.mosaicTexMetaDataJsonArray[0];
let mosaicColumnsCount = someData.mosaicColumnsCount;
let mosaicRowsCount = someData.mosaicRowsCount;
const slicesCount = 1;
let mosaicSize = [mosaicColumnsCount, mosaicRowsCount, slicesCount];
return mosaicSize;
}
#getCuttingAAPlanePositionMC() {
// Uniform for shader.
if (this.cuttingAAPlanePositionMC === undefined) {
this.cuttingAAPlanePositionMC = new Cesium.Cartesian3(0.0, 0.0, 0.0);
}
return this.cuttingAAPlanePositionMC;
}
#getCuttingAAPlaneNormalMC() {
// Uniform for shader.
// The normal of the plane. 0 = noApply, 1 = x, 2 = y, 3 = z, 4 = -x, 5 = -y, 6 = -z.***
if (this.cuttingAAPlaneNormalMC === undefined) {
this.cuttingAAPlaneNormalMC = 0;
}
return this.cuttingAAPlaneNormalMC;
}
getAABB() {
let sizeX = this.jsonIndex.width_km * 1000.0;
let sizeY = this.jsonIndex.height_km * 1000.0;
let sizeZ = this.airPollutionLayers[0].getMaxAltitude();
let semiX = sizeX / 2;
let semiY = sizeY / 2;
let minX = -semiX;
let minY = -semiY;
let minZ = this.airPollutionLayers[0].getMinAltitude();
let maxX = semiX;
let maxY = semiY;
let maxZ = sizeZ;
let minPos = new MagoPoint3(minX, minY, minZ);
let maxPos = new MagoPoint3(maxX, maxY, maxZ);
let aabb = new MagoAABB(minPos, maxPos);
return aabb;
}
createVolumetricBoxGeometry() {
let aabb = this.getAABB();
let minPos = aabb.minPosition;
let maxPos = aabb.maxPosition;
let minX = minPos.x;
let minY = minPos.y;
let minZ = minPos.z;
let maxX = maxPos.x;
let maxY = maxPos.y;
let maxZ = maxPos.z;
let magoModeler = new MagoModeler();
let box = magoModeler.createBox(minX, minY, minZ, maxX, maxY, maxZ);
return new Cesium.Geometry({
attributes: new Cesium.GeometryAttributes({
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT, componentsPerAttribute: 3, values: box.positions,
}), normal: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT, componentsPerAttribute: 3, values: box.normals,
}),
}), indices: box.indices,
});
}
async #createRenderPrimitive() {
let aabb = this.getAABB();
let minPos = aabb.minPosition;
let maxPos = aabb.maxPosition;
let minX = minPos.x;
let minY = minPos.y;
let minZ = minPos.z;
let maxX = maxPos.x;
let maxY = maxPos.y;
let maxZ = maxPos.z;
let lonDeg = this.jsonIndex.centerGeographicCoord.longitude;
let latDeg = this.jsonIndex.centerGeographicCoord.latitude;
let altitude = this.jsonIndex.centerGeographicCoord.altitude;
let center = Cesium.Cartesian3.fromDegrees(lonDeg, latDeg, altitude);
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
let tex3DSize = this.#getTexture3DSize();
let mosaicSize = this.#getMosaicSize();
let minMaxAltitudeSlices = this.#getMinMaxAltitudeSlices();
let totalMinValue = this.jsonIndex.totalMinValue;
let totalMaxValue = this.jsonIndex.totalMaxValue;
this.legendValuesCount = 0;
if (this.legendValues !== undefined && this.legendValues.length > 0) {
this.legendValuesCount = this.legendValues.length;
}
const volumetricVertexShader = await this.shaderLoader.getShaderSource("volumetric.vert");
const volumetricFragmentShader = await this.shaderLoader.getShaderSource("volumetric.frag");
let that = this;
return new RenderPrimitive(this.context, {
attributeLocations: {
position: 0,
}, geometry: this.createVolumetricBoxGeometry(), primitiveType: Cesium.PrimitiveType.TRIANGLES, modelMatrix: transform, uniformMap: {
u_minBoxPosition: function() {
return new Cesium.Cartesian3(minX, minY, minZ);
}, u_maxBoxPosition: function() {
return new Cesium.Cartesian3(maxX, maxY, maxZ);
}, mosaicTexture: function() {
return that.airPollutionLayers[0].volumetricDatasArray[that.currentIdx].mosaicTexture;
}, u_camPosWC: function() {
return that.viewer.scene.camera.positionWC;
}, u_texSize: function() {
return tex3DSize;
}, u_mosaicSize: function() {
return mosaicSize;
}, u_minMaxAltitudeSlices: function() {
return minMaxAltitudeSlices;
}, u_minMaxValues: function() {
return new Cesium.Cartesian2(totalMinValue, totalMaxValue);
}, u_legendColors: function() {
return that.colorLegends;
}, u_legendValues: function() {
return that.legendValues;
}, u_legendColorsCount: function() {
return that.legendValuesCount;
}, u_legendValuesScale: function() {
return that.legendValuesScale;
}, u_renderingColorType: function() {
return that.renderingColorType;
}, u_samplingsCount: function() {
return that.samplingsCount;
}, u_AAPlanePosMC: function() {
return that.#getCuttingAAPlanePositionMC();
}, u_AAPlaneNormalMC: function() {
return that.#getCuttingAAPlaneNormalMC();
},
}, vertexShaderSource: new Cesium.ShaderSource({
sources: [volumetricVertexShader],
}), fragmentShaderSource: new Cesium.ShaderSource({
sources: [volumetricFragmentShader],
}), rawRenderState: this.createRawRenderState({
depthTest: {
enabled: true,
}, depthMask: true, blending: {
enabled: true,
}, cull: {
enabled: false,
},
}), autoClear: false,
});
}
createRawRenderState(options) {
let translucent = true;
let closed = false;
let existing = {
viewport: options.viewport, depthTest: options.depthTest, depthMask: options.depthMask, blending: options.blending, cull: options.cull, colorMask: options.colorMask,
};
let rawRenderState = Cesium.Appearance.getDefaultRenderState(translucent, closed, existing);
return rawRenderState;
}
}