Skip to content

Commit

Permalink
fix(Terrain): use exact method to compute min and max elevation node;
Browse files Browse the repository at this point in the history
  • Loading branch information
gchoqueux committed Feb 2, 2021
1 parent e7b2653 commit 6297c09
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 51 deletions.
2 changes: 1 addition & 1 deletion src/Core/TileMesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class TileMesh extends THREE.Mesh {
* @param {?number} scale
*/
setBBoxZ(min, max, scale) {
if (min == undefined && max == undefined) {
if (min == null && max == null) {
return;
}
// FIXME: Why the floors ? This is not conservative : the obb may be too short by almost 1m !
Expand Down
71 changes: 47 additions & 24 deletions src/Parser/XbilParser.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,71 @@
import { readTextureValueWithBilinearFiltering } from 'Utils/DEMUtils';

function minMax4Corners(texture, pitch, noDataValue) {
const u = pitch.x;
const v = pitch.y;
const w = pitch.z;
const z = [
readTextureValueWithBilinearFiltering({ noDataValue }, texture, u, v),
readTextureValueWithBilinearFiltering({ noDataValue }, texture, u + w, v),
readTextureValueWithBilinearFiltering({ noDataValue }, texture, u + w, v + w),
readTextureValueWithBilinearFiltering({ noDataValue }, texture, u, v + w),
].filter(v => v != undefined && v > -10);

if (z.length) {
return { min: Math.min(...z), max: Math.max(...z) };
}
}

/**
* Calculates the minimum maximum elevation of xbil buffer
* Calculates the minimum maximum texture elevation with xbil data
*
* @param {number} buffer The buffer to parse
* @param {number} width The buffer's width
* @param {number} height The buffer's height
* @param {THREE.Texture} texture The texture to parse
* @param {THREE.Vector4} pitch The pitch, restrict zone to parse
* @param {number} noDataValue No data value
* @return {Object} The minimum maximum elevation.
*/
export function computeMinMaxElevation(buffer, width, height, pitch) {
let min = 1000000;
let max = -1000000;

if (!buffer) {
export function computeMinMaxElevation(texture, pitch, noDataValue) {
const { width, height, data } = texture.image;
if (!data) {
// Return null values means there's no elevation values.
// They can't be determined.
// Don't return 0 because the result will be wrong
return { min: null, max: null };
}

const sizeX = pitch ? Math.floor(pitch.z * width) : buffer.length;
const sizeY = pitch ? Math.floor(pitch.z * height) : 1;
const xs = pitch ? Math.floor(pitch.x * width) : 0;
const ys = pitch ? Math.floor(pitch.y * height) : 0;
// compute extact minimum and maximum elvation on 4 corners texture.
let { min, max } = minMax4Corners(texture, pitch, noDataValue) || { max: -Infinity, min: Infinity };

const inc = pitch ? Math.max(Math.floor(sizeX / 8), 2) : 16;
const sizeX = Math.floor(pitch.z * width);

for (let y = ys; y < ys + sizeY; y += inc) {
const pit = y * (width || 0);
for (let x = xs; x < xs + sizeX; x += inc) {
const val = buffer[pit + x];
if (val > -10) {
max = Math.max(max, val);
min = Math.min(min, val);
if (sizeX > 2) {
const sizeY = Math.floor(pitch.z * height);
const xs = Math.floor(pitch.x * width);
const ys = Math.floor(pitch.y * height);
const inc = Math.max(Math.floor(sizeX / 32), 2);
const limX = ys + sizeY;
for (let y = ys; y < limX; y += inc) {
const pit = y * (width || 0);
let x = pit + xs;
const limX = x + sizeX;
for (x; x < limX; x += inc) {
const val = data[x];
if (val > -10 && val != noDataValue) {
max = Math.max(max, val);
min = Math.min(min, val);
}
}
}
}

if (max === -1000000 || min === 1000000) {
if (max === -Infinity || min === Infinity) {
// Return null values means the elevation values are incoherent
// They can't be determined.
// Don't return 0, -1000000 or 1000000 because the result will be wrong
// Don't return 0, -Infinity or Infinity because the result will be wrong
return { min: null, max: null };
} else {
return { min, max };
}
return { min, max };
}

// We check if the elevation texture has some significant values through corners
Expand Down
21 changes: 11 additions & 10 deletions src/Process/LayeredMaterialNodeProcessing.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,18 @@ export function updateLayeredMaterialNodeElevation(context, layer, node, parent)
const parentLayer = parent.material && parent.material.getLayer(layer.id);
nodeLayer.initFromParent(parentLayer, extentsDestination);

if (nodeLayer.level >= layer.source.zoom.min) {
if (nodeLayer.level >= 0) {
// Compute min max elevation if the node is initialized
const { min, max } = computeMinMaxElevation(
nodeLayer.textures[0].image.data,
SIZE_TEXTURE_TILE, SIZE_TEXTURE_TILE,
nodeLayer.offsetScales[0]);
nodeLayer.textures[0],
nodeLayer.offsetScales[0],
nodeLayer.layer.noDataValue);
node.setBBoxZ(min, max, layer.scale);
context.view.notifyChange(node, false);
return;

if (nodeLayer.level >= layer.source.zoom.min) {
context.view.notifyChange(node, false);
return;
}
}
}

Expand Down Expand Up @@ -248,10 +252,7 @@ export function updateLayeredMaterialNodeElevation(context, layer, node, parent)
elevation.min = layer.colorTextureElevationMinZ;
elevation.max = layer.colorTextureElevationMaxZ;
} else {
const { min, max } = computeMinMaxElevation(elevation.texture.image.data,
SIZE_TEXTURE_TILE,
SIZE_TEXTURE_TILE,
elevation.pitch);
const { min, max } = computeMinMaxElevation(elevation.texture, elevation.pitch, layer.noDataValue);
elevation.min = !min ? 0 : min;
elevation.max = !max ? 0 : max;
}
Expand Down
35 changes: 19 additions & 16 deletions src/Utils/DEMUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function tileAt(pt, tile) {
}

let _canvas;
function _readTextureValueAt(layer, texture, ...uv) {
function _readTextureValueAt(metadata, texture, ...uv) {
for (let i = 0; i < uv.length; i += 2) {
uv[i] = THREE.MathUtils.clamp(uv[i], 0, texture.image.width - 1);
uv[i + 1] = THREE.MathUtils.clamp(uv[i + 1], 0, texture.image.height - 1);
Expand All @@ -116,12 +116,14 @@ function _readTextureValueAt(layer, texture, ...uv) {
if (texture.image.data) {
// read a single value
if (uv.length === 2) {
return texture.image.data[uv[1] * texture.image.width + uv[0]];
const v = texture.image.data[uv[1] * texture.image.width + uv[0]];
return v != metadata.noDataValue ? v : undefined;
}
// or read multiple values
const result = [];
for (let i = 0; i < uv.length; i += 2) {
result.push(texture.image.data[uv[i + 1] * texture.image.width + uv[i]]);
const v = texture.image.data[uv[i + 1] * texture.image.width + uv[i]];
result.push(v != metadata.noDataValue ? v : undefined);
}
return result;
} else {
Expand Down Expand Up @@ -149,19 +151,17 @@ function _readTextureValueAt(layer, texture, ...uv) {
ctx.drawImage(texture.image, minx, miny, dw, dh, 0, 0, dw, dh);
const d = ctx.getImageData(0, 0, dw, dh);

const elevationLayer = layer.attachedLayers.filter(l => l.isElevationLayer)[0];

const result = [];
for (let i = 0; i < uv.length; i += 2) {
const ox = uv[i] - minx;
const oy = uv[i + 1] - miny;

// d is 4 bytes per pixel
const v = THREE.MathUtils.lerp(
elevationLayer.colorTextureElevationMinZ,
elevationLayer.colorTextureElevationMaxZ,
metadata.colorTextureElevationMinZ,
metadata.colorTextureElevationMaxZ,
d.data[4 * oy * dw + 4 * ox] / 255);
result.push(v != elevationLayer.noDataValue ? v : undefined);
result.push(v != metadata.noDataValue ? v : undefined);
}
if (uv.length === 2) {
return result[0];
Expand Down Expand Up @@ -189,13 +189,13 @@ function _convertUVtoTextureCoords(texture, u, v) {
return { u1, u2, v1, v2, wu, wv };
}

function _readTextureValueNearestFiltering(layer, texture, vertexU, vertexV) {
function _readTextureValueNearestFiltering(metadata, texture, vertexU, vertexV) {
const coords = _convertUVtoTextureCoords(texture, vertexU, vertexV);

const u = (coords.wu <= 0) ? coords.u1 : coords.u2;
const v = (coords.wv <= 0) ? coords.v1 : coords.v2;

return _readTextureValueAt(layer, texture, u, v);
return _readTextureValueAt(metadata, texture, u, v);
}

function _lerpWithUndefinedCheck(x, y, t) {
Expand All @@ -208,10 +208,10 @@ function _lerpWithUndefinedCheck(x, y, t) {
}
}

function _readTextureValueWithBilinearFiltering(layer, texture, vertexU, vertexV) {
export function readTextureValueWithBilinearFiltering(metadata, texture, vertexU, vertexV) {
const coords = _convertUVtoTextureCoords(texture, vertexU, vertexV);

const [z11, z21, z12, z22] = _readTextureValueAt(layer, texture,
const [z11, z21, z12, z22] = _readTextureValueAt(metadata, texture,
coords.u1, coords.v1,
coords.u2, coords.v1,
coords.u1, coords.v2,
Expand All @@ -227,7 +227,8 @@ function _readTextureValueWithBilinearFiltering(layer, texture, vertexU, vertexV


function _readZFast(layer, texture, uv) {
return _readTextureValueNearestFiltering(layer, texture, uv.x, uv.y);
const elevationLayer = layer.attachedLayers.filter(l => l.isElevationLayer)[0];
return _readTextureValueNearestFiltering(elevationLayer, texture, uv.x, uv.y);
}

const bary = new THREE.Vector3();
Expand Down Expand Up @@ -282,10 +283,12 @@ function _readZCorrect(layer, texture, uv, tileDimensions, tileOwnerDimensions)
// bary holds the respective weight of each vertices of the triangles
tri.getBarycoord(new THREE.Vector3(uv.x, uv.y), bary);

const elevationLayer = layer.attachedLayers.filter(l => l.isElevationLayer)[0];

// read the 3 interesting values
const z1 = _readTextureValueWithBilinearFiltering(layer, texture, tri.a.x, tri.a.y);
const z2 = _readTextureValueWithBilinearFiltering(layer, texture, tri.b.x, tri.b.y);
const z3 = _readTextureValueWithBilinearFiltering(layer, texture, tri.c.x, tri.c.y);
const z1 = readTextureValueWithBilinearFiltering(elevationLayer, texture, tri.a.x, tri.a.y);
const z2 = readTextureValueWithBilinearFiltering(elevationLayer, texture, tri.b.x, tri.b.y);
const z3 = readTextureValueWithBilinearFiltering(elevationLayer, texture, tri.c.x, tri.c.y);

// Blend with bary
return z1 * bary.x + z2 * bary.y + z3 * bary.z;
Expand Down

0 comments on commit 6297c09

Please # to comment.