@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
142 lines (138 loc) • 4.2 kB
JavaScript
/*
* Copyright (c) 2015-2018, IGN France.
* Copyright (c) 2018-2026, Giro3D team.
* SPDX-License-Identifier: MIT
*/
import { FloatType, NoColorSpace, RGFormat } from 'three';
import { isFiniteNumber } from '../../utils/predicates';
import { nonNull } from '../../utils/tsutils';
import Layer from './Layer';
/**
* A layer that provides elevation data to display terrains.
*/
export class ElevationLayer extends Layer {
/**
* Read-only flag to check if a given object is of type ElevationLayer.
*/
isElevationLayer = true;
/**
* Creates an elevation layer.
* See the example for more information on layer creation.
*
* @param options - The layer options.
*/
constructor(options) {
super({
...options,
noDataOptions: options.noDataOptions ?? {
replaceNoData: false
},
computeMinMax: options.computeMinMax ?? true,
// If min/max is not provided, we *have* to preload images
// to compute the min/max during preprocessing.
preloadImages: options.preloadImages ?? options.minmax == null
});
if (options.minmax) {
this.minmax = options.minmax;
} else {
this.minmax = {
min: 0,
max: 0,
isDefault: true
};
}
this.type = 'ElevationLayer';
}
getRenderTargetDataType() {
return FloatType;
}
getRenderTargetPixelFormat() {
// Elevation textures need two channels:
// - The elevation values
// - A bitmask to indicate no-data values
// The closest format that suits those needs is the RGFormat,
// although we have to be aware that the bitmask is not located
// in the alpha channel, but in the green channel.
return RGFormat;
}
adjustExtent(extent) {
// If we know the extent of the source/layer, we can additionally
// crop the margin extent to ensure it does not overflow the layer extent.
// This is necessary for elevation layers as they do not use an atlas.
const thisExtent = this.getExtent();
if (thisExtent && extent.intersectsExtent(thisExtent)) {
extent.intersect(thisExtent);
}
return extent;
}
async onInitialized() {
// Compute a min/max approximation using the background images that
// are already present on the composer.
if (this.minmax == null || this.minmax.isDefault === true) {
const extent = nonNull(this.getExtent(), 'neither this layer nor the source has an extent');
const {
min,
max
} = nonNull(this._composer).getMinMax(extent);
this.minmax = {
min,
max
};
}
}
unregisterNode(node) {
super.unregisterNode(node);
node.removeElevationTexture();
node.material.removeElevationLayer();
}
getMinMax(texture) {
const min = isFiniteNumber(texture.min) ? texture.min : this.minmax.min;
const max = isFiniteNumber(texture.max) ? texture.max : this.minmax.max;
// Refine the min/max values using the new texture.
this.minmax.min = Math.min(min, this.minmax.min);
this.minmax.max = Math.max(max, this.minmax.max);
return {
min,
max
};
}
applyTextureToNode(textureAndPitch, target) {
const {
texture,
pitch
} = textureAndPitch;
const {
min,
max
} = this.getMinMax(texture);
const node = target.node;
if (!node.material.hasElevationLayer(this)) {
node.material.pushElevationLayer(this);
}
node.setElevationTexture(this, {
...{
texture,
pitch,
min,
max
},
renderTarget: nonNull(target.renderTarget).object
});
}
applyEmptyTextureToNode(target) {
target.node.removeElevationTexture();
}
onTextureCreated(texture) {
// Elevation textures not being color textures, they must not be
// subjected to colorspace transformations that would alter their values.
// See https://threejs.org/docs/#manual/en/introduction/Color-management
texture.colorSpace = NoColorSpace;
}
}
/**
* Returns `true` if the given object is a {@link ElevationLayer}.
*/
export function isElevationLayer(obj) {
return typeof obj === 'object' && obj?.isElevationLayer;
}
export default ElevationLayer;