@loaders.gl/tiles
Version:
Common components for different tiles loaders.
113 lines (112 loc) • 5.98 kB
JavaScript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
// This file is derived from the Cesium code base under Apache 2 license
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md
// TODO - Dynamic screen space error provides an optimization when looking at
// tilesets from above
/* eslint-disable */
// @ts-nocheck
import { Matrix4, Vector3, clamp } from '@math.gl/core';
const scratchPositionNormal = new Vector3();
const scratchCartographic = new Vector3();
const scratchMatrix = new Matrix4();
const scratchCenter = new Vector3();
const scratchPosition = new Vector3();
const scratchDirection = new Vector3();
// eslint-disable-next-line max-statements, complexity
export function calculateDynamicScreenSpaceError(root, { camera, mapProjection }, options = {}) {
const { dynamicScreenSpaceErrorHeightFalloff = 0.25, dynamicScreenSpaceErrorDensity = 0.00278 } = options;
let up;
let direction;
let height;
let minimumHeight;
let maximumHeight;
const tileBoundingVolume = root.contentBoundingVolume;
if (tileBoundingVolume instanceof TileBoundingRegion) {
up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
direction = camera.directionWC;
height = camera.positionCartographic.height;
minimumHeight = tileBoundingVolume.minimumHeight;
maximumHeight = tileBoundingVolume.maximumHeight;
}
else {
// Transform camera position and direction into the local coordinate system of the tileset
const transformLocal = Matrix4.inverseTransformation(root.computedTransform, scratchMatrix);
const ellipsoid = mapProjection.ellipsoid;
const boundingVolume = tileBoundingVolume.boundingVolume;
const centerLocal = Matrix4.multiplyByPoint(transformLocal, boundingVolume.center, scratchCenter);
if (Cartesian3.magnitude(centerLocal) > ellipsoid.minimumRadius) {
// The tileset is defined in WGS84. Approximate the minimum and maximum height.
const centerCartographic = Cartographic.fromCartesian(centerLocal, ellipsoid, scratchCartographic);
up = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
direction = camera.directionWC;
height = camera.positionCartographic.height;
minimumHeight = 0.0;
maximumHeight = centerCartographic.height * 2.0;
}
else {
// The tileset is defined in local coordinates (z-up)
const positionLocal = Matrix4.multiplyByPoint(transformLocal, camera.positionWC, scratchPosition);
up = Cartesian3.UNIT_Z;
direction = Matrix4.multiplyByPointAsVector(transformLocal, camera.directionWC, scratchDirection);
direction = Cartesian3.normalize(direction, direction);
height = positionLocal.z;
if (tileBoundingVolume instanceof TileOrientedBoundingBox) {
// Assuming z-up, the last component stores the half-height of the box
const boxHeight = root._header.boundingVolume.box[11];
minimumHeight = centerLocal.z - boxHeight;
maximumHeight = centerLocal.z + boxHeight;
}
else if (tileBoundingVolume instanceof TileBoundingSphere) {
const radius = boundingVolume.radius;
minimumHeight = centerLocal.z - radius;
maximumHeight = centerLocal.z + radius;
}
}
}
// The range where the density starts to lessen. Start at the quarter height of the tileset.
const heightFalloff = dynamicScreenSpaceErrorHeightFalloff;
const heightClose = minimumHeight + (maximumHeight - minimumHeight) * heightFalloff;
const heightFar = maximumHeight;
const t = clamp((height - heightClose) / (heightFar - heightClose), 0.0, 1.0);
// Increase density as the camera tilts towards the horizon
const dot = Math.abs(Cartesian3.dot(direction, up));
let horizonFactor = 1.0 - dot;
// Weaken the horizon factor as the camera height increases, implying the camera is further away from the tileset.
// The goal is to increase density for the "street view", not when viewing the tileset from a distance.
horizonFactor = horizonFactor * (1.0 - t);
return dynamicScreenSpaceErrorDensity * horizonFactor;
}
export function fog(distanceToCamera, density) {
const scalar = distanceToCamera * density;
return 1.0 - Math.exp(-(scalar * scalar));
}
export function getDynamicScreenSpaceError(tileset, distanceToCamera) {
if (tileset.dynamicScreenSpaceError && tileset.dynamicScreenSpaceErrorComputedDensity) {
const density = tileset.dynamicScreenSpaceErrorComputedDensity;
const factor = tileset.dynamicScreenSpaceErrorFactor;
// TODO: Refined screen space error that minimizes tiles in non-first-person
const dynamicError = fog(distanceToCamera, density) * factor;
return dynamicError;
}
return 0;
}
export function getTiles3DScreenSpaceError(tile, frameState, useParentLodMetric) {
const tileset = tile.tileset;
const parentLodMetricValue = (tile.parent && tile.parent.lodMetricValue) || tile.lodMetricValue;
const lodMetricValue = useParentLodMetric ? parentLodMetricValue : tile.lodMetricValue;
// Leaf tiles do not have any error so save the computation
if (lodMetricValue === 0.0) {
return 0.0;
}
// TODO: Orthographic Frustum needs special treatment?
// this._getOrthograhicScreenSpaceError();
// Avoid divide by zero when viewer is inside the tile
const distance = Math.max(tile._distanceToCamera, 1e-7);
const { height, sseDenominator } = frameState;
const { viewDistanceScale } = tileset.options;
let error = (lodMetricValue * height * (viewDistanceScale || 1.0)) / (distance * sseDenominator);
error -= getDynamicScreenSpaceError(tileset, distance);
return error;
}