| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- import {
- AViewerPluginEventMap,
- AViewerPluginSync,
- Box3B,
- EventListener2,
- generateUUID,
- IAssetImporter,
- ILoader,
- Importer,
- IObject3D,
- iObjectCommons,
- IRenderManager,
- IRenderManagerEventMap,
- IScene,
- ISceneEventMap,
- Loader,
- ImportAddOptions,
- LoadingManager,
- ThreeViewer, Sphere,
- } from 'threepipe'
- import {TilesGroup, TilesRenderer} from '3d-tiles-renderer'
- import {gltfCesiumRTCExtension, gltfMeshFeaturesExtension, gltfStructuralMetadataExtension} from './gltf'
- // @ts-expect-error moduleResolution issue
- import {ImplicitTilingPlugin, TilesFadePlugin, UpdateOnChangePlugin, CesiumIonAuthPlugin} from '3d-tiles-renderer/plugins'
-
- export type TilesRendererGroup = TilesGroup & IObject3D
-
- export interface TilesRendererPluginEventMap extends AViewerPluginEventMap {
- addTile: {group: TilesRendererGroup}
- removeTile: {group: TilesRendererGroup}
- }
-
- /**
- * TilesRendererPlugin is a plugin for loading and rendering OGC 3D Tiles using [3d-tiles-renderer](https://github.com/NASA-AMMOS/3DTilesRendererJS) package.
- *
- * Specification - https://www.ogc.org/standards/3dtiles/
- */
- export class TilesRendererPlugin extends AViewerPluginSync<TilesRendererPluginEventMap> {
- public static readonly PluginType: string = 'TilesRendererPlugin'
- enabled = true
- dependencies = []
-
- static readonly DUMMY_EXT = 'tileset'
-
- objects: TilesRendererGroup[] = []
-
- protected _importer = new Importer(TilesRendererLoader, [TilesRendererPlugin.DUMMY_EXT, TilesRendererPlugin.DUMMY_EXT + '.json'], [], false)
-
- constructor() {
- super()
- }
-
- async load(url: string, options?: ImportAddOptions) {
- if (!this._viewer) {
- console.warn('TilesRendererPlugin: viewer not set')
- return
- }
- const temp = generateUUID() + '.' + TilesRendererPlugin.DUMMY_EXT
- const importer = this._viewer.assetManager.importer.registerFile(temp) as TilesRendererLoader
- if (!importer.isTilesRendererLoader) {
- console.warn('TilesRendererPlugin: TilesRendererLoader not registered')
- return
- }
- return await this._viewer.load<TilesRendererGroup>(url, {
- ...options,
- fileExtension: TilesRendererPlugin.DUMMY_EXT,
- fileHandler: importer,
- })
- }
-
- async loadCesiumIon(info: TilesImportOptions['CesiumIonAuthPlugin'], options?: ImportAddOptions) {
- const file = generateUUID() + '.' + TilesRendererPlugin.DUMMY_EXT
- return this.load(file, {
- ...options,
- tiles: {
- CesiumIonAuthPlugin: info,
- ...options?.tiles,
- },
- })
- }
-
- onAdded(viewer: ThreeViewer) {
- super.onAdded(viewer)
- this._importer.onCtor = (l, ai) => {
- if (l) l.ai = ai
- return l
- }
- viewer.assetManager.registerGltfExtension(gltfCesiumRTCExtension)
- viewer.assetManager.registerGltfExtension(gltfStructuralMetadataExtension)
- viewer.assetManager.registerGltfExtension(gltfMeshFeaturesExtension)
- viewer.assetManager.importer.addImporter(this._importer)
- viewer.scene.addEventListener('addSceneObject', this._addSceneObject)
- // viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange)
- // viewer.scene.addEventListener('mainCameraUpdate', this._mainCameraUpdate)
- viewer.renderManager.addEventListener('preRender', this._preRender) // note - adding to renderManager preRender, not viewer. So its fired for each camera render
- viewer.renderManager.addEventListener('resize', this._resize)
- }
-
- onRemove(viewer: ThreeViewer) {
- viewer.assetManager.importer.removeImporter(this._importer)
- viewer.assetManager.unregisterGltfExtension(gltfCesiumRTCExtension.name)
- viewer.assetManager.unregisterGltfExtension(gltfStructuralMetadataExtension.name)
- viewer.assetManager.unregisterGltfExtension(gltfMeshFeaturesExtension.name)
- viewer.scene.removeEventListener('addSceneObject', this._addSceneObject)
- // viewer.scene.removeEventListener('mainCameraChange', this._mainCameraChange)
- // viewer.scene.removeEventListener('mainCameraUpdate', this._mainCameraUpdate)
- viewer.renderManager.removeEventListener('preRender', this._preRender)
- viewer.renderManager.removeEventListener('resize', this._resize)
- // todo dispose all tiles renderers?
- super.onRemove(viewer)
- }
-
- private _preRender/* : EventListener2<'preRender', ThreeViewerEventMap, ThreeViewer>*/ = () => {
- const camera = this._viewer?.scene.renderCamera
- if (!this._viewer || !camera) return
- if (this._viewer.renderManager.frameCount > 0) return // from ProgressivePlugin
- camera.updateProjectionMatrix()
- camera.updateMatrixWorld()
- for (const group of this.objects) {
- // todo do we need to do this every frame for every camera?
- group.tilesRenderer.setCamera(camera)
- group.tilesRenderer.setResolutionFromRenderer(camera, this._viewer.renderManager.webglRenderer)
- // group.tilesRenderer.frameCount = 0
- // console.log('update tiles renderer')
- group.tilesRenderer.update()
- }
- }
-
- // private _mainCameraUpdate: EventListener2<'mainCameraUpdate', ISceneEventMap, IScene> = (e) => {
- // const camera = e.camera ?? this._viewer?.scene.mainCamera
- // if (!this._viewer || !camera) return
- // camera.updateProjectionMatrix()
- // camera.updateMatrixWorld()
- // for (const group of this.objects) {
- // group.tilesRenderer.update()
- // }
- // }
- //
- // private _mainCameraChange: EventListener2<'mainCameraChange', ISceneEventMap, IScene> = (e) => {
- // const camera = e.camera
- // if (!this._viewer) return
- // for (const group of this.objects) {
- // // todo deleteCamera?
- // group.tilesRenderer.setCamera(camera)
- // group.tilesRenderer.setResolutionFromRenderer(camera, this._viewer.renderManager.webglRenderer)
- // }
- // }
-
- private _resize: EventListener2<'resize', IRenderManagerEventMap, IRenderManager> = () => {
- if (!this._viewer) return
- for (const group of this.objects) {
- group.tilesRenderer.setResolutionFromRenderer(this._viewer.scene.mainCamera, this._viewer.renderManager.webglRenderer)
- }
- }
-
- private _cachedRefs?: Pick<TilesRenderer, 'lruCache' | 'downloadQueue' | 'parseQueue'/* | 'processNodeQueue'*/>
-
- private _addSceneObject: EventListener2<'addSceneObject', ISceneEventMap, IScene> = (e)=>{
- const object = e.object
- const group = object as TilesRendererGroup
- // console.log(e)
- if (!group || !group.tilesRenderer || !this._viewer) return
-
- // set the second renderer to share the cache and queues from the first
- if (!this._cachedRefs) {
- this._cachedRefs = {
- lruCache: group.tilesRenderer.lruCache,
- downloadQueue: group.tilesRenderer.downloadQueue,
- parseQueue: group.tilesRenderer.parseQueue,
- // @ts-expect-error not in ts
- processNodeQueue: group.tilesRenderer.processNodeQueue,
- }
- } else {
- group.tilesRenderer.lruCache = this._cachedRefs.lruCache
- group.tilesRenderer.downloadQueue = this._cachedRefs.downloadQueue
- group.tilesRenderer.parseQueue = this._cachedRefs.parseQueue
- // @ts-expect-error not in ts
- group.tilesRenderer.processNodeQueue = this._cachedRefs.processNodeQueue
- }
-
- this.objects.push(group)
-
- group.tilesRenderer.registerPlugin({
- dispose: () => {
- const index = this.objects.indexOf(group)
- if (index !== -1) this.objects.splice(index, 1)
- this.dispatchEvent({type: 'removeTile', group})
- },
- })
- group.tilesRenderer.setCamera(this._viewer.scene.mainCamera)
- group.tilesRenderer.setResolutionFromRenderer(this._viewer.scene.mainCamera, this._viewer.renderManager.webglRenderer)
- group.tilesRenderer.update()
- group.addEventListener('dispose', ()=>{
- group.tilesRenderer.dispose()
- })
-
- this.dispatchEvent({type: 'addTile', group})
- }
-
- }
-
- export class TilesRendererLoader extends Loader implements ILoader<TilesRendererGroup> {
- isTilesRendererLoader = true
- ai?: IAssetImporter
- importOptions?: ImportAddOptions
-
- plugins: ((o: TilesImportOptions, group: TilesRendererGroup)=>object|undefined)[] = [
- ()=>new UpdateOnChangePlugin(),
- (opts)=>opts?.ImplicitTilingPlugin !== false ? new ImplicitTilingPlugin() : undefined,
- (opts)=>opts?.TilesFadePlugin !== false ? new TilesFadePlugin(typeof opts?.TilesFadePlugin === 'object' ? opts.TilesFadePlugin : undefined) : undefined,
- (opts)=>opts?.CesiumIonAuthPlugin ? new CesiumIonAuthPlugin(typeof opts?.CesiumIonAuthPlugin === 'object' ? opts.CesiumIonAuthPlugin : undefined) : undefined,
- ]
-
- constructor(manager: LoadingManager) {
- super(manager)
- }
-
- protected _createTilesRenderer(url: string) {
-
- const tiles = new TilesRenderer(url)
- tiles.manager = this.manager
- tiles.fetchOptions.headers = new Headers(this.requestHeader)
- tiles.fetchOptions.credentials = this.withCredentials ? 'include' : 'same-origin'
- tiles.fetchOptions.mode = 'cors'
-
- return tiles
- }
-
- load(url: string, onLoad: (data: unknown) => void, _onProgress?: (event: ProgressEvent) => void, _onError?: (err: unknown) => void) {
- // todo
- // let resourcePath = this.resourcePath || this.path || LoaderUtils.extractUrlBase(url)
-
- const tiles = this._createTilesRenderer(url)
- const group = iObjectCommons.upgradeObject3D.call(tiles.group) as TilesRendererGroup
- group.autoUpgradeChildren = false
-
- const opts = this.importOptions?.tiles ?? {}
- tiles.errorTarget = opts.errorTarget ?? 1
- const plugins = [...this.plugins, ...opts.plugins ?? []]
- for (const plugin of plugins) {
- const p = plugin(opts, group)
- if (p) tiles.registerPlugin(p)
- }
-
- // bounds, similar to InstancedMesh
- group.boundingBox = null
- group.boundingSphere = null
- group.computeBoundingBox = ()=>{
- if (!group.boundingBox) group.boundingBox = new Box3B()
- tiles.getBoundingBox(group.boundingBox)
- }
- group.computeBoundingSphere = ()=>{
- if (!group.boundingSphere) group.boundingSphere = new Sphere()
- tiles.getBoundingSphere(group.boundingSphere)
- }
- tiles.addEventListener('load-tile-set', (_e) => {
- group.computeBoundingBox!()
- group.computeBoundingSphere!()
- })
-
- // const sup = group.updateWorldMatrix
- // todo remove in next version
- group.updateWorldMatrix = (updateParents) => {
- if (group.parent && updateParents) {
- group.parent.updateWorldMatrix(updateParents, false)
- }
- // run the normal update function to ensure children and inverse matrices are in sync
- group.updateMatrixWorld(true)
- }
-
- // Save promise to tell the viewer/scene when the load is finished, it can then autoScale, autoCenter etc
- let resolve: any
- // let reject: any
- group._loadingPromise = new Promise<void>((res, _rej) => {
- resolve = res
- // reject = _rej
- })
- tiles.addEventListener('load-tile-set', (e) => {
- const isRoot = e.tileSet === tiles.rootTileSet
- if (isRoot && resolve) resolve()
- })
-
- const ai = this.ai
- if (ai) {
- const tmpFile = generateUUID()
- ai.registerFile(tmpFile + '.gltf') // to set the gltf loader in manager
- ai.registerFile(tmpFile + '.drc') // to set the draco loader in manager
-
- const preprocessUrl = (url1: string) => {
- if (tiles.preprocessURL) return tiles.preprocessURL(url1)
- return url1
- }
- ai.addURLModifier(preprocessUrl)
- tiles.registerPlugin({
- dispose: () => {
- if (ai) {
- ai.unregisterFile(tmpFile + '.gltf')
- ai.unregisterFile(tmpFile + '.drc')
- ai.removeURLModifier(preprocessUrl)
- }
- },
- })
-
- }
-
- tiles.addEventListener('tile-visibility-change', (_e) => {
- // console.log(e)
- })
-
- const setDirty = (_e: any)=>{
- // console.log(e)
- group.setDirty({frameFade: false})
- }
- tiles.addEventListener('load-content', setDirty)
- tiles.addEventListener('load-tile-set', setDirty)
- tiles.addEventListener('needs-update', setDirty)
-
- tiles.update()
- onLoad(group)
-
- }
-
- }
-
- export interface TilesImportOptions{
- /**
- * @default 1
- */
- errorTarget?: number
- ImplicitTilingPlugin?: boolean
- TilesFadePlugin?: boolean | {
- maximumFadeOutTiles?: number,
- fadeRootTiles?: boolean,
- fadeDuration?: number,
- }
- CesiumIonAuthPlugin?: boolean | {
- apiToken: string,
- assetId?: string | null,
- autoRefreshToken?: boolean
- }
- plugins?: ((opts: TilesImportOptions, group: TilesRendererGroup)=>object|undefined)[]
- }
-
- declare module 'threepipe'{
- interface ImportAddOptions {
- tiles?: TilesImportOptions
- }
- }
|