control/depthCamera/DepthCamera.tstypescript
import { Camera } from "../../camera/Camera";
import { PlanetCamera } from "../../camera/PlanetCamera";
import { Entity } from "../../entity/Entity";
import { LonLat } from "../../LonLat";
import { DEGREES } from "../../math";
import { Vec2 } from "../../math/Vec2";
import { Vec3 } from "../../math/Vec3";
import { Vec4, type NumberArray4 } from "../../math/Vec4";
import { RADIANS_HALF } from "../../math";
import { Object3d } from "../../Object3d";
import { QuadTreeStrategy } from "../../quadTree";
import type { Renderer } from "../../renderer/Renderer";
import { VARIANCE_SHADOW_ENABLED } from "../../renderer/shadows/ShadowManager";
import type { Planet } from "../../scene/Planet";
import { Framebuffer } from "../../webgl";
import { Vector } from "../../layer/Vector";
import type { ShadowMap } from "../../renderer/shadows/ShadowMap";
import type { DepthCameraHandler } from "./DepthCameraHandler";
const CAM_WIDTH = 512;
const CAM_HEIGHT = 512;
const DEPTH_NEAR = 100;
const DEPTH_FAR = 100000;
const DEPTH_BIAS_WORLD = 1.0;
const DEPTH_NORMAL_BIAS = 0.45;
const TEXEL_SNAP_EPSILON = 1e-9;
const ORTHO_TEXEL_QUANTIZATION_STEPS = 4;
const ORTHO_TEXEL_QUANTIZATION_RATIO = Math.pow(2.0, 1.0 / ORTHO_TEXEL_QUANTIZATION_STEPS);
const DEFAULT_VERTICAL_VIEW_ANGLE = 45;
const PERIMETER_STEP_PX = 1;
const DEFAULT_CAMERA_FRUSTUM_LENGTH = 2.5;
const DEFAULT_CAMERA_FRUSTUM_COLOR = "rgb(155, 255, 155, 0.2)";
const RENDER_SKIRTS_SLOPE = 0.3;
const cameraFrustumObj = Object3d.createFrustum();
interface IPerimeterSegmentsData {
segments: LonLat[][];
isClosed: boolean;
}
export interface IDepthCameraParams {
enabled?: boolean;
width?: number;
height?: number;
near?: number;
far?: number;
verticalViewAngle?: number;
horizontalViewAngle?: number;
showFrustum?: boolean;
showFootprint?: boolean;
frustumLength?: number;
frustumColor?: Vec4 | NumberArray4 | string;
isOrthographic?: boolean;
focusDistance?: number;
enableSegmentSkirts?: boolean;
enableSegmentFaceCulling?: boolean;
excludeLayers?: Vector[];
depthBiasWorld?: number;
normalBias?: number; // 0.2 .. 1.0
}
function getDistanceFromPixel(x: number, y: number, camera: Camera, framebuffer: Framebuffer): number {
const px = new Vec2(x, y);
const nx = px.x / framebuffer.width;
const ny = (framebuffer.height - px.y) / framebuffer.height;
const ddd = new Float32Array(4);
framebuffer.readData(nx, ny, ddd, 0);
if (ddd[0] === 0) {
return 0;
}
const depth = ddd[0];
const proj = camera.frustums[0].inverseProjectionMatrix;
const screenPos = new Vec4(nx * 2.0 - 1.0, ny * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
const viewPosition = proj.mulVec4(screenPos);
const dir = camera.unproject(nx * camera.width, (1 - ny) * camera.height);
return -(viewPosition.z / viewPosition.w) / dir.dot(camera.getForward());
}
export class DepthCamera {
protected static __counter__ = 0;
public readonly id: number;
public readonly width: number;
public readonly height: number;
public readonly near: number;
public readonly far: number;
public readonly verticalViewAngle: number;
public readonly horizontalViewAngle?: number;
public readonly excludeLayers: Vector[];
public depthBiasWorld: number;
public normalBias: number;
public enabled: boolean;
public enableSegmentSkirts: boolean;
public enableSegmentFaceCulling: boolean;
public camera!: Camera;
public framebuffer!: Framebuffer;
public quadTreeStrategy!: QuadTreeStrategy;
public shadowMap: ShadowMap | null;
public _handler: DepthCameraHandler | null;
public _handlerIndex: number;
protected _planet: Planet | null;
protected _renderer: Renderer | null;
protected _initialized: boolean;
public _forceOwnQuadTreeStrategyPass: boolean;
protected _showFrustum: boolean;
protected _showFootprint: boolean;
protected _isOrthographic: boolean;
protected _focusDistance: number;
protected _lastPlanetHeightFactor: number;
protected _orthoTexelSizeX: number;
protected _orthoTexelSizeY: number;
protected _frustumLength: number;
protected _frustumColor: Vec4 | NumberArray4 | string;
protected _cameraFrustumEntity: Entity | null;
protected _cameraFootprintEntity: Entity | null;
protected _cameraFootprintSegmentPointCounts: number[];
protected _cameraFootprintClosedState: boolean;
protected _prevCameraPos: Vec3;
protected _prevCameraPitch: number;
protected _prevCameraYaw: number;
protected _prevCameraRoll: number;
protected _prevCameraFrustumEntityPos: Vec3;
protected _prevCameraFrustumEntityPitch: number;
protected _prevCameraFrustumEntityYaw: number;
protected _prevCameraFrustumEntityRoll: number;
constructor(params: IDepthCameraParams = {}) {
this.id = DepthCamera.__counter__++;
this.enabled = params.enabled ?? true;
this.enableSegmentSkirts = params.enableSegmentSkirts ?? false;
this.enableSegmentFaceCulling = params.enableSegmentFaceCulling ?? true;
this.width = params.width ?? CAM_WIDTH;
this.height = params.height ?? CAM_HEIGHT;
this.near = params.near ?? DEPTH_NEAR;
this.far = params.far ?? DEPTH_FAR;
this.verticalViewAngle = params.verticalViewAngle ?? DEFAULT_VERTICAL_VIEW_ANGLE;
this.horizontalViewAngle = params.horizontalViewAngle;
this.excludeLayers = params.excludeLayers ? [...params.excludeLayers] : [];
this.depthBiasWorld = params.depthBiasWorld ?? DEPTH_BIAS_WORLD;
this.normalBias = params.normalBias ?? DEPTH_NORMAL_BIAS;
this._planet = null;
this._renderer = null;
this._initialized = false;
this._forceOwnQuadTreeStrategyPass = true;
this._showFrustum = params.showFrustum ?? true;
this._showFootprint = params.showFootprint ?? true;
this._isOrthographic = params.isOrthographic ?? false;
this._focusDistance = params.focusDistance ?? this.far;
this._lastPlanetHeightFactor = 1.0;
this._orthoTexelSizeX = 0.0;
this._orthoTexelSizeY = 0.0;
this._frustumLength = params.frustumLength ?? DEFAULT_CAMERA_FRUSTUM_LENGTH;
this._frustumColor = params.frustumColor ?? DEFAULT_CAMERA_FRUSTUM_COLOR;
this._cameraFootprintEntity = this._showFootprint ? this._createCameraFootprintEntity() : null;
this._cameraFrustumEntity = this._showFrustum ? this._createCameraFrustumEntity() : null;
this._cameraFootprintSegmentPointCounts = [];
this._cameraFootprintClosedState = false;
this._handler = null;
this._handlerIndex = -1;
this.shadowMap = null;
this._prevCameraPos = new Vec3();
this._prevCameraPitch = 0;
this._prevCameraYaw = 0;
this._prevCameraRoll = 0;
this._prevCameraFrustumEntityPos = new Vec3();
this._prevCameraFrustumEntityPitch = 0;
this._prevCameraFrustumEntityYaw = 0;
this._prevCameraFrustumEntityRoll = 0;
}
public get initialized(): boolean {
return this._initialized;
}
public get showFrustum(): boolean {
return this._showFrustum;
}
public set showFrustum(showFrustum: boolean) {
this._showFrustum = showFrustum;
if (showFrustum && !this._cameraFrustumEntity) {
this._cameraFrustumEntity = this._createCameraFrustumEntity();
}
if (this._handler && this._handlerIndex !== -1 && this._cameraFrustumEntity) {
if (showFrustum) {
this._handler.cameraFrustumLayer.add(this._cameraFrustumEntity);
} else {
this._handler.cameraFrustumLayer.removeEntity(this._cameraFrustumEntity);
}
}
}
public get showFootprint(): boolean {
return this._showFootprint;
}
public set showFootprint(showFootprint: boolean) {
this._showFootprint = showFootprint;
if (showFootprint && !this._cameraFootprintEntity) {
this._cameraFootprintEntity = this._createCameraFootprintEntity();
}
if (this._handler && this._handlerIndex !== -1 && this._cameraFootprintEntity) {
if (showFootprint) {
this._handler.cameraFootprintLayer.add(this._cameraFootprintEntity);
} else {
this._handler.cameraFootprintLayer.removeEntity(this._cameraFootprintEntity);
}
}
}
public get texelScale(): number {
const height = Math.max(this.height, 1);
if (this.camera.isOrthographic) {
const frustum = this.camera.frustums[0];
return Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom) / height;
}
return (2.0 * Math.tan(this.camera.verticalViewAngle * RADIANS_HALF)) / height;
}
public get isOrthographic(): boolean {
return this._initialized ? this.camera.isOrthographic : this._isOrthographic;
}
public set isOrthographic(isOrthographic: boolean) {
this._isOrthographic = isOrthographic;
this._forceOwnQuadTreeStrategyPass = true;
if (this._initialized) {
if (isOrthographic) {
this.camera.focusDistance = this._focusDistance;
}
this.camera.isOrthographic = isOrthographic;
}
}
public get focusDistance(): number {
return this._initialized ? this.camera.focusDistance : this._focusDistance;
}
public set focusDistance(focusDistance: number) {
if (!Number.isFinite(focusDistance) || focusDistance <= 0) return;
this._focusDistance = focusDistance;
this._forceOwnQuadTreeStrategyPass = true;
if (this._initialized) {
this.camera.focusDistance = focusDistance;
}
}
public get cameraFootprintEntity(): Entity | null {
return this._cameraFootprintEntity;
}
public get cameraFrustumEntity(): Entity | null {
return this._cameraFrustumEntity;
}
public get frustumLength(): number {
return this._frustumLength;
}
public set frustumLength(length: number) {
this._frustumLength = length > 0 ? length : DEFAULT_CAMERA_FRUSTUM_LENGTH;
}
public get frustumColor(): Vec4 | NumberArray4 | string {
return this._frustumColor;
}
public set frustumColor(color: Vec4 | NumberArray4 | string) {
this._frustumColor = color;
const geoObject = this._cameraFrustumEntity?.geoObject;
if (!geoObject) return;
if (typeof color === "string") {
geoObject.setColorHTML(color);
} else {
const c = color instanceof Vec4 ? color : new Vec4(color[0], color[1], color[2], color[3]);
geoObject.setColor(c.x, c.y, c.z, c.w);
}
}
public get frustumScale(): Vec3 {
return Object3d.getFrustumScaleByCameraAngles(
this._frustumLength,
this.camera.horizontalViewAngle,
this.camera.verticalViewAngle
);
}
public init(planet: Planet, renderer: Renderer): void {
if (this._initialized) return;
this._planet = planet;
this._renderer = renderer;
this.camera = this._createCamera(planet);
this.framebuffer = this._createFramebuffer(renderer);
this.framebuffer.init();
this.quadTreeStrategy = this._createQuadTreeStrategy(planet, this.camera as PlanetCamera);
this._lastPlanetHeightFactor = planet._heightFactor;
this._forceOwnQuadTreeStrategyPass = true;
this._initialized = true;
}
public destroy(): void {
if (!this._initialized) return;
this.quadTreeStrategy.clear();
this.framebuffer.destroy();
this._planet = null;
this._renderer = null;
this._initialized = false;
}
public frame(): void {
if (!this.enabled || !this._initialized || !this._renderer || !this._planet) return;
const framebuffer = this.framebuffer;
const gl = framebuffer.handler.gl!;
const mainCam = this._renderer.activeCamera;
const depthCamera = this.camera as PlanetCamera;
this.prepareFrame();
framebuffer.activate();
this._renderer.applyDepthForCamera(depthCamera);
const quadTreeStrategy = this._getQuadTreeStrategy(depthCamera);
gl.clearColor(1.0, 1.0, 1.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.disable(gl.BLEND);
this._segmentsPass(depthCamera, quadTreeStrategy);
this._geoObjectsPass(depthCamera);
gl.enable(gl.BLEND);
framebuffer.deactivate();
this.renderFootprint();
if (VARIANCE_SHADOW_ENABLED) {
this.shadowMap?.blur();
}
this._renderer.applyDepthForCamera(mainCam);
this.finishFrame();
}
public prepareFrame(): void {
this._syncPlanetHeightFactor();
this._prepareOrthographicProjection();
this._prepareCameraEntitySync();
}
protected _prepareCameraEntitySync(): void {
const cameraFrustumEntity = this._cameraFrustumEntity;
if (!this._showFrustum || !cameraFrustumEntity) {
return;
}
const cam = this.camera;
const cameraFrustumEntityPos = cameraFrustumEntity.getAbsoluteCartesian();
const cameraFrustumEntityPitch = cameraFrustumEntity.getPitch();
const cameraFrustumEntityYaw = cameraFrustumEntity.getYaw();
const cameraFrustumEntityRoll = cameraFrustumEntity.getRoll();
let cameraUpdated = false;
if (this._prevCameraPos.equal(cam.eye) && !this._prevCameraPos.equal(cameraFrustumEntityPos)) {
cam.eye.copy(cameraFrustumEntityPos);
cameraUpdated = true;
}
const cameraPitch = cam.getPitch();
const cameraYaw = cam.getYaw();
const cameraRoll = cam.getRoll();
if (
this._prevCameraPitch === cameraPitch &&
this._prevCameraYaw === cameraYaw &&
this._prevCameraRoll === cameraRoll &&
(this._prevCameraFrustumEntityPitch !== cameraFrustumEntityPitch ||
this._prevCameraFrustumEntityYaw !== cameraFrustumEntityYaw ||
this._prevCameraFrustumEntityRoll !== cameraFrustumEntityRoll)
) {
cam.setPitchYawRoll(cameraFrustumEntityPitch, cameraFrustumEntityYaw, cameraFrustumEntityRoll);
cameraUpdated = true;
}
if (
cameraUpdated ||
!this._prevCameraPos.equal(cam.eye) ||
this._prevCameraPitch !== cam.getPitch() ||
this._prevCameraYaw !== cam.getYaw() ||
this._prevCameraRoll !== cam.getRoll()
) {
cam.update();
}
}
protected _finishCameraEntitySync(): void {
const cameraFrustumEntity = this._cameraFrustumEntity;
if (!this._showFrustum || !cameraFrustumEntity) return;
const cam = this.camera;
cameraFrustumEntity.setScale3v(this.frustumScale);
cameraFrustumEntity.setCartesian3v(cam.eye);
cameraFrustumEntity.setAbsolutePitch(cam.getPitch());
cameraFrustumEntity.setAbsoluteYaw(cam.getYaw());
cameraFrustumEntity.setAbsoluteRoll(cam.getRoll());
this._prevCameraPitch = cam.getPitch();
this._prevCameraYaw = cam.getYaw();
this._prevCameraRoll = cam.getRoll();
this._prevCameraPos.copy(cam.eye);
this._prevCameraFrustumEntityPos.copy(cameraFrustumEntity.getAbsoluteCartesian());
this._prevCameraFrustumEntityPitch = cameraFrustumEntity.getPitch();
this._prevCameraFrustumEntityYaw = cameraFrustumEntity.getYaw();
this._prevCameraFrustumEntityRoll = cameraFrustumEntity.getRoll();
if (this._planet && this._planet.ellipsoid) {
cameraFrustumEntity._lonLat.copy(
this._planet.ellipsoid.cartesianToLonLat(cameraFrustumEntity.getAbsoluteCartesian())
);
}
}
public finishFrame(): void {
this._finishCameraEntitySync();
}
public renderFootprint(): void {
const cameraFootprintEntity = this._cameraFootprintEntity;
if (!this._showFootprint || !cameraFootprintEntity) return;
const framebuffer = this.framebuffer;
framebuffer.readPixelBuffers();
const perimeterData = this._collectPerimeterLonLats(framebuffer.width, framebuffer.height);
const segments = perimeterData.segments;
const nextPointCounts = segments.map((segment) => segment.length);
const nextClosedState = perimeterData.isClosed;
const isSameTopology =
this._cameraFootprintSegmentPointCounts !== null &&
this._cameraFootprintClosedState === nextClosedState &&
this._cameraFootprintSegmentPointCounts.length === nextPointCounts.length &&
this._cameraFootprintSegmentPointCounts.every((count, index) => count === nextPointCounts[index]);
const polyline = cameraFootprintEntity.polyline!;
if (polyline.isClosed !== nextClosedState) {
polyline.isClosed = nextClosedState;
}
if (isSameTopology) {
polyline.setPathLonLatFast(segments);
} else {
polyline.setPathLonLat(segments);
}
this._cameraFootprintSegmentPointCounts = nextPointCounts;
this._cameraFootprintClosedState = nextClosedState;
}
public getCamera(): Camera {
return this.camera;
}
public getFramebuffer(): Framebuffer {
return this.framebuffer;
}
public getDepthTexture(): WebGLTexture {
return this.framebuffer.textures[0]!;
}
protected _syncPlanetHeightFactor(): void {
const planet = this._planet;
if (!planet || this._lastPlanetHeightFactor === planet._heightFactor) return;
this._lastPlanetHeightFactor = planet._heightFactor;
this.quadTreeStrategy.destroyBranches();
this.quadTreeStrategy.clearRenderedNodes();
this._forceOwnQuadTreeStrategyPass = true;
}
protected _prepareOrthographicProjection(): void {
if (!this.camera.isMoving) return;
if (this._snapOrthographicProjectionToTexelGrid()) {
this.camera.update();
}
}
/**
* Quantizes the orthographic extent and keeps the previous size while it still fits to prevent jitter.
*/
protected _quantizeOrthoTexelSize(extent: number, resolution: number, prevTexelSize: number): number {
const texelSize = extent / resolution;
if (!(texelSize > 0.0)) {
return 0.0;
}
if (
prevTexelSize > 0.0 &&
texelSize <= prevTexelSize &&
texelSize > prevTexelSize / ORTHO_TEXEL_QUANTIZATION_RATIO
) {
return prevTexelSize;
}
const step = Math.ceil(Math.log2(texelSize) * ORTHO_TEXEL_QUANTIZATION_STEPS);
return Math.pow(2.0, step / ORTHO_TEXEL_QUANTIZATION_STEPS);
}
protected _snapOrthographicProjectionToTexelGrid(): boolean {
const cam = this.camera;
if (!cam.isOrthographic) {
return false;
}
const frustum = cam.frustums[0];
const framebuffer = this.framebuffer;
const texelSizeX = this._quantizeOrthoTexelSize(
frustum.right - frustum.left,
framebuffer.width,
this._orthoTexelSizeX
);
const texelSizeY = this._quantizeOrthoTexelSize(
frustum.top - frustum.bottom,
framebuffer.height,
this._orthoTexelSizeY
);
if (texelSizeX <= 0.0 || texelSizeY <= 0.0) {
return false;
}
this._orthoTexelSizeX = texelSizeX;
this._orthoTexelSizeY = texelSizeY;
const width = texelSizeX * framebuffer.width;
const height = texelSizeY * framebuffer.height;
const centerX = (frustum.left + frustum.right) * 0.5;
const centerY = (frustum.bottom + frustum.top) * 0.5;
const eyeX = cam.eye.dot(cam._r);
const eyeY = cam.eye.dot(cam._u);
const snappedMinX = Math.floor((eyeX + centerX - width * 0.5) / texelSizeX) * texelSizeX;
const snappedMinY = Math.floor((eyeY + centerY - height * 0.5) / texelSizeY) * texelSizeY;
const left = snappedMinX - eyeX;
const right = left + width;
const bottom = snappedMinY - eyeY;
const top = bottom + height;
if (
Math.abs(left - frustum.left) <= TEXEL_SNAP_EPSILON &&
Math.abs(right - frustum.right) <= TEXEL_SNAP_EPSILON &&
Math.abs(bottom - frustum.bottom) <= TEXEL_SNAP_EPSILON &&
Math.abs(top - frustum.top) <= TEXEL_SNAP_EPSILON
) {
return false;
}
frustum.setOrthoBounds(left, right, bottom, top);
return true;
}
public getCartesianFromPixelTerrain(x: number, y: number): Vec3 | undefined {
const distance = getDistanceFromPixel(x, y, this.camera, this.framebuffer);
if (distance === 0) return;
const nx = x / this.framebuffer.width;
const ny = (this.framebuffer.height - y) / this.framebuffer.height;
if (this.camera.isOrthographic) {
const position = new Vec3();
this.camera.unproject(nx * this.camera.width, (1 - ny) * this.camera.height, distance, position);
return position;
}
const direction = this.camera.unproject(nx * this.camera.width, (1 - ny) * this.camera.height);
return direction.scaleTo(distance).addA(this.camera.eye);
}
public getLonLatFromPixelTerrain(x: number, y: number): LonLat | undefined {
const coords = this.getCartesianFromPixelTerrain(x, y);
return coords && this._planet ? this._planet.ellipsoid.cartesianToLonLat(coords) : undefined;
}
protected _createCameraFootprintEntity(): Entity {
return new Entity({
polyline: {
color: "rgba(255,0,0,0.82)",
thickness: 5.0,
isClosed: true
}
});
}
protected _createCameraFrustumEntity(): Entity {
return new Entity({
visibility: true,
scale: new Vec3(1, 1, 1),
geoObject: {
tag: "depth-camera-frustum",
color: this._frustumColor,
object3d: cameraFrustumObj
},
properties: {
depthCamera: this
}
});
}
protected _createCamera(planet: Planet): Camera {
const camera = new PlanetCamera(planet, {
frustums: [[this.near, this.far]],
width: this.width,
height: this.height,
viewAngle: this.verticalViewAngle,
isOrthographic: this._isOrthographic,
focusDistance: this._focusDistance,
reverseDepth: false
});
if (this.horizontalViewAngle != null) {
camera.setHorizontalViewAngle(this.horizontalViewAngle);
}
return camera;
}
protected _createFramebuffer(renderer: Renderer): Framebuffer {
return new Framebuffer(renderer.handler, {
width: this.width,
height: this.height,
depthComponent: "DEPTH_COMPONENT32F",
targets: [
{
internalFormat: "RGBA32F",
attachment: "COLOR_ATTACHMENT",
readAsync: true
}
],
useDepth: true
});
}
protected _createQuadTreeStrategy(planet: Planet, camera: PlanetCamera): QuadTreeStrategy {
const quadTreeStrategy = new planet.quadTreeStrategyPrototype({
planet,
maxEqualZoomAltitude: planet.quadTreeStrategy.maxEqualZoomAltitude,
minEqualZoomAltitude: planet.quadTreeStrategy.minEqualZoomAltitude,
minEqualZoomCameraSlope: planet.quadTreeStrategy.minEqualZoomCameraSlope,
transitionOpacityEnabled: false
});
quadTreeStrategy.init(camera);
quadTreeStrategy.lodCamera = planet.camera;
quadTreeStrategy.preRender();
quadTreeStrategy.clearRenderedNodes();
quadTreeStrategy.preLoad();
return quadTreeStrategy;
}
protected _getQuadTreeStrategy(depthCamera: PlanetCamera): QuadTreeStrategy {
const planet = this._planet!;
const mainCam = this._renderer!.activeCamera;
//
// @test
//
//return planet.quadTreeStrategy;
const sun = planet.sun;
let sunHorizonAngle = 0;
if (sun) {
const up = planet.ellipsoid.getSurfaceNormal3v(depthCamera.eye);
const toSun = sun.getPosition().sub(depthCamera.eye).normalize();
sunHorizonAngle = Math.asin(Math.max(-1.0, Math.min(1.0, up.dot(toSun)))) * DEGREES;
//console.log(`Sun horizon angle: ${sunHorizonAngle.toFixed(2)}°`);
}
if (planet.camera.getHeight() > 135818 /* || sunHorizonAngle > 22*/) {
return planet.quadTreeStrategy;
}
if (
//!depthCamera.isOrthographic &&
!this._forceOwnQuadTreeStrategyPass &&
mainCam.containsPoint(depthCamera.eye)
) {
return planet.quadTreeStrategy;
}
this._forceOwnQuadTreeStrategyPass = false;
depthCamera.updateCameraSlope();
const quadTreeStrategy = this.quadTreeStrategy;
quadTreeStrategy.maxZoomLimit = planet.quadTreeStrategy.maxCurrZoom;
quadTreeStrategy.disableNeighbors = true;
quadTreeStrategy.collectRenderNodes(depthCamera);
return quadTreeStrategy;
}
protected _segmentsPass(camera: PlanetCamera, quadTreeStrategy: QuadTreeStrategy): void {
const h = this._renderer!.handler;
const gl = h.gl!;
const planet = this._planet;
if (!planet) return;
let checkSlope = camera.isOrthographic && camera.slope < RENDER_SKIRTS_SLOPE;
h.programs.depth_camera.activate();
const sh = h.programs.depth_camera;
const shu = sh.uniforms;
gl.uniformMatrix4fv(shu.viewMatrix, false, camera.getViewMatrix());
gl.uniformMatrix4fv(shu.projectionMatrix, false, camera.getProjectionMatrix());
if (this.enableSegmentFaceCulling) {
gl.enable(gl.CULL_FACE);
} else if (checkSlope) {
gl.disable(gl.CULL_FACE);
}
//const isEq = planet.terrain!.equalizeVertices;
const baseLayerSlice = planet.visibleTileLayers.length ? [planet.visibleTileLayers[0]] : undefined;
const rn = quadTreeStrategy._renderedNodesInFrustum[camera.getCurrentFrustum()];
//const renderSkirts = this.enableSegmentSkirts && checkSlope;
const renderSkirts = true;
let i = rn.length;
while (i--) {
const s = rn[i].segment;
if (!s.node) continue;
if (s._transitionOpacity >= 1) {
//isEq && s.equalize();
s.readyToEngage && s.engage();
s.ensureIndexBuffer();
s.updateRTCEyePosition(camera);
s.depthRendering(sh, baseLayerSlice, renderSkirts);
}
}
for (let j = 0; j < quadTreeStrategy._fadingOpaqueSegments.length; ++j) {
const s = quadTreeStrategy._fadingOpaqueSegments[j];
if (!s.node) continue;
//isEq && s.equalize();
s.readyToEngage && s.engage();
s.ensureIndexBuffer();
s.updateRTCEyePosition(camera);
s.depthRendering(sh, baseLayerSlice, renderSkirts);
}
gl.enable(gl.CULL_FACE);
}
protected _geoObjectsPass(camera: PlanetCamera): void {
const layers = this._planet!.layers;
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
if (!(layer instanceof Vector) || !layer.getVisibility()) {
continue;
}
if (this.excludeLayers.includes(layer)) {
continue;
}
layer._geoObjectEntityCollection.geoObjectHandler.drawDepthCameraPass(camera);
}
}
protected _collectPerimeterLonLats(width: number, height: number): IPerimeterSegmentsData {
const segments: LonLat[][] = [];
let currentSegment: LonLat[] | null = null;
let hasMissingData = false;
let firstPointHasData = false;
let lastPointHasData = false;
let sampledPointIndex = 0;
const addPoint = (x: number, y: number) => {
const lonLat = this.getLonLatFromPixelTerrain(x, y);
const hasData = !!lonLat;
if (sampledPointIndex === 0) {
firstPointHasData = hasData;
}
lastPointHasData = hasData;
sampledPointIndex++;
if (lonLat) {
if (!currentSegment) {
currentSegment = [];
segments.push(currentSegment);
}
currentSegment.push(new LonLat(lonLat.lon, lonLat.lat, lonLat.height));
} else {
hasMissingData = true;
currentSegment = null;
}
};
for (let x = 1; x < width; x += PERIMETER_STEP_PX) addPoint(x, 1);
for (let y = 2; y < height; y += PERIMETER_STEP_PX) addPoint(width - 1, y);
for (let x = width - 2; x >= 1; x -= PERIMETER_STEP_PX) addPoint(x, height - 1);
for (let y = height - 2; y >= 2; y -= PERIMETER_STEP_PX) addPoint(1, y);
if (segments.length > 1 && firstPointHasData && lastPointHasData) {
const firstSegment = segments[0];
const lastSegment = segments[segments.length - 1];
lastSegment.push(...firstSegment);
segments[0] = lastSegment;
segments.pop();
}
const normalizedSegments = segments.filter((segment) => segment.length > 1);
const isClosed = !hasMissingData && normalizedSegments.length === 1;
return {
segments: normalizedSegments,
isClosed
};
}
}