From 08ec5ef5f54debf39772dfd77688275fbbe17bfa Mon Sep 17 00:00:00 2001 From: Stanislav Lorents Date: Fri, 9 May 2025 17:22:23 +1000 Subject: [PATCH 1/3] chore: auto-format --- src/draping.ts | 207 +++++++++++++++++---------------- src/gradients.ts | 9 +- src/index.ts | 295 ++++++++++++++++++++++------------------------- src/types.ts | 76 ++++++------ src/util.ts | 49 ++++---- 5 files changed, 307 insertions(+), 329 deletions(-) diff --git a/src/draping.ts b/src/draping.ts index 6f3753de..f6df4fba 100644 --- a/src/draping.ts +++ b/src/draping.ts @@ -4,128 +4,131 @@ * https://ieeexplore.ieee.org/abstract/document/8811991 */ import { - Vector2, - Scene, - Object3D, - ShaderMaterial, - MeshBasicMaterial, - WebGLRenderTarget, - NearestFilter, - WebGLRenderer, - DoubleSide, - FloatType, - RGBAFormat, - NormalBlending, - Blending -} from 'three' + Vector2, + Scene, + Object3D, + ShaderMaterial, + MeshBasicMaterial, + WebGLRenderTarget, + NearestFilter, + WebGLRenderer, + DoubleSide, + FloatType, + RGBAFormat, + NormalBlending, + Blending, +} from 'three'; import type { Viewport } from './types'; let target: WebGLRenderTarget | null = null; let targetRenderer: WebGLRenderer | null = null; let targetScene: Scene | null = null; -let targetModel: Object3D | null = null; +let targetModel: Object3D | null = null; interface DrapingShaderOptions { - /** Minimum terrain height when searching for a matching vertex to the GEOJson overlay. Default: `0` */ - minHeight: number, - /** Maximum terrain height when searching for a matching vertex to the GEOJson overlay. Default: `300` */ - maxHeight: number, - /** Number of samples to average around the render target cordinate when draping the GEOJson overlay. Default: `4` */ - samples: number, - /** Number of pixels to step toward each sample when averaging the samples. Default: 4.0 */ - sampleStep: number, - /** Opacity of the draped colors. Default: `0.5` */ - opacity: number, - /** Blending algoritum of the draped colors. Default: `THREE.NormalBlending` */ - blendingType: Blending + /** Minimum terrain height when searching for a matching vertex to the GEOJson overlay. Default: `0` */ + minHeight: number; + /** Maximum terrain height when searching for a matching vertex to the GEOJson overlay. Default: `300` */ + maxHeight: number; + /** Number of samples to average around the render target cordinate when draping the GEOJson overlay. Default: `4` */ + samples: number; + /** Number of pixels to step toward each sample when averaging the samples. Default: 4.0 */ + sampleStep: number; + /** Opacity of the draped colors. Default: `0.5` */ + opacity: number; + /** Blending algoritum of the draped colors. Default: `THREE.NormalBlending` */ + blendingType: Blending; } const defaultShaderOptions: DrapingShaderOptions = { - minHeight: 0, - maxHeight: 300, - samples: 4, - sampleStep: 4.0, - opacity: 0.5, - blendingType: NormalBlending -} + minHeight: 0, + maxHeight: 300, + samples: 4, + sampleStep: 4.0, + opacity: 0.5, + blendingType: NormalBlending, +}; function setup( - viewport: Viewport, - model: Object3D, - renderer: WebGLRenderer, - shaderOptions: DrapingShaderOptions = defaultShaderOptions + viewport: Viewport, + model: Object3D, + renderer: WebGLRenderer, + shaderOptions: DrapingShaderOptions = defaultShaderOptions, ) { - if ( target ) { - target.dispose(); - } - - if (!targetRenderer) { - targetRenderer = renderer; - } - - const options = { ...defaultShaderOptions, ...shaderOptions }; - - target = new WebGLRenderTarget(viewport.width * viewport.devicePixelRatio, viewport.height * viewport.devicePixelRatio); - target.texture.minFilter = NearestFilter; - target.texture.magFilter = NearestFilter; - target.stencilBuffer = false; - target.texture.format = RGBAFormat; - target.texture.type = FloatType; - - targetRenderer.setPixelRatio(devicePixelRatio); - targetRenderer.setSize(viewport.width, viewport.height); - targetRenderer.setRenderTarget(target); - - targetScene = new Scene(); - targetScene.overrideMaterial = positionShaderMaterial; - targetModel = model; - - drapingMaterial.uniforms.tPosition.value = target.texture; - drapingMaterial.uniforms.minHeight.value = options.minHeight; - drapingMaterial.uniforms.maxHeight.value = options.maxHeight; - drapingMaterial.uniforms.samples.value = options.samples; - drapingMaterial.uniforms.sampleStep.value = options.sampleStep; - drapingMaterial.uniforms.opacity.value = options.opacity; - drapingMaterial.blending = options.blendingType; + if (target) { + target.dispose(); + } + + if (!targetRenderer) { + targetRenderer = renderer; + } + + const options = { ...defaultShaderOptions, ...shaderOptions }; + + target = new WebGLRenderTarget( + viewport.width * viewport.devicePixelRatio, + viewport.height * viewport.devicePixelRatio, + ); + target.texture.minFilter = NearestFilter; + target.texture.magFilter = NearestFilter; + target.stencilBuffer = false; + target.texture.format = RGBAFormat; + target.texture.type = FloatType; + + targetRenderer.setPixelRatio(devicePixelRatio); + targetRenderer.setSize(viewport.width, viewport.height); + targetRenderer.setRenderTarget(target); + + targetScene = new Scene(); + targetScene.overrideMaterial = positionShaderMaterial; + targetModel = model; + + drapingMaterial.uniforms.tPosition.value = target.texture; + drapingMaterial.uniforms.minHeight.value = options.minHeight; + drapingMaterial.uniforms.maxHeight.value = options.maxHeight; + drapingMaterial.uniforms.samples.value = options.samples; + drapingMaterial.uniforms.sampleStep.value = options.sampleStep; + drapingMaterial.uniforms.opacity.value = options.opacity; + drapingMaterial.blending = options.blendingType; } function resizeRenderTarget(viewport: Viewport) { - target.setSize(viewport.width * viewport.devicePixelRatio, viewport.height * viewport.devicePixelRatio); - targetRenderer.setPixelRatio(devicePixelRatio); - targetRenderer.setSize(viewport.width, viewport.height); + target.setSize(viewport.width * viewport.devicePixelRatio, viewport.height * viewport.devicePixelRatio); + targetRenderer.setPixelRatio(devicePixelRatio); + targetRenderer.setSize(viewport.width, viewport.height); } function update(camera) { - if (targetRenderer) { - const oldParent = targetModel.parent; - targetScene.add(targetModel); - targetRenderer.setRenderTarget(target); - targetRenderer.render(targetScene, camera); - if (oldParent) { - oldParent.add(targetModel); - } - targetRenderer.setRenderTarget(null); + if (targetRenderer) { + const oldParent = targetModel.parent; + targetScene.add(targetModel); + targetRenderer.setRenderTarget(target); + targetRenderer.render(targetScene, camera); + if (oldParent) { + oldParent.add(targetModel); } + targetRenderer.setRenderTarget(null); + } } // For syntax highlighting -const glsl = (x:any) => x.toString(); +const glsl = (x: any) => x.toString(); const positionShaderMaterial = new ShaderMaterial({ - vertexShader: glsl` + vertexShader: glsl` varying vec3 vPosition; void main() { vPosition = (modelMatrix * vec4(position, 1.0)).xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, - fragmentShader: glsl` + fragmentShader: glsl` varying vec3 vPosition; void main() { gl_FragColor = vec4(vPosition, 1.0); } `, - side: DoubleSide + side: DoubleSide, }); const drapingVertexShader = glsl` @@ -212,21 +215,21 @@ const drapingFragmentShader = glsl` } `; -const drapingMaterial = new ShaderMaterial( { - vertexShader: drapingVertexShader, - fragmentShader: drapingFragmentShader, - uniforms: { - tPosition: { value: null }, - minHeight: { value: 0.0 }, - maxHeight: { value: 300.0 }, - opacity: { value: 0.5 }, - samples: { value: 4 }, - sampleStep: { value: 4.0 } - }, - vertexColors: true, - transparent: true, - depthTest: false, - blending: NormalBlending +const drapingMaterial = new ShaderMaterial({ + vertexShader: drapingVertexShader, + fragmentShader: drapingFragmentShader, + uniforms: { + tPosition: { value: null }, + minHeight: { value: 0.0 }, + maxHeight: { value: 300.0 }, + opacity: { value: 0.5 }, + samples: { value: 4 }, + sampleStep: { value: 4.0 }, + }, + vertexColors: true, + transparent: true, + depthTest: false, + blending: NormalBlending, }); -export { target, setup, resizeRenderTarget, update, drapingMaterial, DrapingShaderOptions } \ No newline at end of file +export { target, setup, resizeRenderTarget, update, drapingMaterial, DrapingShaderOptions }; diff --git a/src/gradients.ts b/src/gradients.ts index 5dbe46a8..8cbe78da 100644 --- a/src/gradients.ts +++ b/src/gradients.ts @@ -1,7 +1,7 @@ -import { Color } from 'three' +import { Color } from 'three'; type Gradient = Array<[number, Color]>; -const Gradients: {[key: string] : Gradient } = { +const Gradients: { [key: string]: Gradient } = { // From chroma spectral http://gka.github.io/chroma.js/ SPECTRAL: [ [0, new Color(0.3686, 0.3098, 0.6353)], @@ -108,7 +108,6 @@ const Gradients: {[key: string] : Gradient } = { [0.04, new Color(1, 1, 1)], [1.0, new Color(1, 1, 1)], ], -} - -export {Gradients, Gradient} +}; +export { Gradients, Gradient }; diff --git a/src/index.ts b/src/index.ts index d0f5ca9b..6172676c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import { CesiumIonLoader, Tiles3DLoader } from '@loaders.gl/3d-tiles'; import { _GeoJSONLoader } from '@loaders.gl/json'; import { Tileset3D, TILE_TYPE, TILE_CONTENT_STATE } from '@loaders.gl/tiles'; import { CullingVolume, Plane } from '@math.gl/culling'; -import { _PerspectiveFrustum as PerspectiveFrustum} from '@math.gl/culling'; +import { _PerspectiveFrustum as PerspectiveFrustum } from '@math.gl/culling'; import { Matrix4 as MathGLMatrix4, toRadians } from '@math.gl/core'; import { Ellipsoid } from '@math.gl/geospatial'; import * as Util from './util'; @@ -30,7 +30,7 @@ import { Euler, Quaternion, NormalBlending, - WebGLRenderer + WebGLRenderer, } from 'three'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; @@ -41,15 +41,15 @@ import { Gradients } from './gradients'; import { PointCloudFS, PointCloudVS } from './shaders'; -import type { - LoaderProps, - LoaderOptions, - Runtime, - GeoCoord, - GeoJSONLoaderProps, - FeatureToColor, +import type { + LoaderProps, + LoaderOptions, + Runtime, + GeoCoord, + GeoJSONLoaderProps, + FeatureToColor, DrapingShaderOptions, - Viewport + Viewport, } from './types'; import { PointCloudColoring, Shading } from './types'; import { BinaryFeatureCollection, FeatureCollection } from '@loaders.gl/schema'; @@ -68,7 +68,7 @@ const defaultOptions: LoaderOptions = { maximumScreenSpaceError: 16, memoryAdjustedScreenSpaceError: true, maximumMemoryUsage: 400, - memoryCacheOverflow : 128, + memoryCacheOverflow: 128, viewDistanceScale: 1.0, skipLevelOfDetail: false, resetTransform: false, @@ -86,20 +86,20 @@ const defaultOptions: LoaderOptions = { material: null, contentPostProcess: undefined, preloadTilesCount: null, - collectAttributions: false + collectAttributions: false, }; /** 3D Tiles Loader */ class Loader3DTiles { /** - * Loads a tileset of 3D Tiles according to the given {@link LoaderProps} - * @public - * - * @param props - Properties for this load call {@link LoaderProps}. - * @returns An object containing the 3D Model to be added to the scene - * and a runtime engine to be updated every frame. - */ - public static async load (props: LoaderProps): Promise<{ model: Object3D; runtime: Runtime }> { + * Loads a tileset of 3D Tiles according to the given {@link LoaderProps} + * @public + * + * @param props - Properties for this load call {@link LoaderProps}. + * @returns An object containing the 3D Model to be added to the scene + * and a runtime engine to be updated every frame. + */ + public static async load(props: LoaderProps): Promise<{ model: Object3D; runtime: Runtime }> { const options = { ...defaultOptions, ...props.options }; const { url } = props; @@ -108,7 +108,7 @@ class Loader3DTiles { const UPDATE_INTERVAL = options.updateInterval; const MAX_DEPTH_FOR_ORIENTATION = 5; - const loadersGLOptions: {[key: string]: unknown} = {}; + const loadersGLOptions: { [key: string]: unknown } = {}; if (options.cesiumIONToken) { loadersGLOptions['cesium-ion'] = { @@ -119,13 +119,12 @@ class Loader3DTiles { } if (options.googleApiKey) { - loadersGLOptions['fetch'] = { headers: { 'X-GOOG-API-KEY': options.googleApiKey} }; - if (!props.options.hasOwnProperty('collectAttributions')) { + loadersGLOptions['fetch'] = { headers: { 'X-GOOG-API-KEY': options.googleApiKey } }; + if (!props.options.hasOwnProperty('collectAttributions')) { options.collectAttributions = true; } } - if (props.loadingManager) { props.loadingManager.itemStart(url); } @@ -166,17 +165,16 @@ class Loader3DTiles { vertexShader: PointCloudVS, fragmentShader: PointCloudFS, transparent: options.transparent, - vertexColors: true + vertexColors: true, }); - + let gltfLoader = undefined; let ktx2Loader = undefined; let dracoLoader = undefined; if (options.gltfLoader) { gltfLoader = options.gltfLoader; - } - else { + } else { gltfLoader = new GLTFLoader(); if (options.basisTranscoderPath) { @@ -195,8 +193,8 @@ class Loader3DTiles { gltfLoader.setDRACOLoader(dracoLoader); } } - - const unlitMaterial = new MeshBasicMaterial({transparent: options.transparent}); + + const unlitMaterial = new MeshBasicMaterial({ transparent: options.transparent }); const tileOptions = { maximumMemoryUsage: options.maximumMemoryUsage, @@ -253,7 +251,7 @@ class Loader3DTiles { dataAttributions = collectAttributions(selectedTiles); } return selectedTiles; - } + }, }; const tileset = new Tileset3D(tilesetJson, { ...tileOptions, @@ -265,7 +263,7 @@ class Loader3DTiles { loadImages: false, }, '3d-tiles': { - loadGLTF: false + loadGLTF: false, }, }, }); @@ -282,15 +280,17 @@ class Loader3DTiles { if (tileset.root.header.boundingVolume.region) { // TODO: Handle region type bounding volumes // https://github.com/visgl/loaders.gl/issues/1994 - console.warn("Cannot apply a model matrix to bounding volumes of type region. Tileset stays in original geo-coordinates.") + console.warn( + 'Cannot apply a model matrix to bounding volumes of type region. Tileset stays in original geo-coordinates.', + ); } tileTransform.setPosition( tileset.root.boundingVolume.center[0], tileset.root.boundingVolume.center[1], - tileset.root.boundingVolume.center[2] - ) + tileset.root.boundingVolume.center[2], + ); } else { - console.warn("Bounding volume not found, no transformations applied") + console.warn('Bounding volume not found, no transformations applied'); } if (options.debug) { @@ -306,7 +306,7 @@ class Loader3DTiles { pointcloudUniforms.rootNormal.value.copy(new Vector3(0, 0, 1).normalize()); // Extra stats - tileset.stats.get('Loader concurrency').count = options.maxConcurrency + tileset.stats.get('Loader concurrency').count = options.maxConcurrency; tileset.stats.get('Maximum mem usage').count = options.maximumMemoryUsage; let timer = 0; @@ -318,7 +318,7 @@ class Loader3DTiles { let sseDenominator = null; root.updateMatrixWorld(true); - const lastRootTransform:Matrix4 = new Matrix4().copy(root.matrixWorld) + const lastRootTransform: Matrix4 = new Matrix4().copy(root.matrixWorld); const rootTransformInverse = new Matrix4().copy(lastRootTransform).invert(); if (options.resetTransform) { @@ -336,28 +336,24 @@ class Loader3DTiles { } const halfAxes = tile.boundingVolume.halfAxes; const orientationMatrix = new Matrix4() - .extractRotation(Util.getMatrix4FromHalfAxes(halfAxes)) - .premultiply(new Matrix4().extractRotation(rootTransformInverse)); + .extractRotation(Util.getMatrix4FromHalfAxes(halfAxes)) + .premultiply(new Matrix4().extractRotation(rootTransformInverse)); const rotation = new Euler().setFromRotationMatrix(orientationMatrix); if (!rotation.equals(new Euler())) { orientationDetected = true; - const pos = new Vector3( - tileTransform.elements[12], - tileTransform.elements[13], - tileTransform.elements[14]) - ; + const pos = new Vector3(tileTransform.elements[12], tileTransform.elements[13], tileTransform.elements[14]); tileTransform.extractRotation(orientationMatrix); tileTransform.setPosition(pos); - } + } updateTransform(); } function updateTransform() { // Reset the current model matrix and apply our own transformation threeMat.copy(lastRootTransform); - + if (options.resetTransform) { threeMat.multiply(new Matrix4().copy(tileTransform).invert()); } @@ -437,21 +433,14 @@ class Loader3DTiles { const tilesLoading = tileset.stats.get('Tiles Loading').count; if (props.onProgress) { - props.onProgress( - tilesLoaded, - tilesLoaded + tilesLoading - ); + props.onProgress(tilesLoaded, tilesLoaded + tilesLoading); } if (props.loadingManager && !loadingEnded) { - if (tilesLoading == 0 && - ( - options.preloadTilesCount == null || - tilesLoaded >= options.preloadTilesCount) - ) { - loadingEnded = true; - props.loadingManager.itemEnd(props.url); - } + if (tilesLoading == 0 && (options.preloadTilesCount == null || tilesLoaded >= options.preloadTilesCount)) { + loadingEnded = true; + props.loadingManager.itemEnd(props.url); + } } return frameState; @@ -478,7 +467,6 @@ class Loader3DTiles { rootTransformInverse.copy(lastRootTransform).invert(); updateTransform(); - } return { @@ -553,30 +541,26 @@ class Loader3DTiles { }; }, getPositionFromLatLongHeight: (coord) => { - const cartesianPosition = tileset.ellipsoid.cartographicToCartesian([ - coord.long, - coord.lat, - coord.height - ]); + const cartesianPosition = tileset.ellipsoid.cartographicToCartesian([coord.long, coord.lat, coord.height]); return new Vector3(...cartesianPosition).applyMatrix4(threeMat); }, - orientToGeocoord: (coord:GeoCoord) => { + orientToGeocoord: (coord: GeoCoord) => { // Set the transofrmation matrix to the rotate the WGS84 globe to the given lat/long/Alt const cartographicPosition = [coord.long, coord.lat, coord.height]; - const cartesianPosition:number[] = tileset.ellipsoid.cartographicToCartesian(cartographicPosition); - const ellipsoidTransform = new Matrix4().fromArray(tileset.ellipsoid.eastNorthUpToFixedFrame(cartesianPosition)); + const cartesianPosition: number[] = tileset.ellipsoid.cartographicToCartesian(cartographicPosition); + const ellipsoidTransform = new Matrix4().fromArray( + tileset.ellipsoid.eastNorthUpToFixedFrame(cartesianPosition), + ); // Flip to Z is altitiude, Y is north, X is east - const alignRotation = new Matrix4().makeRotationFromEuler( - new Euler(Math.PI / 2, Math.PI / 2, 0) - ); + const alignRotation = new Matrix4().makeRotationFromEuler(new Euler(Math.PI / 2, Math.PI / 2, 0)); - const geoTransform = new Matrix4().copy(ellipsoidTransform).multiply(alignRotation).invert() + const geoTransform = new Matrix4().copy(ellipsoidTransform).multiply(alignRotation).invert(); setGeoTransformation(geoTransform); }, - getWebMercatorCoord: (coord:GeoCoord): Vector2 => { + getWebMercatorCoord: (coord: GeoCoord): Vector2 => { return Util.datumsToSpherical(coord.lat, coord.long); }, getCameraFrustum: (camera: Camera) => { @@ -596,7 +580,7 @@ class Loader3DTiles { // Initialize draping if (!renderer) { - throw new Error("GeoJSON draping requires a renderer reference via LoaderProps"); + throw new Error('GeoJSON draping requires a renderer reference via LoaderProps'); } Draping.setup(viewport, root, renderer, shaderOptions); (geoJSONMesh.material as Material).dispose(); @@ -629,13 +613,15 @@ class Loader3DTiles { timer = 0; lastRootTransform.copy(root.matrixWorld); if (options.updateTransforms) { - updateTransform(); + updateTransform(); } const rootCenter = new Vector3().setFromMatrixPosition(lastRootTransform); pointcloudUniforms.rootCenter.value.copy(rootCenter); - pointcloudUniforms.rootNormal.value.copy(new Vector3(0, 0, 1).applyMatrix4(lastRootTransform).normalize()); - rootTransformInverse.copy(lastRootTransform).invert(); + pointcloudUniforms.rootNormal.value.copy( + new Vector3(0, 0, 1).applyMatrix4(lastRootTransform).normalize(), + ); + rootTransformInverse.copy(lastRootTransform).invert(); if (options.debug) { boxMap[tileset.root.id].matrixWorld.copy(threeMat); @@ -646,10 +632,7 @@ class Loader3DTiles { if (lastCameraTransform == null) { lastCameraTransform = new Matrix4().copy(camera.matrixWorld); } else { - if ( - needsUpdate || - cameraChanged(camera, lastCameraTransform) - ) { + if (needsUpdate || cameraChanged(camera, lastCameraTransform)) { timer = 0; needsUpdate = false; tileset._frameNumber++; @@ -685,19 +668,20 @@ class Loader3DTiles { }; } /** - * Loads a tileset of 3D Tiles according to the given {@link GeoJSONLoaderProps} - * Could be overlayed on geograpical 3D Tiles using {@link Runtime.overlayGeoJSON} - * @public - * - * @param props - Properties for this load call {@link GeoJSONLoaderProps}. - * @returns An object containing the 3D Model to be added to the scene - */ - public static async loadGeoJSON(props: GeoJSONLoaderProps): Promise { - const { url, height, featureToColor } = props; - return load(url, _GeoJSONLoader, { worker: false, gis: {format: 'binary'}}).then((data) => { - const featureCollection = data as unknown as BinaryFeatureCollection; - const geometry = new BufferGeometry(); - const cartesianPositions = (featureCollection.polygons.positions.value as Float32Array).reduce((acc, val, i, src) => { + * Loads a tileset of 3D Tiles according to the given {@link GeoJSONLoaderProps} + * Could be overlayed on geograpical 3D Tiles using {@link Runtime.overlayGeoJSON} + * @public + * + * @param props - Properties for this load call {@link GeoJSONLoaderProps}. + * @returns An object containing the 3D Model to be added to the scene + */ + public static async loadGeoJSON(props: GeoJSONLoaderProps): Promise { + const { url, height, featureToColor } = props; + return load(url, _GeoJSONLoader, { worker: false, gis: { format: 'binary' } }).then((data) => { + const featureCollection = data as unknown as BinaryFeatureCollection; + const geometry = new BufferGeometry(); + const cartesianPositions = (featureCollection.polygons.positions.value as Float32Array).reduce( + (acc, val, i, src) => { if (i % 2 == 0) { const cartographic = [val, src[i + 1], height ?? 0]; const cartesian = Ellipsoid.WGS84.cartographicToCartesian(cartographic); @@ -705,46 +689,39 @@ class Loader3DTiles { acc.push(...cartesian); } return acc; + }, + [], + ); + geometry.setAttribute('position', new Float32BufferAttribute(cartesianPositions, 3)); + if (featureToColor) { + const colors = ( + (featureCollection.polygons.numericProps as any)[featureToColor.feature].value as Array + ).reduce((acc, val, i, src) => { + const color = featureToColor.colorMap(val); + acc[i * 3] = color.r; + acc[i * 3 + 1] = color.g; + acc[i * 3 + 2] = color.b; + return acc; }, []); - geometry.setAttribute('position', new Float32BufferAttribute( - cartesianPositions, - 3 - )); - if (featureToColor) { - const colors = ((featureCollection.polygons.numericProps as any) - [featureToColor.feature].value as Array).reduce((acc, val, i, src) => { - const color = featureToColor.colorMap(val); - acc[i * 3] = color.r; - acc[(i *3) + 1] = color.g; - acc[(i *3) + 2] = color.b; - return acc; - }, []); - geometry.setAttribute('color', new Float32BufferAttribute( - colors, - 3 - )); - } - geometry.setIndex( - new BufferAttribute(featureCollection.polygons.triangles.value, 1) - ); - const material = new MeshBasicMaterial({ - transparent: true, - vertexColors: true, - opacity: 0.5, - blending: NormalBlending - }); - const mesh = new Mesh( geometry, material ); - return mesh; + geometry.setAttribute('color', new Float32BufferAttribute(colors, 3)); + } + geometry.setIndex(new BufferAttribute(featureCollection.polygons.triangles.value, 1)); + const material = new MeshBasicMaterial({ + transparent: true, + vertexColors: true, + opacity: 0.5, + blending: NormalBlending, + }); + const mesh = new Mesh(geometry, material); + return mesh; }); } } - - async function createGLTFNodes(gltfLoader, tile, unlitMaterial, options, rootTransformInverse): Promise { return new Promise((resolve, reject) => { const rotateX = new Matrix4().makeRotationAxis(new Vector3(1, 0, 0), Math.PI / 2); - const shouldRotate = tile.content.gltfUpAxis !== "Z"; + const shouldRotate = tile.content.gltfUpAxis !== 'Z'; // The computed trasnform already contains the root's transform, so we have to invert it const contentTransform = new Matrix4().fromArray(tile.computedTransform).premultiply(rootTransformInverse); @@ -760,24 +737,24 @@ async function createGLTFNodes(gltfLoader, tile, unlitMaterial, options, rootTra gltfLoader.parse( tile.content.gltfArrayBuffer, - tile.contentUrl ? tile.contentUrl.substr(0,tile.contentUrl.lastIndexOf('/') + 1) : null, + tile.contentUrl ? tile.contentUrl.substr(0, tile.contentUrl.lastIndexOf('/') + 1) : null, (gltf) => { tile.userData.asset = gltf.asset; - + const tileContent = gltf.scenes[0] as Group; - tileContent.applyMatrix4(contentTransform); - - // Memory usage + tileContent.applyMatrix4(contentTransform); + + // Memory usage tile.content.texturesByteLength = 0; tile.content.geometriesByteLength = 0; tileContent.traverse((object) => { - if (object.type == "Mesh") { + if (object.type == 'Mesh') { const mesh = object as Mesh; tile.content.geometriesByteLength += Util.getGeometryVRAMByteLength(mesh.geometry); - const originalMaterial = (mesh.material as MeshStandardMaterial); + const originalMaterial = mesh.material as MeshStandardMaterial; const originalMap = originalMaterial.map; if (originalMap) { @@ -790,14 +767,17 @@ async function createGLTFNodes(gltfLoader, tile, unlitMaterial, options, rootTra if (options.material) { mesh.material = options.material.clone(); originalMaterial.dispose(); - } else if (options.shading == Shading.FlatTexture && (mesh.material as Material).type !== "MeshBasicMaterial") { + } else if ( + options.shading == Shading.FlatTexture && + (mesh.material as Material).type !== 'MeshBasicMaterial' + ) { mesh.material = unlitMaterial.clone(); originalMaterial.dispose(); } if (options.shading != Shading.ShadedNoTexture) { - if ((mesh.material as Material).type == "ShaderMaterial") { - (mesh.material as ShaderMaterial).uniforms.map = { value: originalMap }; + if ((mesh.material as Material).type == 'ShaderMaterial') { + (mesh.material as ShaderMaterial).uniforms.map = { value: originalMap }; } else { (mesh.material as MeshStandardMaterial).map = originalMap; } @@ -855,7 +835,7 @@ function createPointNodes(tile, pointcloudMaterial, options, rootTransformInvers geometry.setAttribute( 'intensity', // Handles both 16bit or 8bit intensity values - new BufferAttribute(d.intensities, 1, true) + new BufferAttribute(d.intensities, 1, true), ); } if (d.classifications) { @@ -880,13 +860,10 @@ function createPointNodes(tile, pointcloudMaterial, options, rootTransformInvers return tileContent; } - function disposeMaterial(material) { - if ((material as ShaderMaterial)?.uniforms?.map) { ((material as ShaderMaterial)?.uniforms?.map.value as Texture)?.dispose(); - } - else if (material.map) { + } else if (material.map) { (material.map as Texture)?.dispose(); } material.dispose(); @@ -898,12 +875,12 @@ function disposeNode(node) { object.geometry.dispose(); if (object.material.isMaterial) { - disposeMaterial(object.material); + disposeMaterial(object.material); } else { // an array of materials for (const material of object.material) { disposeMaterial(material); - } + } } } }); @@ -913,20 +890,20 @@ function disposeNode(node) { } } -function cameraChanged(camera:Camera, lastCameraTransform:Matrix4) { +function cameraChanged(camera: Camera, lastCameraTransform: Matrix4) { return !camera.matrixWorld.equals(lastCameraTransform); } function collectAttributions(tiles) { // attribution guidelines: https://developers.google.com/maps/documentation/tile/create-renderer#display-attributions - + const copyrightCounts = new Map(); // Use a Map to keep track of counts - tiles.forEach(tile => { + tiles.forEach((tile) => { const copyright = tile?.userData?.asset?.copyright; if (copyright) { - const attributions = copyright.split(/;/g).map(attr => attr.trim()); - attributions.forEach(attr => { + const attributions = copyright.split(/;/g).map((attr) => attr.trim()); + attributions.forEach((attr) => { if (attr) { // Increment the count for this attribution in the Map copyrightCounts.set(attr, (copyrightCounts.get(attr) || 0) + 1); @@ -937,21 +914,21 @@ function collectAttributions(tiles) { const sortedAttributions = Array.from(copyrightCounts) .sort((a, b) => b[1] - a[1]) - .map(([attr,]) => attr); + .map(([attr]) => attr); const attributionString = sortedAttributions.join('; '); return attributionString; } export { - Loader3DTiles, - PointCloudColoring, - Shading, - Runtime, - GeoCoord, - FeatureToColor, - LoaderOptions, - LoaderProps, - GeoJSONLoaderProps, - DrapingShaderOptions + Loader3DTiles, + PointCloudColoring, + Shading, + Runtime, + GeoCoord, + FeatureToColor, + LoaderOptions, + LoaderProps, + GeoJSONLoaderProps, + DrapingShaderOptions, }; diff --git a/src/types.ts b/src/types.ts index b4bb29c8..c2b40d32 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,7 +10,7 @@ import { LoadingManager, Mesh, Points, - Color + Color, } from 'three'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; @@ -41,20 +41,20 @@ interface Viewport { /** Properties for loading a tileset */ interface LoaderProps { - /** The URL of the tileset. For example if using Cesium ION, - * it would have the form: `https://assets.cesium.com/[ASSET_ID]/tileset.json`. - */ - url: string; - /** Viewport properties. Use `setViewport()` to update */ - viewport: Viewport; - /** An existing renderer reference. Required for shader processing. */ - renderer?: WebGLRenderer; - /** Advanced options for loading the tileset ({@link LoaderOptions}) */ - options?: LoaderOptions; - /** a loading progress callback function */ - onProgress?(progress: number | ProgressEvent, total?: number): void; - /** Use a Three JS loading manager */ - loadingManager?: LoadingManager; + /** The URL of the tileset. For example if using Cesium ION, + * it would have the form: `https://assets.cesium.com/[ASSET_ID]/tileset.json`. + */ + url: string; + /** Viewport properties. Use `setViewport()` to update */ + viewport: Viewport; + /** An existing renderer reference. Required for shader processing. */ + renderer?: WebGLRenderer; + /** Advanced options for loading the tileset ({@link LoaderOptions}) */ + options?: LoaderOptions; + /** a loading progress callback function */ + onProgress?(progress: number | ProgressEvent, total?: number): void; + /** Use a Three JS loading manager */ + loadingManager?: LoadingManager; } /** Advanced loader options */ @@ -79,7 +79,7 @@ interface LoaderOptions { memoryAdjustedScreenSpaceError?: boolean; /** The maximum additional memory (in MB) to allow for cache headroom before adjusting the screen spacer error - Default: `1`. */ memoryCacheOverflow?: number; - /** 0-1 scale for the LOD quality. A lower value loads tiles from lower LODs (increases performance). */ + /** 0-1 scale for the LOD quality. A lower value loads tiles from lower LODs (increases performance). */ viewDistanceScale?: number; /** Maximum worker thread concurrency when processing DRACO-compressed tiles - Default: `1` worker. */ maxConcurrency?: number; @@ -89,7 +89,7 @@ interface LoaderOptions { throttleRequests?: boolean; /** When thorttling requests, how many requests can launch simultaneously - Default: `64` */ maxRequests?: number; - /** _EXPERIMENTAL_: Skip traversal mechanism, not yet supported. Default: `false` */ + /** _EXPERIMENTAL_: Skip traversal mechanism, not yet supported. Default: `false` */ skipLevelOfDetail?: boolean; /** When viewing b3dm (mesh) tiles, which type of {@link Shading} is used - Default: `Shading.FlatTexture` */ shading?: Shading; @@ -137,7 +137,7 @@ interface FeatureToColor { interface GeoJSONLoaderProps { /** The URL of the GeoJSON file. */ url: string; - /** cartographic A height in which to place the GeoJSON */ + /** cartographic A height in which to place the GeoJSON */ height: number; /** A mapping function between data features and vertex colors */ featureToColor?: FeatureToColor; @@ -145,16 +145,16 @@ interface GeoJSONLoaderProps { /** Runtime methods that can be used once a tileset is loaded */ interface Runtime { - /** - * Get a reference to the loaders.gl {@link https://github.com/visgl/loaders.gl/blob/master/modules/tiles/docs/api-reference/tileset-3d.md | Tileset3D} object. - * - * @returns {@link https://github.com/visgl/loaders.gl/blob/master/modules/tiles/docs/api-reference/tileset-3d.md | Tileset3D} - */ + /** + * Get a reference to the loaders.gl {@link https://github.com/visgl/loaders.gl/blob/master/modules/tiles/docs/api-reference/tileset-3d.md | Tileset3D} object. + * + * @returns {@link https://github.com/visgl/loaders.gl/blob/master/modules/tiles/docs/api-reference/tileset-3d.md | Tileset3D} + */ getTileset(): Tileset3D; - /** - * Get a reference to the probe.gl {@link https://github.com/uber-web/probe.gl/blob/master/docs/api-reference/stats/stats.md | Stats} object. - * @returns {@link https://github.com/uber-web/probe.gl/blob/master/docs/api-reference/stats/stats.md | Stats} - */ + /** + * Get a reference to the probe.gl {@link https://github.com/uber-web/probe.gl/blob/master/docs/api-reference/stats/stats.md | Stats} object. + * @returns {@link https://github.com/uber-web/probe.gl/blob/master/docs/api-reference/stats/stats.md | Stats} + */ getStats(): Stats; /** Get the tileset's attribution text. */ getDataAttributions(): string; @@ -195,25 +195,25 @@ interface Runtime { /** Get the current camera frustum as mesh planes (for debugging purposes). */ getCameraFrustum(camera: Camera): Object3D; /** Overlay a GeoJSON polygon on top of geo-located 3d tiles. Implements a _Draping_ algorithm from https://ieeexplore.ieee.org/abstract/document/8811991 */ - overlayGeoJSON(geoJSONMesh: Mesh, shaderOptions?:DrapingShaderOptions): void; + overlayGeoJSON(geoJSONMesh: Mesh, shaderOptions?: DrapingShaderOptions): void; /** Set the viewport properties */ setViewport(viewport: Viewport): void; /** Set the renderer used for shader processsing */ setRenderer(renderer: WebGLRenderer): void; /** Update the tileset for rendering. */ - update(dt:Number, camera:Camera): void; + update(dt: Number, camera: Camera): void; /** Dispose of all of the tileset's assets in memory. */ dispose(): void; } -export type { - LoaderProps, - LoaderOptions, - Runtime, - GeoCoord, - GeoJSONLoaderProps, - FeatureToColor, +export type { + LoaderProps, + LoaderOptions, + Runtime, + GeoCoord, + GeoJSONLoaderProps, + FeatureToColor, DrapingShaderOptions, - Viewport + Viewport, }; -export { PointCloudColoring, Shading } \ No newline at end of file +export { PointCloudColoring, Shading }; diff --git a/src/util.ts b/src/util.ts index 5e79ec5a..73fc05b2 100644 --- a/src/util.ts +++ b/src/util.ts @@ -19,13 +19,13 @@ import { ArrowHelper, Color, Texture, - BufferGeometry + BufferGeometry, } from 'three'; import { Tile3D } from '@loaders.gl/tiles'; import { Plane as MathGLPlane } from '@math.gl/culling'; import { Matrix3 as MathGLMatrix3 } from '@math.gl/core'; -import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils' -import { Gradient } from './gradients' +import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils'; +import { Gradient } from './gradients'; // From https://github.com/potree/potree/blob/master/src/materials/PointCloudMaterial.js function generateGradientTexture(gradient: Gradient): CanvasTexture { @@ -123,7 +123,7 @@ function loadersBoundingBoxToMesh(tile: Tile3D): LineSegments { if (boundingVolume.halfAxes) { boxTransform.copy(getMatrix4FromHalfAxes(boundingVolume.halfAxes)); } else if (boundingVolume.radius) { - boxGeometry.scale(boundingVolume.radius * 2, boundingVolume.radius * 2, boundingVolume.radius * 2); + boxGeometry.scale(boundingVolume.radius * 2, boundingVolume.radius * 2, boundingVolume.radius * 2); } boxGeometry.applyMatrix4(boxTransform); @@ -156,19 +156,19 @@ function getMatrix4FromHalfAxes(halfAxes: MathGLMatrix3): Matrix4 { return rotateMatrix; } -/* +/* * from https://github.com/tentone/geo-three * Tree-shaking did not work, probably due to static class methods -*/ -function datumsToSpherical(latitude:number, longitude:number): Vector2 { - const EARTH_RADIUS = 6378137; - const EARTH_PERIMETER = 2 * Math.PI * EARTH_RADIUS; - const EARTH_ORIGIN = EARTH_PERIMETER / 2.0; - - const x = longitude * EARTH_ORIGIN / 180.0; - let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360.0)) / (Math.PI / 180.0); - y = y * EARTH_ORIGIN / 180.0; - return new Vector2(x, y); + */ +function datumsToSpherical(latitude: number, longitude: number): Vector2 { + const EARTH_RADIUS = 6378137; + const EARTH_PERIMETER = 2 * Math.PI * EARTH_RADIUS; + const EARTH_ORIGIN = EARTH_PERIMETER / 2.0; + + const x = (longitude * EARTH_ORIGIN) / 180.0; + let y = Math.log(Math.tan(((90 + latitude) * Math.PI) / 360.0)) / (Math.PI / 180.0); + y = (y * EARTH_ORIGIN) / 180.0; + return new Vector2(x, y); } function getTextureVRAMByteLength(texture: Texture): number | undefined { @@ -176,25 +176,24 @@ function getTextureVRAMByteLength(texture: Texture): number | undefined { let uncompressedBytes = 0; - if (texture?.userData.mimeType == "image/ktx2" && texture.mipmaps) { + if (texture?.userData.mimeType == 'image/ktx2' && texture.mipmaps) { for (let i = 0; i < texture.mipmaps.length; i++) { uncompressedBytes += texture.mipmaps[i].data.byteLength; } - return uncompressedBytes; - + return uncompressedBytes; } else if (texture.image) { const { image } = texture; const channels = 4; let resolution = [image.width, image.height]; while (resolution[0] > 1 || resolution[1] > 1) { - uncompressedBytes += resolution[0] * resolution[1] * channels; - resolution[0] = Math.max(Math.floor(resolution[0] / 2), 1); - resolution[1] = Math.max(Math.floor(resolution[1] / 2), 1); - } - uncompressedBytes += 1 * 1 * channels; + uncompressedBytes += resolution[0] * resolution[1] * channels; + resolution[0] = Math.max(Math.floor(resolution[0] / 2), 1); + resolution[1] = Math.max(Math.floor(resolution[1] / 2), 1); + } + uncompressedBytes += 1 * 1 * channels; - return uncompressedBytes + return uncompressedBytes; } else { return undefined; } @@ -211,5 +210,5 @@ export { getMatrix4FromHalfAxes, datumsToSpherical, getTextureVRAMByteLength, - getGeometryVRAMByteLength + getGeometryVRAMByteLength, }; From 25999f87194a66c16c8339c615294427e606d997 Mon Sep 17 00:00:00 2001 From: Stanislav Lorents Date: Fri, 9 May 2025 17:34:23 +1000 Subject: [PATCH 2/3] feat: allow to pass options to underlaying loaders --- src/index.ts | 4 +++- src/types.ts | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6172676c..994f6007 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,7 @@ import { PointCloudFS, PointCloudVS } from './shaders'; import type { LoaderProps, LoaderOptions, + LoaderGLOptions, Runtime, GeoCoord, GeoJSONLoaderProps, @@ -108,7 +109,7 @@ class Loader3DTiles { const UPDATE_INTERVAL = options.updateInterval; const MAX_DEPTH_FOR_ORIENTATION = 5; - const loadersGLOptions: { [key: string]: unknown } = {}; + const loadersGLOptions: { [key: string]: unknown } = { ...options.loaderGLOptions }; if (options.cesiumIONToken) { loadersGLOptions['cesium-ion'] = { @@ -928,6 +929,7 @@ export { GeoCoord, FeatureToColor, LoaderOptions, + LoaderGLOptions, LoaderProps, GeoJSONLoaderProps, DrapingShaderOptions, diff --git a/src/types.ts b/src/types.ts index c2b40d32..98795114 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ import { Stats } from '@probe.gl/stats'; import { Tileset3D } from '@loaders.gl/tiles'; +import { LoaderOptions as LoaderGLOptions } from '@loaders.gl/core'; import { Object3D, Vector2, @@ -115,6 +116,8 @@ interface LoaderOptions { dracoDecoderPath?: string; /** When using a three.js loading manager, do not call `onLoad` until this number of tiles were loaded - Default: `undefined` */ preloadTilesCount?: number; + /** Extra options to pass to underlying loaders */ + loaderGLOptions?: LoaderGLOptions; } /** Container object for interfacing with lat/long/height coordinates */ @@ -209,6 +212,7 @@ interface Runtime { export type { LoaderProps, LoaderOptions, + LoaderGLOptions, Runtime, GeoCoord, GeoJSONLoaderProps, From 3b0cc48ed7d1b9395728e3c2fbca3130298394d0 Mon Sep 17 00:00:00 2001 From: Stanislav Lorents Date: Mon, 12 May 2025 10:59:33 +1000 Subject: [PATCH 3/3] chore: build and publish to gemfury --- etc/three-loader-3dtiles.api.md | 6 +++++- package-lock.json | 4 ++-- package.json | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/etc/three-loader-3dtiles.api.md b/etc/three-loader-3dtiles.api.md index d2b61be5..541a94be 100644 --- a/etc/three-loader-3dtiles.api.md +++ b/etc/three-loader-3dtiles.api.md @@ -1,4 +1,4 @@ -## API Report File for "three-loader-3dtiles" +## API Report File for "@abyss/three-loader-3dtiles" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). @@ -8,6 +8,7 @@ import { Blending } from 'three'; import { Camera } from 'three'; import { Color } from 'three'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +import { LoaderOptions as LoaderGLOptions } from '@loaders.gl/core'; import { LoadingManager } from 'three'; import { Material } from 'three'; import { Mesh } from 'three'; @@ -57,6 +58,8 @@ export class Loader3DTiles { static loadGeoJSON(props: GeoJSONLoaderProps): Promise; } +export { LoaderGLOptions } + // @public export interface LoaderOptions { basisTranscoderPath?: string; @@ -67,6 +70,7 @@ export interface LoaderOptions { dracoDecoderPath?: string; gltfLoader?: GLTFLoader; googleApiKey?: string; + loaderGLOptions?: LoaderGLOptions; material?: Material; maxConcurrency?: number; maximumMemoryUsage?: number; diff --git a/package-lock.json b/package-lock.json index 3ce0a466..fe7bbb0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "three-loader-3dtiles", + "name": "@abyss/three-loader-3dtiles", "version": "1.2.7", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "three-loader-3dtiles", + "name": "@abyss/three-loader-3dtiles", "version": "1.2.7", "license": "Apache-2.0", "devDependencies": { diff --git a/package.json b/package.json index 412c413a..95491231 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "three-loader-3dtiles", + "name": "@abyss/three-loader-3dtiles", "version": "1.2.7", "description": "A 3D Tiles loader for Three.js", "main": "dist/lib/three-loader-3dtiles.umd.cjs", @@ -8,9 +8,8 @@ "license": "Apache-2.0", "repository": { "type": "git", - "url": "git+https://github.com/nytimes/three-loader-3dtiles.git" + "url": "git+https://github.com/abyss-solutions/three-loader-3dtiles.git" }, - "homepage": "https://github.com/nytimes/three-loader-3dtiles", "directories": { "example": "examples" }, @@ -45,6 +44,7 @@ "docs": "npm run build:types && npm run api:extract && npm run api:generate", "docs:production": "npm run build:types && npm run api:extract:production && npm run api:generate", "build:production": "export NODE_ENV=production || set NODE_ENV=production && npm run build", + "build:production:lib": "export NODE_ENV=production || set NODE_ENV=production && npm run build:lib && npm run build:types && npm run api:extract:production", "test": "mocha --experimental-specifier-resolution=node --loader=ts-node/esm test/**/*.spec.ts" }, "browserslist": [