threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

TilesRendererPlugin.ts 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. import {
  2. AViewerPluginEventMap,
  3. AViewerPluginSync,
  4. Box3B,
  5. EventListener2,
  6. generateUUID,
  7. IAssetImporter,
  8. ILoader,
  9. Importer,
  10. IObject3D,
  11. iObjectCommons,
  12. IRenderManager,
  13. IRenderManagerEventMap,
  14. IScene,
  15. ISceneEventMap,
  16. Loader,
  17. ImportAddOptions,
  18. LoadingManager,
  19. ThreeViewer, Sphere, uiButton,
  20. } from 'threepipe'
  21. import {TilesGroup, TilesRenderer} from '3d-tiles-renderer'
  22. import {gltfCesiumRTCExtension, gltfMeshFeaturesExtension, gltfStructuralMetadataExtension} from './gltf'
  23. // @ts-expect-error moduleResolution issue
  24. import {ImplicitTilingPlugin, TilesFadePlugin, UpdateOnChangePlugin, CesiumIonAuthPlugin} from '3d-tiles-renderer/plugins'
  25. export type TilesRendererGroup = TilesGroup & IObject3D
  26. export interface TilesRendererPluginEventMap extends AViewerPluginEventMap {
  27. addTile: {group: TilesRendererGroup}
  28. removeTile: {group: TilesRendererGroup}
  29. }
  30. /**
  31. * TilesRendererPlugin is a plugin for loading and rendering OGC 3D Tiles using [3d-tiles-renderer](https://github.com/NASA-AMMOS/3DTilesRendererJS) package.
  32. *
  33. * Specification - https://www.ogc.org/standards/3dtiles/
  34. */
  35. export class TilesRendererPlugin extends AViewerPluginSync<TilesRendererPluginEventMap> {
  36. public static readonly PluginType: string = 'TilesRendererPlugin'
  37. enabled = true
  38. dependencies = []
  39. static readonly DUMMY_EXT = 'tileset'
  40. objects: TilesRendererGroup[] = []
  41. protected _importer = new Importer(TilesRendererLoader, [TilesRendererPlugin.DUMMY_EXT, TilesRendererPlugin.DUMMY_EXT + '.json'], [], false)
  42. constructor() {
  43. super()
  44. }
  45. async load(url: string, options?: ImportAddOptions) {
  46. if (!this._viewer) {
  47. console.warn('TilesRendererPlugin: viewer not set')
  48. return
  49. }
  50. const temp = generateUUID() + '.' + TilesRendererPlugin.DUMMY_EXT
  51. const importer = this._viewer.assetManager.importer.registerFile(temp) as TilesRendererLoader
  52. if (!importer.isTilesRendererLoader) {
  53. console.warn('TilesRendererPlugin: TilesRendererLoader not registered')
  54. return
  55. }
  56. return await this._viewer.load<TilesRendererGroup>(url, {
  57. ...options,
  58. fileExtension: TilesRendererPlugin.DUMMY_EXT,
  59. fileHandler: importer,
  60. })
  61. }
  62. async loadCesiumIon(info: TilesImportOptions['CesiumIonAuthPlugin'], options?: ImportAddOptions) {
  63. const file = generateUUID() + '.' + TilesRendererPlugin.DUMMY_EXT
  64. return this.load(file, {
  65. ...options,
  66. tiles: {
  67. CesiumIonAuthPlugin: info,
  68. ...options?.tiles,
  69. },
  70. })
  71. }
  72. onAdded(viewer: ThreeViewer) {
  73. super.onAdded(viewer)
  74. this._importer.onCtor = (l, ai) => {
  75. if (l) l.ai = ai
  76. return l
  77. }
  78. viewer.assetManager.registerGltfExtension(gltfCesiumRTCExtension)
  79. viewer.assetManager.registerGltfExtension(gltfStructuralMetadataExtension)
  80. viewer.assetManager.registerGltfExtension(gltfMeshFeaturesExtension)
  81. viewer.assetManager.importer.addImporter(this._importer)
  82. viewer.scene.addEventListener('addSceneObject', this._addSceneObject)
  83. // viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange)
  84. // viewer.scene.addEventListener('mainCameraUpdate', this._mainCameraUpdate)
  85. viewer.renderManager.addEventListener('preRender', this._preRender) // note - adding to renderManager preRender, not viewer. So its fired for each camera render
  86. viewer.renderManager.addEventListener('resize', this._resize)
  87. }
  88. onRemove(viewer: ThreeViewer) {
  89. viewer.assetManager.importer.removeImporter(this._importer)
  90. viewer.assetManager.unregisterGltfExtension(gltfCesiumRTCExtension.name)
  91. viewer.assetManager.unregisterGltfExtension(gltfStructuralMetadataExtension.name)
  92. viewer.assetManager.unregisterGltfExtension(gltfMeshFeaturesExtension.name)
  93. viewer.scene.removeEventListener('addSceneObject', this._addSceneObject)
  94. // viewer.scene.removeEventListener('mainCameraChange', this._mainCameraChange)
  95. // viewer.scene.removeEventListener('mainCameraUpdate', this._mainCameraUpdate)
  96. viewer.renderManager.removeEventListener('preRender', this._preRender)
  97. viewer.renderManager.removeEventListener('resize', this._resize)
  98. // todo dispose all tiles renderers?
  99. super.onRemove(viewer)
  100. }
  101. private _preRender/* : EventListener2<'preRender', ThreeViewerEventMap, ThreeViewer>*/ = () => {
  102. const camera = this._viewer?.scene.renderCamera
  103. if (!this._viewer || !camera) return
  104. if (this._viewer.renderManager.frameCount > 0) return // from ProgressivePlugin
  105. camera.updateProjectionMatrix()
  106. camera.updateMatrixWorld()
  107. for (const group of this.objects) {
  108. // todo do we need to do this every frame for every camera?
  109. group.tilesRenderer.setCamera(camera)
  110. group.tilesRenderer.setResolutionFromRenderer(camera, this._viewer.renderManager.webglRenderer)
  111. // group.tilesRenderer.frameCount = 0
  112. // console.log('update tiles renderer')
  113. group.tilesRenderer.update()
  114. }
  115. }
  116. // private _mainCameraUpdate: EventListener2<'mainCameraUpdate', ISceneEventMap, IScene> = (e) => {
  117. // const camera = e.camera ?? this._viewer?.scene.mainCamera
  118. // if (!this._viewer || !camera) return
  119. // camera.updateProjectionMatrix()
  120. // camera.updateMatrixWorld()
  121. // for (const group of this.objects) {
  122. // group.tilesRenderer.update()
  123. // }
  124. // }
  125. //
  126. // private _mainCameraChange: EventListener2<'mainCameraChange', ISceneEventMap, IScene> = (e) => {
  127. // const camera = e.camera
  128. // if (!this._viewer) return
  129. // for (const group of this.objects) {
  130. // // todo deleteCamera?
  131. // group.tilesRenderer.setCamera(camera)
  132. // group.tilesRenderer.setResolutionFromRenderer(camera, this._viewer.renderManager.webglRenderer)
  133. // }
  134. // }
  135. private _resize: EventListener2<'resize', IRenderManagerEventMap, IRenderManager> = () => {
  136. if (!this._viewer) return
  137. for (const group of this.objects) {
  138. group.tilesRenderer.setResolutionFromRenderer(this._viewer.scene.mainCamera, this._viewer.renderManager.webglRenderer)
  139. }
  140. }
  141. private _cachedRefs?: Pick<TilesRenderer, 'lruCache' | 'downloadQueue' | 'parseQueue'/* | 'processNodeQueue'*/>
  142. private _addSceneObject: EventListener2<'addSceneObject', ISceneEventMap, IScene> = (e)=>{
  143. const object = e.object
  144. const group = object as TilesRendererGroup
  145. // console.log(e)
  146. if (!group || !group.tilesRenderer || !this._viewer) return
  147. // set the second renderer to share the cache and queues from the first
  148. if (!this._cachedRefs) {
  149. this._cachedRefs = {
  150. lruCache: group.tilesRenderer.lruCache,
  151. downloadQueue: group.tilesRenderer.downloadQueue,
  152. parseQueue: group.tilesRenderer.parseQueue,
  153. // @ts-expect-error not in ts
  154. processNodeQueue: group.tilesRenderer.processNodeQueue,
  155. }
  156. } else {
  157. group.tilesRenderer.lruCache = this._cachedRefs.lruCache
  158. group.tilesRenderer.downloadQueue = this._cachedRefs.downloadQueue
  159. group.tilesRenderer.parseQueue = this._cachedRefs.parseQueue
  160. // @ts-expect-error not in ts
  161. group.tilesRenderer.processNodeQueue = this._cachedRefs.processNodeQueue
  162. }
  163. this.objects.push(group)
  164. group.tilesRenderer.registerPlugin({
  165. dispose: () => {
  166. const index = this.objects.indexOf(group)
  167. if (index !== -1) this.objects.splice(index, 1)
  168. this.dispatchEvent({type: 'removeTile', group})
  169. },
  170. })
  171. group.tilesRenderer.setCamera(this._viewer.scene.mainCamera)
  172. group.tilesRenderer.setResolutionFromRenderer(this._viewer.scene.mainCamera, this._viewer.renderManager.webglRenderer)
  173. group.tilesRenderer.update()
  174. group.addEventListener('dispose', ()=>{
  175. group.tilesRenderer.dispose()
  176. })
  177. this.dispatchEvent({type: 'addTile', group})
  178. }
  179. @uiButton()
  180. async promptForURL() {
  181. const url = await this._viewer?.dialog.prompt('TilesRendererPlugin: Enter URL for the root tileset', '', true)
  182. if (!url) return
  183. return this.load(url)
  184. }
  185. }
  186. export class TilesRendererLoader extends Loader implements ILoader<TilesRendererGroup> {
  187. isTilesRendererLoader = true
  188. ai?: IAssetImporter
  189. importOptions?: ImportAddOptions
  190. plugins: ((o: TilesImportOptions, group: TilesRendererGroup)=>object|undefined)[] = [
  191. ()=>new UpdateOnChangePlugin(),
  192. (opts)=>opts?.ImplicitTilingPlugin !== false ? new ImplicitTilingPlugin() : undefined,
  193. (opts)=>opts?.TilesFadePlugin !== false ? new TilesFadePlugin(typeof opts?.TilesFadePlugin === 'object' ? opts.TilesFadePlugin : undefined) : undefined,
  194. (opts)=>opts?.CesiumIonAuthPlugin ? new CesiumIonAuthPlugin(typeof opts?.CesiumIonAuthPlugin === 'object' ? opts.CesiumIonAuthPlugin : undefined) : undefined,
  195. ]
  196. constructor(manager: LoadingManager) {
  197. super(manager)
  198. }
  199. protected _createTilesRenderer(url: string) {
  200. const tiles = new TilesRenderer(url)
  201. tiles.manager = this.manager
  202. tiles.fetchOptions.headers = new Headers(this.requestHeader)
  203. tiles.fetchOptions.credentials = this.withCredentials ? 'include' : 'same-origin'
  204. tiles.fetchOptions.mode = 'cors'
  205. return tiles
  206. }
  207. load(url: string, onLoad: (data: unknown) => void, _onProgress?: (event: ProgressEvent) => void, _onError?: (err: unknown) => void) {
  208. // todo
  209. // let resourcePath = this.resourcePath || this.path || LoaderUtils.extractUrlBase(url)
  210. const tiles = this._createTilesRenderer(url)
  211. const group = iObjectCommons.upgradeObject3D.call(tiles.group) as TilesRendererGroup
  212. group.autoUpgradeChildren = false
  213. const opts = this.importOptions?.tiles ?? {}
  214. tiles.errorTarget = opts.errorTarget ?? 1
  215. const plugins = [...this.plugins, ...opts.plugins ?? []]
  216. for (const plugin of plugins) {
  217. const p = plugin(opts, group)
  218. if (p) tiles.registerPlugin(p)
  219. }
  220. // bounds, similar to InstancedMesh
  221. group.boundingBox = null
  222. group.boundingSphere = null
  223. group.computeBoundingBox = ()=>{
  224. if (!group.boundingBox) group.boundingBox = new Box3B()
  225. tiles.getBoundingBox(group.boundingBox)
  226. }
  227. group.computeBoundingSphere = ()=>{
  228. if (!group.boundingSphere) group.boundingSphere = new Sphere()
  229. tiles.getBoundingSphere(group.boundingSphere)
  230. }
  231. tiles.addEventListener('load-tile-set', (_e) => {
  232. group.computeBoundingBox!()
  233. group.computeBoundingSphere!()
  234. })
  235. // const sup = group.updateWorldMatrix
  236. // todo remove in next version
  237. group.updateWorldMatrix = (updateParents) => {
  238. if (group.parent && updateParents) {
  239. group.parent.updateWorldMatrix(updateParents, false)
  240. }
  241. // run the normal update function to ensure children and inverse matrices are in sync
  242. group.updateMatrixWorld(true)
  243. }
  244. // Save promise to tell the viewer/scene when the load is finished, it can then autoScale, autoCenter etc
  245. let resolve: any
  246. // let reject: any
  247. group._loadingPromise = new Promise<void>((res, _rej) => {
  248. resolve = res
  249. // reject = _rej
  250. })
  251. tiles.addEventListener('load-tile-set', (e) => {
  252. const isRoot = e.tileSet === tiles.rootTileSet
  253. if (isRoot && resolve) resolve()
  254. })
  255. const ai = this.ai
  256. if (ai) {
  257. const tmpFile = generateUUID()
  258. ai.registerFile(tmpFile + '.gltf') // to set the gltf loader in manager
  259. ai.registerFile(tmpFile + '.drc') // to set the draco loader in manager
  260. const preprocessUrl = (url1: string) => {
  261. if (tiles.preprocessURL) return tiles.preprocessURL(url1)
  262. return url1
  263. }
  264. ai.addURLModifier(preprocessUrl)
  265. tiles.registerPlugin({
  266. dispose: () => {
  267. if (ai) {
  268. ai.unregisterFile(tmpFile + '.gltf')
  269. ai.unregisterFile(tmpFile + '.drc')
  270. ai.removeURLModifier(preprocessUrl)
  271. }
  272. },
  273. })
  274. }
  275. tiles.addEventListener('tile-visibility-change', (_e) => {
  276. // console.log(e)
  277. })
  278. const setDirty = (_e: any)=>{
  279. // console.log(e)
  280. group.setDirty({frameFade: false})
  281. }
  282. tiles.addEventListener('load-content', setDirty)
  283. tiles.addEventListener('load-tile-set', setDirty)
  284. tiles.addEventListener('needs-update', setDirty)
  285. tiles.update()
  286. onLoad(group)
  287. }
  288. }
  289. export interface TilesImportOptions{
  290. /**
  291. * @default 1
  292. */
  293. errorTarget?: number
  294. ImplicitTilingPlugin?: boolean
  295. TilesFadePlugin?: boolean | {
  296. maximumFadeOutTiles?: number,
  297. fadeRootTiles?: boolean,
  298. fadeDuration?: number,
  299. }
  300. CesiumIonAuthPlugin?: boolean | {
  301. apiToken: string,
  302. assetId?: string | null,
  303. autoRefreshToken?: boolean
  304. }
  305. plugins?: ((opts: TilesImportOptions, group: TilesRendererGroup)=>object|undefined)[]
  306. }
  307. declare module 'threepipe'{
  308. interface ImportAddOptions {
  309. tiles?: TilesImportOptions
  310. }
  311. }