UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

167 lines (157 loc) 6.06 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { Box3, BufferAttribute, BufferGeometry, Vector2, Vector3 } from 'three'; import { getGeometryMemoryUsage } from '../../core/MemoryUsage'; import { getGridBuffers } from './GridBuilder'; const tmpVec2 = new Vector2(); const tmpVec3 = new Vector3(); const tmpNormal = new Vector3(); const tmpNW = new Vector3(); const tmpNE = new Vector3(); const tmpSW = new Vector3(); const tmpSE = new Vector3(); var Usage = /*#__PURE__*/function (Usage) { Usage[Usage["Rendering"] = 0] = "Rendering"; Usage[Usage["Raycasting"] = 1] = "Raycasting"; return Usage; }(Usage || {}); function copySkirtValues(array, segments) { const rowSize = segments + 1; const end = rowSize * rowSize; array.copyItem(0, end + 0); array.copyItem(rowSize, end + 1); array.copyItem(end - rowSize, end + 2); array.copyItem(end, end + 3); } export class EllipsoidTileGeometry extends BufferGeometry { isMemoryUsage = true; _segments = 32; _heightMap = null; _skirtDepth = null; get vertexCount() { return this.getAttribute('position').count; } get segments() { return this._segments; } set segments(v) { if (this._segments !== v) { this._segments = v; this.buildBuffers(this, Usage.Rendering); this.buildBuffers(this._raycastGeometry, Usage.Raycasting); } } get origin() { return this._origin; } get raycastGeometry() { return this._raycastGeometry; } constructor(params) { super(); this._segments = params.segments; this._extent = params.extent; this._skirtDepth = params.skirtDepth; this._ellipsoid = params.ellipsoid; this._origin = this._ellipsoid.toCartesian(this._extent.maxY, this._extent.minX, 0); if (!this._extent.crs.isEpsg(4326)) { throw new Error(`invalid CRS. Expected EPSG:4326, got: ${this._extent.crs.id}`); } this._raycastGeometry = new BufferGeometry(); this.buildBuffers(this, Usage.Rendering); this.buildBuffers(this._raycastGeometry, Usage.Raycasting); } resetHeights() { this.buildBuffers(this.raycastGeometry, Usage.Raycasting); } applyHeightMap(heightMap) { this._heightMap = heightMap; return this.buildBuffers(this.raycastGeometry, Usage.Raycasting); } getMemoryUsage(context) { getGeometryMemoryUsage(context, this); getGeometryMemoryUsage(context, this.raycastGeometry); } buildBuffers(geometry, usage) { this.dispose(); const rowVertices = this._segments + 1; const dims = this._extent.dimensions(tmpVec2); const width = dims.width; const height = dims.height; const west = this._extent.minX; const north = this._extent.maxY; const south = this._extent.minY; const east = this._extent.maxX; // Positions are relative to the origin of the tile const origin = this._origin; // A shortcut to get ready to use buffers const buffers = getGridBuffers(this.segments, this._skirtDepth != null); const heightMap = this._heightMap; /** * Returns the elevation by sampling the heightmap at the (u, v) coordinate. * Note: the sampling does not perform any interpolation. */ function getElevation(u, v) { if (!heightMap) { return 0; } return heightMap.getValue(u, v, true) ?? 0; } let min = +Infinity; let max = -Infinity; const boundingBox = new Box3().makeEmpty(); // Those buffers need to be cloned because they are unique per-tile const positionBuffer = buffers.positionBuffer.clone(); const normalBuffer = buffers.normalBuffer.clone(); // But these one can be reused as they are never modified const uvBuffer = buffers.uvBuffer; const indexBuffer = buffers.indexBuffer; for (let j = 0; j < rowVertices; j++) { for (let i = 0; i < rowVertices; i++) { const idx = j * rowVertices + i; const u = i / this.segments; const v = j / this.segments; const altitude = usage === Usage.Raycasting ? getElevation(u, 1 - v) : 0; min = Math.min(min, altitude); max = Math.max(max, altitude); const cartesian = this._ellipsoid.toCartesian(north - v * height, west + u * width, altitude, tmpVec3); const normal = this._ellipsoid.getNormalFromCartesian(cartesian, tmpNormal); const pos = cartesian.sub(origin); // Note that the bounding box ignores the skirts, which are purely // graphical and should not count towards the actual volume of the tile. boundingBox.expandByPoint(pos); positionBuffer.set(idx, pos.x, pos.y, pos.z); normalBuffer.set(idx, normal.x, normal.y, normal.z); } } if (this._skirtDepth != null) { const skirtDepth = this._skirtDepth; const skirtStart = rowVertices * rowVertices; const nw = this._ellipsoid.toCartesian(north, west, skirtDepth, tmpNW).sub(origin); const ne = this._ellipsoid.toCartesian(north, east, skirtDepth, tmpNE).sub(origin); const sw = this._ellipsoid.toCartesian(south, west, skirtDepth, tmpSW).sub(origin); const se = this._ellipsoid.toCartesian(south, east, skirtDepth, tmpSE).sub(origin); positionBuffer.set(skirtStart + 0, nw.x, nw.y, nw.z); positionBuffer.set(skirtStart + 1, ne.x, ne.y, ne.z); positionBuffer.set(skirtStart + 2, sw.x, sw.y, sw.z); positionBuffer.set(skirtStart + 3, se.x, se.y, se.z); // Skirt normals are the same as their non-skirt counterpart copySkirtValues(normalBuffer, this.segments); } // Per-tile buffers geometry.setAttribute('position', new BufferAttribute(positionBuffer.array, 3)); geometry.setAttribute('normal', new BufferAttribute(normalBuffer.array, 3)); // Shared buffers geometry.setAttribute('uv', new BufferAttribute(uvBuffer.array, 2)); geometry.setIndex(new BufferAttribute(indexBuffer, 1)); this.boundingBox = boundingBox; return { min, max }; } } export default EllipsoidTileGeometry;