@webviz/subsurface-viewer
Version:
3D visualization component for subsurface reservoir data
495 lines • 22.6 kB
JavaScript
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 { 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;
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;
}
const smooth = (_b = (_a = this.props.colormapSetup) === null || _a === void 0 ? void 0 : _a.smooth) !== null && _b !== void 0 ? _b : 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,
],
})), { 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;
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);
// 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, _k, _l, _m, _o, _p, _q, _r;
model.shaderInputs.setProps(Object.assign(Object.assign({}, args.uniforms), { triangles: {
colormapRange: (_b = (_a = this.props.colormapSetup) === null || _a === void 0 ? void 0 : _a.valueRange) !== null && _b !== void 0 ? _b : defaultColormapSetup.valueRange,
clampRange: ((_c = this.props.colormapSetup) === null || _c === void 0 ? void 0 : _c.clampRange) === null
? null
: ((_g = (_e = (_d = this.props.colormapSetup) === null || _d === void 0 ? void 0 : _d.clampRange) !== null && _e !== void 0 ? _e : (_f = this.props.colormapSetup) === null || _f === void 0 ? void 0 : _f.valueRange) !== null && _g !== void 0 ? _g : defaultColormapSetup.valueRange),
useClampColors: ((_h = this.props.colormapSetup) === null || _h === void 0 ? void 0 : _h.clampColor) !== null &&
((_j = this.props.colormapSetup) === null || _j === void 0 ? void 0 : _j.clampRange) !== null,
lowClampColor: lowClampColors,
highClampColor: highClampColor,
undefinedValue: (_l = (_k = this.props.colormapSetup) === null || _k === void 0 ? void 0 : _k.undefinedValue) !== null && _l !== void 0 ? _l : defaultColormapSetup.undefinedValue,
undefinedColor: toNormalizedColor((_o = (_m = this.props.colormapSetup) === null || _m === void 0 ? void 0 : _m.undefinedColor) !== null && _o !== void 0 ? _o : defaultColormapSetup.undefinedColor),
// Normalize color to [0,1] range.
uColor: toNormalizedColor((_p = this.props.color) !== null && _p !== void 0 ? _p : defaultProps.color),
smoothShading: (_q = this.props.smoothShading) !== null && _q !== void 0 ? _q : defaultProps.smoothShading,
ZIncreasingDownwards: (_r = this.props.ZIncreasingDownwards) !== null && _r !== void 0 ? _r : 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;
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));
}
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