Skip to content

Commit

Permalink
Map3 d layer readout (equinor#749)
Browse files Browse the repository at this point in the history
* Map3DLayer: Added switch for depth readout. Also improved shading of surface.

* Lint & typecheck.

* Fix typecheck error.
  • Loading branch information
nilscb authored Jan 14, 2022
1 parent d7a6ba9 commit ec1ace3
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 29 deletions.
2 changes: 1 addition & 1 deletion react/src/lib/components/DeckGLMap/components/InfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ const InfoCard: React.FC<InfoCardProps> = (props: InfoCardProps) => {

// collecting card data for 1st type
const zValue = (info as PropertyMapPickInfo).propertyValue;
if (zValue) {
if (typeof zValue !== "undefined") {
const property = xy_properties.find(
(item) => item.name === layer_name
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export const layersDefaultProps: Record<string, unknown> = {
colorMapRange: { type: "array", value: [0, 1] },
rotDeg: 0,
contours: [-1.0, -1.0],
// readout is default property value but if set to true it will be depth/z-value.
isReadoutDepth: false,
enableSmoothShading: true,
},
GridLayer: {
"@@type": "GridLayer",
Expand Down
117 changes: 108 additions & 9 deletions react/src/lib/components/DeckGLMap/layers/terrain/map3DLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { layersDefaultProps } from "../layersDefaultProps";
import { TerrainLoader } from "@loaders.gl/terrain";
import { ImageLoader } from "@loaders.gl/images";
import { load } from "@loaders.gl/core";

import { Vector3 } from "@math.gl/core";
import { getModelMatrix } from "../utils/layerTools";

const ELEVATION_DECODER = {
Expand All @@ -18,6 +18,90 @@ const ELEVATION_DECODER = {
offset: 0,
};

type MeshType = {
attributes: {
POSITION: { value: number[] };
normals: { value: Float32Array; size: number };
};
indices: { value: number[] };
};

function add_normals(resolved_mesh: MeshType) {
const vertexs = resolved_mesh.attributes.POSITION.value;
const indices = resolved_mesh.indices.value;
const ntriangles = indices.length / 3;

//Calculate one normal pr triangle. And record the triangles each vertex' belongs to.
const no_unique_vertexes = vertexs.length / 3;
const vertex_triangles = Array(no_unique_vertexes); // for each vertex a list of triangles it belogs to.
for (let i = 0; i < no_unique_vertexes; i++) {
vertex_triangles[i] = new Set();
}

const triangle_normals = Array(ntriangles);
for (let t = 0; t < ntriangles; t++) {
const i0 = indices[t * 3 + 0];
const i1 = indices[t * 3 + 1];
const i2 = indices[t * 3 + 2];

vertex_triangles[i0].add(t);
vertex_triangles[i1].add(t);
vertex_triangles[i2].add(t);

// Triangles' three corners.
const v0 = new Vector3(
vertexs[i0 * 3 + 0],
vertexs[i0 * 3 + 1],
vertexs[i0 * 3 + 2]
);
const v1 = new Vector3(
vertexs[i1 * 3 + 0],
vertexs[i1 * 3 + 1],
vertexs[i1 * 3 + 2]
);
const v2 = new Vector3(
vertexs[i2 * 3 + 0],
vertexs[i2 * 3 + 1],
vertexs[i2 * 3 + 2]
);

const vec1 = v1.subtract(v0);
const vec2 = v2.subtract(v0);

const normal = vec1.cross(vec2).normalize();
triangle_normals[t] = normal;
}

// Calculate normals. The vertex normal will be the mean of the normals of every triangle the vertex
// belongs to.
const normals = Array(vertexs.length).fill(0.0);

for (let i = 0; i < no_unique_vertexes; i++) {
const triangles = [...vertex_triangles[i]];
// Set normal to mean of all triangle normals.
const v =
triangles.length !== 0
? triangle_normals[triangles[0]]
: new Vector3(0.0, 0.0, 1.0);
for (let t = 1; t < triangles.length; t++) {
v.add(triangle_normals[triangles[t]]);
}
v.normalize();

const idx = i * 3;
normals[idx + 0] = v[0];
normals[idx + 1] = v[1];
normals[idx + 2] = v[2];
}

resolved_mesh.attributes.normals = {
value: new Float32Array(normals),
size: 3,
};

return resolved_mesh;
}

export interface Map3DLayerProps<D> extends ExtendedLayerProps<D> {
// Url to png image representing the height mesh.
mesh: string;
Expand Down Expand Up @@ -45,12 +129,33 @@ export interface Map3DLayerProps<D> extends ExtendedLayerProps<D> {

// Use color map in this range.
colorMapRange: [number, number];

// If true readout will be z value (depth). Otherwise it is the texture property value.
isReadoutDepth: boolean;

// Will calculate normals and enable phong shading.
enableSmoothShading: boolean;
}

export default class Map3DLayer extends CompositeLayer<
unknown,
Map3DLayerProps<unknown>
> {
renderLayers(): [TerrainMapLayer] {
let mesh = load(this.props.mesh, TerrainLoader, {
terrain: {
elevationDecoder: ELEVATION_DECODER,
bounds: this.props.bounds,
meshMaxError: this.props.meshMaxError,
skirtHeight: 0.0,
},
});

// Note: mesh contains triangles. No normals.
if (this.props.enableSmoothShading) {
mesh = mesh.then(add_normals);
}

const rotatingModelMatrix = getModelMatrix(
this.props.rotDeg,
this.props.bounds[0] as number, // Rotate around upper left corner of bounds
Expand All @@ -62,21 +167,15 @@ export default class Map3DLayer extends CompositeLayer<
TerrainMapLayerData,
TerrainMapLayerProps<TerrainMapLayerData>
>({
mesh: load(this.props.mesh, TerrainLoader, {
terrain: {
elevationDecoder: ELEVATION_DECODER,
bounds: this.props.bounds,
meshMaxError: this.props.meshMaxError,
},
}),

mesh,
texture: load(this.props.propertyTexture, ImageLoader, {}),
pickable: this.props.pickable,
modelMatrix: rotatingModelMatrix,
contours: this.props.contours,
colorMapName: this.props.colorMapName,
valueRange: this.props.valueRange,
colorMapRange: this.props.colorMapRange,
isReadoutDepth: this.props.isReadoutDepth,
})
);
return [layer];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export interface TerrainMapLayerProps<D> extends SimpleMeshLayerProps<D> {

// Use color map in this range.
colorMapRange: [number, number];

//If true readout will be z value (depth). Otherwise it is the texture property value.
isReadoutDepth: boolean;
}

const defaultProps = {
Expand All @@ -74,12 +77,17 @@ const defaultProps = {
getColor: (d: DataItem) => d.color,
getOrientation: (d: DataItem) => [0, d.angle, 0],
contours: [-1, -1],

colorMapName: "",
valueRange: [0.0, 1.0],
colorMapRange: [0.0, 1.0],

isReadoutDepth: false,
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
material: {
ambient: 0.35,
diffuse: 0.6,
shininess: 600,
specularColor: [255, 255, 255],
},
};

// This is a private layer used only by the composite Map3DLayer.
Expand All @@ -92,6 +100,10 @@ export default class TerrainMapLayer extends SimpleMeshLayer<
// Signature from the base class, eslint doesn't like the any type.
// eslint-disable-next-line
draw({ uniforms, context }: any): void {
const contourReferencePoint = this.props.contours[0] ?? -1.0;
const contourInterval = this.props.contours[1] ?? -1.0;
const isReadoutDepth = this.props.isReadoutDepth;

const valueRangeMin = this.props.valueRange[0] ?? 0.0;
const valueRangeMax = this.props.valueRange[1] ?? 1.0;

Expand All @@ -103,8 +115,6 @@ export default class TerrainMapLayer extends SimpleMeshLayer<
super.draw({
uniforms: {
...uniforms,
contourReferencePoint: this.props.contours[0] ?? -1.0,
contourInterval: this.props.contours[1] ?? -1.0,
colormap: new Texture2D(context.gl, {
width: 256,
height: 1,
Expand All @@ -120,6 +130,9 @@ export default class TerrainMapLayer extends SimpleMeshLayer<
valueRangeMax,
colorMapRangeMin,
colorMapRangeMax,
contourReferencePoint,
contourInterval,
isReadoutDepth,
},
});
}
Expand Down Expand Up @@ -157,21 +170,28 @@ export default class TerrainMapLayer extends SimpleMeshLayer<
return info;
}

// Note these colors are in the 0-255 range.
const r = info.color[0] * DECODER.rScaler;
const g = info.color[1] * DECODER.gScaler;
const b = info.color[2] * DECODER.bScaler;

const floatScaler = 1.0 / (256.0 * 256.0 * 256.0 - 1.0);

const value = (r + g + b) * floatScaler;
const isPropertyReadout = !this.props.isReadoutDepth; // Either map properties or map depths are encoded here.
let value = (r + g + b) * (isPropertyReadout ? floatScaler : 1.0);

if (isPropertyReadout) {
// Remap the [0, 1] decoded value to colorMapRange.
const [min, max] = this.props.colorMapRange;
value = value * (max - min) + min;
}

// Remap the [0, 1] decoded value to colorMapRange.
const [min, max] = this.props.colorMapRange;
const decodedValue = value * (max - min) + min;
const valueString =
(isPropertyReadout ? "Property: " : "Depth: ") + value.toFixed(1);

return {
...info,
propertyValue: decodedValue,
propertyValue: valueString,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ uniform sampler2D sampler;
uniform bool flatShading;
uniform float opacity;

uniform bool isReadoutDepth;

uniform float contourReferencePoint;
uniform float contourInterval;

uniform bool isHillShadingIn2d;

in vec2 vTexCoord;
in vec3 cameraPosition;
in vec3 normals_commonspace;
Expand All @@ -36,7 +36,7 @@ void main(void) {
geometry.uv = vTexCoord;

vec3 normal;
if (flatShading) { // denne er true
if (flatShading) {
#ifdef DERIVATIVES_AVAILABLE
normal = normalize(cross(dFdx(position_commonspace.xyz), dFdy(position_commonspace.xyz)));
#else
Expand All @@ -48,9 +48,36 @@ void main(void) {

vec4 color = hasTexture ? texture(sampler, vTexCoord) : vColor;

// If it's a picking pass, we just return the raw texture value.
// Picking pass.
if (picking_uActive) {
fragColor = color;
if (isReadoutDepth) {
// Readout should not be the surface property but the surface height (z value).
float depth = abs(worldPos.z);

// Express in 255 system.
float r = 0.0;
float g = 0.0;
float b = 0.0;

if (depth >= (256.0 * 256.0) - 1.0) {
r = floor(depth / (256.0 * 256.0));
depth -= r * (256.0 * 256.0);
}

if (depth >= 256.0 - 1.0) {
g = floor(depth / 256.0);
depth -= g * 256.0;
}

b = floor(depth);

fragColor = vec4(r / 255.0, g / 255.0, b / 255.0, 1.0);
}
else {
// Readout is the surface property, i.e. the raw texture value.
fragColor = color;
}

return;
}

Expand All @@ -73,10 +100,10 @@ void main(void) {

bool is_contours = contourReferencePoint != -1.0 && contourInterval != -1.0;
if (is_contours) {
float height = (worldPos.z - contourReferencePoint) / contourInterval;
float depth = (abs(worldPos.z) - contourReferencePoint) / contourInterval;

float f = fract(height);
float df = fwidth(height);
float f = fract(depth);
float df = fwidth(depth);

// keep: float c = smoothstep(df * 1.0, df * 2.0, f); // smootstep from/to no of pixels distance fronm contour line.
float c = smoothstep(0.0, df * 2.0, f);
Expand All @@ -89,4 +116,4 @@ void main(void) {
fragColor = vec4(lightColor, color.a * opacity);

DECKGL_FILTER_COLOR(fragColor, geometry);
}
}
7 changes: 6 additions & 1 deletion react/src/lib/components/DeckGLMap/redux/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export const SliderTypeProps = [

export const ToggleTypeProps = [
{ id: "logCurves", displayName: "Log curves", dependentOnProp: "logData" },
{
id: "isReadoutDepth",
displayName: "Depth readout",
dependentOnProp: undefined,
},
] as const;

export const MenuTypeProps = [
Expand Down Expand Up @@ -77,7 +82,7 @@ export const LayerIcons = {
ColormapLayer: "surface_layer",
Hillshading2DLayer: "hill_shading",
WellsLayer: "well",
Map3DLayer: "color_palette",
Map3DLayer: "fault",
PieChartLayer: "pie_chart",
GridLayer: "grid_layer",
FaultPolygonsLayer: "fault",
Expand Down

0 comments on commit ec1ace3

Please sign in to comment.