threepipe
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ThreeViewer.ts 47KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  1. import {
  2. BaseEvent,
  3. Color,
  4. Event,
  5. EventDispatcher,
  6. LinearSRGBColorSpace,
  7. Object3D,
  8. Quaternion,
  9. Vector2,
  10. Vector3,
  11. } from 'three'
  12. import {Class, createCanvasElement, onChange, serialize} from 'ts-browser-helpers'
  13. import {TViewerScreenShader} from '../postprocessing'
  14. import {
  15. AddObjectOptions,
  16. IAnimationLoopEvent,
  17. IMaterial,
  18. IObject3D,
  19. IObjectProcessor,
  20. ITexture,
  21. PerspectiveCamera2,
  22. RootScene,
  23. } from '../core'
  24. import {ViewerRenderManager} from './ViewerRenderManager'
  25. import {
  26. convertArrayBufferToStringsInMeta,
  27. getEmptyMeta,
  28. GLStatsJS,
  29. IDialogWrapper,
  30. jsonToBlob,
  31. metaFromResources,
  32. MetaImporter,
  33. metaToResources,
  34. SerializationMetaType,
  35. SerializationResourcesType,
  36. ThreeSerialization,
  37. windowDialogWrapper,
  38. } from '../utils'
  39. import {
  40. AssetManager,
  41. AssetManagerOptions,
  42. BlobExt,
  43. ExportFileOptions,
  44. IAsset,
  45. ImportAddOptions,
  46. ImportAssetOptions,
  47. ImportResult,
  48. RootSceneImportResult,
  49. } from '../assetmanager'
  50. import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin'
  51. // noinspection ES6PreferShortImport
  52. import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/DropzonePlugin'
  53. import {uiConfig, uiFolderContainer, UiObjectConfig} from 'uiconfig.js'
  54. import {IRenderTarget} from '../rendering'
  55. import {TonemapPlugin} from '../plugins'
  56. import {VERSION} from './version'
  57. export type IViewerEvent = BaseEvent & {
  58. type: 'update'|'preRender'|'postRender'|'preFrame'|'postFrame'|'dispose'|'addPlugin'|'renderEnabled'|'renderDisabled'
  59. }
  60. export type IViewerEventTypes = IViewerEvent['type']
  61. export interface ISerializedConfig {
  62. assetType: 'config',
  63. type: string,
  64. metadata?: {
  65. generator: string,
  66. version: number,
  67. [key: string]: any
  68. },
  69. [key: string]: any
  70. }
  71. export interface ISerializedViewerConfig extends ISerializedConfig{
  72. type: 'ThreeViewer'|'ViewerApp',
  73. version: string,
  74. plugins: ISerializedConfig[],
  75. resources?: Partial<SerializationResourcesType> | SerializationMetaType
  76. renderManager?: any // todo
  77. scene?: any
  78. [key: string]: any
  79. }
  80. export type IConsoleWrapper = Partial<Console> & Pick<Console, 'log'|'warn'|'error'>
  81. /**
  82. * Options for the ThreeViewer creation.
  83. * @category Viewer
  84. */
  85. export interface ThreeViewerOptions {
  86. /**
  87. * The canvas element to use for rendering. Only one of container and canvas must be specified.
  88. */
  89. canvas?: HTMLCanvasElement,
  90. /**
  91. * The container for the canvas. A new canvas will be created in this container. Only one of container and canvas must be specified.
  92. */
  93. container?: HTMLElement,
  94. /**
  95. * The fragment shader snippet to render on screen.
  96. */
  97. screenShader?: TViewerScreenShader,
  98. /**
  99. * Use MSAA.
  100. */
  101. msaa?: boolean,
  102. /**
  103. * Use Uint8 RGBM HDR Render Pipeline.
  104. * Provides better performance with post-processing.
  105. * RenderManager Uses Half-float if set to false.
  106. */
  107. rgbm?: boolean
  108. /**
  109. * Use rendered gbuffer as depth-prepass / z-prepass.
  110. */
  111. zPrepass?: boolean
  112. /*
  113. * Render scale, 1 = full resolution, 0.5 = half resolution, 2 = double resolution.
  114. * Same as pixelRatio in three.js
  115. * Can be set to `window.devicePixelRatio` to render at device resolution in browsers.
  116. * An optimal value is `Math.min(2, window.devicePixelRatio)` to prevent issues on mobile.
  117. */
  118. renderScale?: number
  119. debug?: boolean
  120. /**
  121. * TonemapPlugin is added to the viewer if this is true.
  122. * @default true
  123. */
  124. tonemap?: boolean
  125. /**
  126. * Options for the asset manager.
  127. */
  128. assetManager?: AssetManagerOptions
  129. /**
  130. * Add the dropzone plugin to the viewer, allowing to drag and drop files into the viewer over the canvas/container.
  131. * Set to true/false to enable/disable the plugin, or pass options to configure the plugin. Assuming true if options are passed.
  132. * @default - false
  133. */
  134. dropzone?: boolean|DropzonePluginOptions
  135. /**
  136. * @deprecated use {@link msaa} instead
  137. */
  138. isAntialiased?: boolean,
  139. /**
  140. * @deprecated use {@link rgbm} instead
  141. */
  142. useRgbm?: boolean
  143. /**
  144. * @deprecated use {@link zPrepass} instead
  145. */
  146. useGBufferDepth?: boolean
  147. }
  148. /**
  149. * Three Viewer
  150. *
  151. * The ThreeViewer is the main class in the framework to manage a scene, render and add plugins to it.
  152. * @category Viewer
  153. */
  154. @uiFolderContainer('Viewer')
  155. export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes> {
  156. public static readonly VERSION = VERSION
  157. public static readonly ConfigTypeSlug = 'vjson'
  158. uiConfig!: UiObjectConfig
  159. static Console: IConsoleWrapper = {
  160. log: console.log.bind(console),
  161. warn: console.warn.bind(console),
  162. error: console.error.bind(console),
  163. }
  164. static Dialog: IDialogWrapper = windowDialogWrapper
  165. /**
  166. * If the viewer is enabled. Set this `false` to disable RAF loop.
  167. * @type {boolean}
  168. */
  169. enabled = true
  170. /**
  171. * Enable or disable all rendering, Animation loop including any frame/render events won't be fired when this is false.
  172. */
  173. @onChange(ThreeViewer.prototype._renderEnabledChanged)
  174. renderEnabled = true
  175. renderStats: GLStatsJS
  176. readonly assetManager: AssetManager
  177. @uiConfig() @serialize('renderManager')
  178. readonly renderManager: ViewerRenderManager
  179. public readonly plugins: Record<string, IViewerPlugin> = {}
  180. /**
  181. * Scene with object hierarchy used for rendering
  182. */
  183. get scene(): RootScene {
  184. return this._scene
  185. }
  186. /**
  187. * Specifies how many frames to render in a single request animation frame. Keep to 1 for realtime rendering.
  188. * Note: should be max (screen refresh rate / animation frame rate) like 60Hz / 30fps
  189. * @type {number}
  190. */
  191. public maxFramePerLoop = 1
  192. readonly debug: boolean
  193. /**
  194. * Get the HTML Element containing the canvas
  195. * @returns {HTMLElement}
  196. */
  197. get container(): HTMLElement {
  198. return this._container
  199. }
  200. /**
  201. * Get the HTML Canvas Element where the viewer is rendering
  202. * @returns {HTMLCanvasElement}
  203. */
  204. get canvas(): HTMLCanvasElement {
  205. return this._canvas
  206. }
  207. get console(): IConsoleWrapper {
  208. return ThreeViewer.Console
  209. }
  210. get dialog(): IDialogWrapper {
  211. return ThreeViewer.Dialog
  212. }
  213. @serialize() readonly type = 'ThreeViewer'
  214. /**
  215. * The ResizeObserver observing the canvas element. Add more elements to this observer to resize viewer on their size change.
  216. * @type {ResizeObserver | undefined}
  217. */
  218. readonly resizeObserver = window?.ResizeObserver ? new window.ResizeObserver(_ => this.resize()) : undefined
  219. private readonly _canvas: HTMLCanvasElement
  220. // this can be used by other plugins to add ui elements alongside the canvas
  221. private readonly _container: HTMLElement // todo: add a way to move the canvas to a new container... and dispatch event...
  222. /**
  223. * The Scene attached to the viewer, this cannot be changed.
  224. * @type {RootScene}
  225. */
  226. @uiConfig() @serialize('scene')
  227. private readonly _scene: RootScene
  228. private _needsResize = false
  229. private _isRenderingFrame = false
  230. private _objectProcessor: IObjectProcessor = {
  231. processObject: (object: IObject3D)=>{
  232. if (object.material) {
  233. if (Array.isArray(object.material)) this.assetManager.materials.registerMaterials(object.material)
  234. else this.assetManager.materials.registerMaterial(object.material)
  235. }
  236. },
  237. }
  238. private _needsReset = true // renderer needs reset
  239. // Helpers for tracking main camera change and setting dirty automatically
  240. private _lastCameraPosition: Vector3 = new Vector3()
  241. private _lastCameraQuat: Quaternion = new Quaternion()
  242. private _lastCameraTarget: Vector3 = new Vector3()
  243. private _tempVec: Vector3 = new Vector3()
  244. private _tempQuat: Quaternion = new Quaternion()
  245. /**
  246. * Create a viewer instance for using the webgi viewer SDK.
  247. * @param options - {@link ThreeViewerOptions}
  248. */
  249. constructor({debug = true, ...options}: ThreeViewerOptions) {
  250. super()
  251. this.debug = debug
  252. this._canvas = options.canvas || createCanvasElement()
  253. let container = options.container
  254. if (container && !options.canvas) container.appendChild(this._canvas)
  255. if (!container) container = this._canvas.parentElement ?? undefined
  256. if (!container) throw new Error('No container.')
  257. this._container = container
  258. this.setDirty = this.setDirty.bind(this)
  259. this._animationLoop = this._animationLoop.bind(this)
  260. this._setActiveCameraView = this._setActiveCameraView.bind(this)
  261. this.renderStats = new GLStatsJS(this._container)
  262. if (debug) this.renderStats.show()
  263. if (!(window as any).threeViewers) (window as any).threeViewers = [];
  264. (window as any).threeViewers.push(this)
  265. // camera
  266. const camera = new PerspectiveCamera2('orbit', this._canvas)
  267. camera.name = 'Default Camera'
  268. camera.position.set(0, 0, 5)
  269. camera.userData.autoLookAtTarget = true // only for when controls are disabled / not available
  270. // Update camera controls postFrame if allowed to interact
  271. this.addEventListener('postFrame', () => { // todo: move inside RootScene.
  272. const cam = this._scene.mainCamera
  273. if (cam && cam.canUserInteract) {
  274. // todo
  275. // const d = this.getPluginByType<ProgressivePlugin>('Progressive')?.postFrameConvergedRecordingDelta()
  276. // // if (d && d > 0) delta = d
  277. // if (d !== undefined && d === 0) return // not converged yet.
  278. // // if d < 0 or undefined: not recording, do nothing
  279. cam.controls?.update()
  280. }
  281. })
  282. // if camera position or target changed in last frame, call setDirty on camera
  283. this.addEventListener('preFrame', () => { // todo: move inside RootScene.
  284. const cam = this._scene.mainCamera
  285. if (
  286. cam.getWorldPosition(this._tempVec).sub(this._lastCameraPosition).lengthSq() // position is in local space
  287. + this._tempVec.subVectors(cam.target, this._lastCameraTarget).lengthSq() // target is in world space
  288. + cam.getWorldQuaternion(this._tempQuat).angleTo(this._lastCameraQuat)
  289. > 0.000001) cam.setDirty()
  290. })
  291. // scene
  292. this._scene = new RootScene(camera, this._objectProcessor)
  293. this._scene.setBackgroundColor('#ffffff')
  294. // this._scene.addEventListener('addSceneObject', this._addSceneObject)
  295. this._scene.addEventListener('setView', this._setActiveCameraView)
  296. this._scene.addEventListener('activateMain', this._setActiveCameraView)
  297. this._scene.addEventListener('materialUpdate', (e) => this.setDirty(this._scene, e))
  298. this._scene.addEventListener('materialChanged', (e) => this.setDirty(this._scene, e))
  299. this._scene.addEventListener('objectUpdate', (e) => this.setDirty(this._scene, e))
  300. this._scene.addEventListener('textureUpdate', (e) => this.setDirty(this._scene, e))
  301. this._scene.addEventListener('sceneUpdate', (e) => {
  302. this.setDirty(this._scene, e)
  303. if (e.geometryChanged === false) return
  304. this.renderManager.resetShadows()
  305. })
  306. this._scene.addEventListener('mainCameraUpdate', () => {
  307. this._scene.mainCamera.getWorldPosition(this._lastCameraPosition)
  308. this._lastCameraTarget.copy(this._scene.mainCamera.target)
  309. this._scene.mainCamera.getWorldQuaternion(this._lastCameraQuat)
  310. })
  311. // render manager
  312. if (options.isAntialiased !== undefined || options.useRgbm !== undefined || options.useGBufferDepth !== undefined) {
  313. this.console.warn('isAntialiased, useRgbm and useGBufferDepth are deprecated, use msaa, rgbm and zPrepass instead.')
  314. }
  315. this.renderManager = new ViewerRenderManager({
  316. canvas: this._canvas,
  317. msaa: options.msaa ?? options.isAntialiased ?? false,
  318. rgbm: options.rgbm ?? options.useRgbm ?? false,
  319. zPrepass: options.zPrepass ?? options.useGBufferDepth ?? false,
  320. depthBuffer: !(options.zPrepass ?? options.useGBufferDepth ?? false),
  321. screenShader: options.screenShader,
  322. renderScale: options.renderScale,
  323. })
  324. this.renderManager.addEventListener('animationLoop', this._animationLoop as any)
  325. this.renderManager.addEventListener('resize', ()=> this._scene.mainCamera.refreshAspect())
  326. this.renderManager.addEventListener('update', (e) => {
  327. if (e.change === 'registerPass' && e.pass?.materialExtension)
  328. this.assetManager.materials.registerMaterialExtension(e.pass.materialExtension)
  329. else if (e.change === 'unregisterPass' && e.pass?.materialExtension)
  330. this.assetManager.materials.unregisterMaterialExtension(e.pass.materialExtension)
  331. this.setDirty(this.renderManager, e)
  332. })
  333. this.assetManager = new AssetManager(this, options.assetManager)
  334. if (this.resizeObserver) this.resizeObserver.observe(this._canvas)
  335. // sometimes resize observer is late, so extra check
  336. window && window.addEventListener('resize', this.resize)
  337. this._canvas.addEventListener('webglcontextrestored', this._onContextRestore, false)
  338. this._canvas.addEventListener('webglcontextlost', this._onContextLost, false)
  339. if (options.dropzone) {
  340. this.addPluginSync(new DropzonePlugin(typeof options.dropzone === 'object' ? options.dropzone : undefined))
  341. }
  342. if (options.tonemap !== false) {
  343. this.addPluginSync(new TonemapPlugin())
  344. }
  345. this.console.log('ThreePipe Viewer instance initialized, version: ', ThreeViewer.VERSION)
  346. }
  347. /**
  348. * Add an object/model/material/viewer-config/plugin-preset/... to the viewer scene from url or an {@link IAsset} object.
  349. * Same as {@link AssetManager.addAssetSingle}
  350. * @param obj
  351. * @param options
  352. */
  353. async load<T extends ImportResult = ImportResult>(obj: string | IAsset | null, options?: ImportAddOptions) {
  354. if (!obj) return
  355. return await this.assetManager.addAssetSingle<T>(obj, options)
  356. }
  357. /**
  358. * Imports an object/model/material/texture/viewer-config/plugin-preset/... to the viewer scene from url or an {@link IAsset} object.
  359. * Same as {@link AssetImporter.importSingle}
  360. * @param obj
  361. * @param options
  362. */
  363. async import<T extends ImportResult = ImportResult>(obj: string | IAsset | null, options?: ImportAddOptions) {
  364. if (!obj) return
  365. return await this.assetManager.importer.importSingle<T>(obj, options)
  366. }
  367. /**
  368. * Set the environment map of the scene from url or an {@link IAsset} object.
  369. * @param map
  370. * @param setBackground - Set the background image of the scene from the same map.
  371. * @param options - Options for importing the asset. See {@link ImportAssetOptions}
  372. */
  373. async setEnvironmentMap(map: string | IAsset | null | ITexture, {setBackground = false, ...options}: ImportAssetOptions&{setBackground?: boolean} = {}): Promise<ITexture | null> {
  374. this._scene.environment = map && !(<ITexture>map).isTexture ? await this.assetManager.importer.importSingle<ITexture>(map as string|IAsset, options) || null : <ITexture>map || null
  375. if (setBackground) return this.setBackgroundMap(this._scene.environment)
  376. return this._scene.environment
  377. }
  378. /**
  379. * Set the background image of the scene from url or an {@link IAsset} object.
  380. * @param map
  381. * @param setEnvironment - Set the environment map of the scene from the same map.
  382. * @param options - Options for importing the asset. See {@link ImportAssetOptions}
  383. */
  384. async setBackgroundMap(map: string | IAsset | null | ITexture, {setEnvironment = false, ...options}: ImportAssetOptions&{setBackground?: boolean} = {}): Promise<ITexture | null> {
  385. this._scene.background = map && !(<ITexture>map).isTexture ? await this.assetManager.importer.importSingle<ITexture>(map as string|IAsset, options) || null : <ITexture>map || null
  386. if (setEnvironment) return this.setEnvironmentMap(this._scene.background)
  387. return this._scene.background
  388. }
  389. /**
  390. * Exports an object/mesh/material/texture/render-target/plugin-preset/viewer to a blob.
  391. * If no object is given, a glb is exported with the current viewer state.
  392. * @param obj
  393. * @param options
  394. */
  395. async export(obj?: IObject3D|IMaterial|ITexture|IRenderTarget|IViewerPlugin|(typeof this), options?: ExportFileOptions) {
  396. if (!obj) obj = this._scene // this will export the glb with the scene and viewer config
  397. if ((<typeof this>obj).type === this.type) return jsonToBlob((<typeof this>obj).exportConfig())
  398. if ((<IViewerPlugin>obj).constructor?.PluginType) return jsonToBlob(this.exportPluginConfig(<IViewerPlugin>obj))
  399. return await this.assetManager.exporter.exportObject(<IObject3D|IMaterial|ITexture|IRenderTarget>obj, options)
  400. }
  401. /**
  402. * Export the scene to a file (default: glb with viewer config) and return a blob
  403. * @param options
  404. */
  405. async exportScene(options?: ExportFileOptions): Promise<BlobExt | undefined> {
  406. return this.assetManager.exporter.exportObject(this._scene.modelRoot, options)
  407. }
  408. async getScreenshotBlob({mimeType = 'image/jpeg', quality = 90} = {}): Promise<Blob | null> {
  409. const blobPromise = async()=> new Promise<Blob|null>((resolve) => {
  410. this._canvas.toBlob((blob) => {
  411. resolve(blob)
  412. }, mimeType, quality)
  413. })
  414. if (!this.renderEnabled) return blobPromise()
  415. return await this.doOnce('postFrame', async() => {
  416. this.renderEnabled = false
  417. const blob = await blobPromise()
  418. this.renderEnabled = true
  419. return blob
  420. })
  421. }
  422. async getScreenshotDataUrl({mimeType = 'image/jpeg', quality = 90} = {}): Promise<string | null> {
  423. if (!this.renderEnabled) return this._canvas.toDataURL(mimeType, quality)
  424. return await this.doOnce('postFrame', () => this._canvas.toDataURL(mimeType, quality))
  425. }
  426. /**
  427. * Disposes the viewer and frees up all resource and events. Do not use the viewer after calling dispose.
  428. * @note - If you want to reuse the viewer, set viewer.enabled to false instead, then set it to true again when required. To dispose all the objects, materials in the scene use `viewer.scene.disposeSceneModels()`
  429. * This function is not fully implemented yet. There might be some memory leaks.
  430. * @todo - return promise?
  431. */
  432. public dispose(): void {
  433. // todo: dispose stuff from constructor etc
  434. for (const plugin of [...Object.values(this.plugins)]) {
  435. this.removePlugin(plugin, true)
  436. }
  437. this._scene.dispose()
  438. this.renderManager.dispose()
  439. this._canvas.removeEventListener('webglcontextrestored', this._onContextRestore, false)
  440. this._canvas.removeEventListener('webglcontextlost', this._onContextLost, false)
  441. ;(window as any).threeViewers?.splice((window as any).threeViewers.indexOf(this), 1)
  442. if (this.resizeObserver) this.resizeObserver.unobserve(this._canvas)
  443. else window.removeEventListener('resize', this.resize)
  444. this.dispatchEvent({type: 'dispose'})
  445. }
  446. /**
  447. * Mark that the canvas is resized. If the size is changed, the renderer and all render targets are resized. This happens before the render of the next frame.
  448. */
  449. resize = () => {
  450. this._needsResize = true
  451. this.setDirty()
  452. }
  453. /**
  454. * Set the viewer to dirty and trigger render of the next frame.
  455. * @param source - The source of the dirty event. like plugin or 3d object
  456. * @param event - The event that triggered the dirty event.
  457. */
  458. setDirty(source?: any, event?: Event): void {
  459. this._needsReset = true
  460. source = source ?? this
  461. this.dispatchEvent({...event ?? {}, type: 'update', source})
  462. }
  463. protected _animationLoop(event: IAnimationLoopEvent): void {
  464. if (!this.enabled || !this.renderEnabled) return
  465. if (this._isRenderingFrame) {
  466. this.console.warn('animation loop: frame skip') // not possible actually, since this is not async
  467. return
  468. }
  469. this._isRenderingFrame = true
  470. this.renderStats.begin()
  471. for (let i = 0; i < this.maxFramePerLoop; i++) {
  472. if (this._needsReset) {
  473. this.renderManager.reset()
  474. this._needsReset = false
  475. }
  476. if (this._needsResize) {
  477. const size = [this._canvas.clientWidth, this._canvas.clientHeight]
  478. if (event.xrFrame) { // todo: find a better way to resize for XR.
  479. const cam = this.renderManager.webglRenderer.xr.getCamera()?.cameras[0]?.viewport
  480. if (cam) {
  481. if (cam.x !== 0 || cam.y !== 0) {
  482. this.console.warn('x and y must be 0?')
  483. }
  484. size[0] = cam.width
  485. size[1] = cam.height
  486. this.console.log('resize for xr', size)
  487. } else {
  488. this._needsResize = false
  489. }
  490. }
  491. if (this._needsResize) {
  492. this.renderManager.setSize(...size)
  493. this._needsResize = false
  494. }
  495. }
  496. this.dispatchEvent({...event, type: 'preFrame', target: this}) // event will have time, deltaTime and xrFrame
  497. const dirtyPlugins = Object.values(this.plugins).filter(value => value.dirty)
  498. if (dirtyPlugins.length > 0) {
  499. // console.log('dirty plugins', dirtyPlugins)
  500. this.setDirty(dirtyPlugins)
  501. }
  502. if (this._needsReset) {
  503. this.renderManager.reset()
  504. this._needsReset = false
  505. }
  506. // Check if the renderManger is dirty, which happens when it's reset above or if any pass in the composer is dirty
  507. const needsRender = this.renderManager.needsRender
  508. if (needsRender) {
  509. this.dispatchEvent({type: 'preRender', target: this})
  510. if (this.debug) this.renderManager.render(this._scene)
  511. else {
  512. try {
  513. this.renderManager.render(this._scene)
  514. } catch (e) {
  515. this.console.error(e)
  516. // this.enabled = false
  517. }
  518. }
  519. this.dispatchEvent({type: 'postRender', target: this})
  520. }
  521. this.dispatchEvent({type: 'postFrame', target: this})
  522. this.renderManager.onPostFrame()
  523. if (!needsRender) // break if no frame rendered
  524. break
  525. }
  526. this.renderStats.end()
  527. this._isRenderingFrame = false
  528. }
  529. /**
  530. * Get the Plugin by a constructor type or by the string type.
  531. * Use string type if the plugin is not a dependency and you don't want to bundle the plugin.
  532. * @param type - The class of the plugin to get, or the string type of the plugin to get which is in the static PluginType property of the plugin
  533. * @returns {T | undefined} - The plugin of the specified type.
  534. */
  535. getPlugin<T extends IViewerPlugin>(type: Class<T>|string): T | undefined {
  536. return this.plugins[typeof type === 'string' ? type : (type as any).PluginType] as T | undefined
  537. }
  538. /**
  539. * Get the Plugin by a constructor type or add a new plugin of the specified type if it doesn't exist.
  540. * @param type
  541. * @param args - arguments for the constructor of the plugin, used when a new plugin is created.
  542. */
  543. async getOrAddPlugin<T extends IViewerPlugin>(type: Class<T>, ...args: ConstructorParameters<Class<T>>): Promise<T> {
  544. const plugin = this.getPlugin(type)
  545. if (plugin) return plugin
  546. return this.addPlugin(type, ...args)
  547. }
  548. /**
  549. * Get the Plugin by a constructor type or add a new plugin to the viewer of the specified type if it doesn't exist(sync).
  550. * @param type
  551. * @param args - arguments for the constructor of the plugin, used when a new plugin is created.
  552. */
  553. getOrAddPluginSync<T extends IViewerPluginSync>(type: Class<T>, ...args: ConstructorParameters<Class<T>>): T {
  554. const plugin = this.getPlugin(type)
  555. if (plugin) return plugin
  556. return this.addPluginSync(type, ...args)
  557. }
  558. /**
  559. * Add a plugin to the viewer.
  560. * @param plugin - The instance of the plugin to add or the class of the plugin to add.
  561. * @param args - Arguments for the constructor of the plugin, in case a class is passed.
  562. * @returns {Promise<T>} - The plugin added.
  563. */
  564. async addPlugin<T extends IViewerPlugin>(plugin: T | Class<T>, ...args: ConstructorParameters<Class<T>>): Promise<T> {
  565. const p = this._resolvePluginOrClass(plugin, ...args)
  566. const type = p.constructor.PluginType
  567. if (!p.constructor.PluginType) {
  568. this.console.error('PluginType is not defined for', p)
  569. return p
  570. }
  571. for (const d of p.dependencies || []) {
  572. await this.getOrAddPlugin(d)
  573. }
  574. if (this.plugins[type]) {
  575. this.console.error(`Plugin of type ${type} already exists, removing and disposing old plugin. This might break functionality, ensure only one plugin of a type is added`, this.plugins[type], p)
  576. await this.removePlugin(this.plugins[type])
  577. }
  578. this.plugins[type] = p
  579. await p.onAdded(this)
  580. this.dispatchEvent({type: 'addPlugin', target: this, plugin: p})
  581. this.setDirty(p)
  582. return p
  583. }
  584. /**
  585. * Add a plugin to the viewer(sync).
  586. * @param plugin
  587. * @param args
  588. */
  589. addPluginSync<T extends IViewerPluginSync>(plugin: T|Class<T>, ...args: ConstructorParameters<Class<T>>): T {
  590. const p = this._resolvePluginOrClass(plugin, ...args)
  591. const type = p.constructor.PluginType
  592. if (!p.constructor.PluginType) {
  593. this.console.error('PluginType is not defined for', p)
  594. return p
  595. }
  596. for (const d of p.dependencies || []) {
  597. this.getOrAddPluginSync(d)
  598. }
  599. if (this.plugins[type]) {
  600. this.console.error(`Plugin of type ${type} already exists, removing and disposing old plugin. This might break functionality, ensure only one plugin of a type is added`, this.plugins[type], p)
  601. this.removePluginSync(this.plugins[type])
  602. }
  603. this.plugins[type] = p
  604. p.onAdded(this)
  605. this.dispatchEvent({type: 'addPlugin', target: this, plugin: p})
  606. this.setDirty(p)
  607. return p
  608. }
  609. /**
  610. * Add multiple plugins to the viewer.
  611. * @param plugins - List of plugin instances or classes
  612. */
  613. async addPlugins(plugins: (IViewerPlugin | Class<IViewerPlugin>)[]): Promise<void> {
  614. for (const p of plugins) await this.addPlugin(p)
  615. }
  616. /**
  617. * Add multiple plugins to the viewer(sync).
  618. * @param plugins - List of plugin instances or classes
  619. */
  620. addPluginsSync(plugins: (IViewerPluginSync | Class<IViewerPluginSync>)[]): void {
  621. for (const p of plugins) this.addPluginSync(p)
  622. }
  623. /**
  624. * Remove a plugin instance or a plugin class. Works similar to {@link ThreeViewer.addPlugin}
  625. * @param p
  626. * @param dispose
  627. * @returns {Promise<void>}
  628. */
  629. async removePlugin(p: IViewerPlugin<ThreeViewer, false>, dispose = true): Promise<void> {
  630. const type = p.constructor.PluginType
  631. if (!this.plugins[type]) return
  632. await p.onRemove(this)
  633. delete this.plugins[type]
  634. if (dispose) await p.dispose() // todo await?
  635. this.setDirty(p)
  636. }
  637. /**
  638. * Remove a plugin instance or a plugin class(sync). Works similar to {@link ThreeViewer.addPluginSync}
  639. * @param p
  640. * @param dispose
  641. */
  642. removePluginSync(p: IViewerPluginSync, dispose = true): void {
  643. const type = p.constructor.PluginType
  644. if (!this.plugins[type]) return
  645. p.onRemove(this)
  646. delete this.plugins[type]
  647. if (dispose) p.dispose()
  648. this.setDirty(p)
  649. }
  650. /**
  651. * Set size of the canvas and update the renderer.
  652. * If no width/height is passed, canvas is set to 100% of the container.
  653. * @param size
  654. */
  655. setSize(size?: {width?: number, height?: number}) {
  656. this._canvas.style.width = size?.width ? size.width + 'px' : '100%'
  657. this._canvas.style.height = size?.height ? size.height + 'px' : '100%'
  658. // this._canvas.style.maxWidth = '100%' // this is upto the app to do.
  659. // this._canvas.style.maxHeight = '100%'
  660. this.resize()
  661. }
  662. /**
  663. * Traverse all objects in scene model root.
  664. * @param callback
  665. */
  666. traverseSceneObjects<T extends IObject3D = IObject3D>(callback: (o: T)=>void): void {
  667. this._scene.modelRoot.traverse(callback)
  668. }
  669. /**
  670. * Add an object to the scene model root.
  671. * If an imported scene model root is passed, it will be loaded with viewer configuration, unless importConfig is false
  672. * @param imported
  673. * @param options
  674. */
  675. async addSceneObject<T extends IObject3D|Object3D|RootSceneImportResult = RootSceneImportResult>(imported: T, options?: AddObjectOptions): Promise<T> {
  676. if (imported.userData?.rootSceneModelRoot) {
  677. const obj = <RootSceneImportResult>imported
  678. if (obj.importedViewerConfig && options?.importConfig !== false) await this.importConfig(obj.importedViewerConfig)
  679. this._scene.loadModelRoot(obj, options)
  680. return this._scene.modelRoot as T
  681. }
  682. this._scene.addObject(imported, options)
  683. return imported
  684. }
  685. /**
  686. * Serialize all the plugins and their settings to save or create presets. Used in {@link toJSON}.
  687. * @param meta - The meta object.
  688. * @param filter - List of PluginType for the to include. If empty, no plugins will be serialized. If undefined, all plugins will be serialized.
  689. * @returns {any[]}
  690. */
  691. serializePlugins(meta: SerializationMetaType, filter?: string[]): any[] {
  692. if (filter && filter.length === 0) return []
  693. return Object.entries(this.plugins).map(p=> {
  694. if (filter && !filter.includes(p[1].constructor.PluginType)) return
  695. // if (!p[1].toJSON) this.console.log(`Plugin of type ${p[0]} is not serializable`)
  696. return p[1].serializeWithViewer !== false ? p[1].toJSON?.(meta) : undefined
  697. }).filter(p=> !!p)
  698. }
  699. /**
  700. * Deserialize all the plugins and their settings from a preset. Used in {@link fromJSON}.
  701. * @param plugins - The output of {@link serializePlugins}.
  702. * @param meta - The meta object.
  703. * @returns {this}
  704. */
  705. deserializePlugins(plugins: any[], meta?: SerializationMetaType): this {
  706. plugins.forEach(p=>{
  707. if (!p.type) {
  708. this.console.warn('Invalid plugin to import ', p)
  709. return
  710. }
  711. const plugin = this.getPlugin(p.type)
  712. if (!plugin) {
  713. // this.console.warn(`Plugin of type ${p.type} is not added, cannot deserialize`)
  714. return
  715. }
  716. plugin.fromJSON?.(p, meta)
  717. })
  718. return this
  719. }
  720. /**
  721. * Serialize a single plugin settings.
  722. */
  723. exportPluginConfig(plugin?: string|Class<IViewerPlugin>|IViewerPlugin): ISerializedConfig | Record<string, never> {
  724. if (plugin && typeof plugin === 'string' || (plugin as any).PluginType) plugin = this.getPlugin(plugin as any)
  725. if (!plugin) return {}
  726. const meta = getEmptyMeta()
  727. const data = (<IViewerPlugin>plugin).toJSON?.(meta)
  728. if (!data) return {}
  729. data.resources = metaToResources(meta)
  730. return data
  731. }
  732. /**
  733. * Deserialize and import a single plugin settings.
  734. * Can also use {@link ThreeViewer.importConfig} to import only plugin config.
  735. * @param json
  736. * @param plugin
  737. */
  738. async importPluginConfig(json: ISerializedConfig, plugin?: IViewerPlugin) {
  739. // this.console.log('importing plugin preset', json, plugin)
  740. const type = json.type
  741. plugin = plugin || this.getPlugin(type)
  742. if (!plugin) {
  743. this.console.warn(`No plugin found for type ${type} to import config`)
  744. return undefined
  745. }
  746. if (!plugin.fromJSON) {
  747. this.console.warn(`Plugin ${type} does not support importing presets`)
  748. return undefined
  749. }
  750. const resources = json.resources || {}
  751. if (json.resources) delete json.resources
  752. const meta = await this.loadConfigResources(resources)
  753. await plugin.fromJSON(json, meta)
  754. if (meta) json.resources = meta
  755. return plugin
  756. }
  757. /**
  758. * Serialize multiple plugin settings.
  759. * @param filter - List of PluginType to include. If empty, no plugins will be serialized. If undefined, all plugins will be serialized.
  760. */
  761. exportPluginsConfig(filter?: string[]): ISerializedViewerConfig {
  762. const meta = getEmptyMeta()
  763. const plugins = this.serializePlugins(meta, filter)
  764. convertArrayBufferToStringsInMeta(meta) // assuming not binary
  765. return {
  766. ...this._defaultConfig,
  767. plugins, resources: metaToResources(meta),
  768. }
  769. }
  770. /**
  771. * Serialize all the viewer and plugin settings.
  772. * @param binary - Indicate that the output will be converted and saved as binary data. (default: false)
  773. * @param pluginFilter - List of PluginType to include. If empty, no plugins will be serialized. If undefined, all plugins will be serialized.
  774. */
  775. exportConfig(binary = false, pluginFilter?: string[]) {
  776. return this.toJSON(binary, pluginFilter)
  777. }
  778. /**
  779. * Deserialize and import all the viewer and plugin settings, exported with {@link exportConfig}.
  780. */
  781. async importConfig(json: ISerializedConfig|ISerializedViewerConfig) {
  782. if (json.type !== this.type && <string>json.type !== 'ViewerApp') {
  783. if (this.getPlugin(json.type)) {
  784. return this.importPluginConfig(json)
  785. } else {
  786. this.console.error(`Unknown config type ${json.type} to import`)
  787. return undefined
  788. }
  789. }
  790. const resources = await this.loadConfigResources(json.resources || {})
  791. this.fromJSON(<ISerializedViewerConfig>json, resources)
  792. }
  793. /**
  794. * Serialize all the viewer and plugin settings and versions.
  795. * @param binary - Indicate that the output will be converted and saved as binary data. (default: true)
  796. * @param pluginFilter - List of PluginType to include. If empty, no plugins will be serialized. If undefined, all plugins will be serialized.
  797. * @returns {any} - Serializable JSON object.
  798. */
  799. toJSON(binary = true, pluginFilter?: string[]): ISerializedViewerConfig {
  800. const meta = getEmptyMeta()
  801. const data: ISerializedViewerConfig = Object.assign({
  802. ...this._defaultConfig,
  803. plugins: this.serializePlugins(meta, pluginFilter),
  804. }, ThreeSerialization.Serialize(this, meta, true))
  805. // this.console.log(dat)
  806. if (!binary) convertArrayBufferToStringsInMeta(meta)
  807. data.resources = metaToResources(meta)
  808. return data
  809. }
  810. /**
  811. * Deserialize all the viewer and plugin settings.
  812. * @note use async {@link ThreeViewer.importConfig} to import a json/config exported with {@link ThreeViewer.exportConfig} or {@link ThreeViewer.toJSON}.
  813. * @param data - The serialized JSON object retured from {@link toJSON}.
  814. * @param meta - The meta object
  815. * @returns {this}
  816. */
  817. fromJSON(data: ISerializedViewerConfig, meta?: SerializationMetaType): this|null {
  818. const data2: Partial<ISerializedViewerConfig> = {...data} // shallow copy
  819. // region legacy
  820. if (data2.backgroundIntensity !== undefined && data2.scene?.backgroundIntensity === undefined) {
  821. this.console.warn('old file format, backgroundIntensity moved to RootScene')
  822. this._scene.backgroundIntensity = data2.backgroundIntensity
  823. delete data2.backgroundIntensity
  824. }
  825. if (data2.useLegacyLights !== undefined && data2.renderManager?.useLegacyLights === undefined) {
  826. this.console.warn('old file format, useLegacyLights moved to RenderManager')
  827. this.renderManager.useLegacyLights = data2.useLegacyLights
  828. delete data2.useLegacyLights
  829. }
  830. if (data2.background !== undefined && data2.scene?.background === undefined) {
  831. this.console.warn('old file format, background moved to RootScene')
  832. if (data2.background === 'envMapBackground') data2.background = 'environment'
  833. else if (typeof data2.background === 'number')
  834. data2.background = new Color().setHex(data2.background, LinearSRGBColorSpace)
  835. else if (typeof data2.background === 'string')
  836. data2.background = new Color().setStyle(data2.background, LinearSRGBColorSpace)
  837. else if (data2.background?.isColor) data2.background = new Color(data2.background)
  838. if (data2.background?.isColor) { // color
  839. this._scene.backgroundColor = data2.background
  840. this._scene.background = null
  841. } else if (!data2.background) { // null
  842. this._scene.backgroundColor = null
  843. this._scene.background = null
  844. } else { // texture or 'environment'
  845. this._scene.backgroundColor = new Color('#ffffff')
  846. if (!data2.scene) data2.scene = {}
  847. data2.scene.background = data2.background
  848. }
  849. delete data2.background
  850. }
  851. // endregion
  852. if (!meta && data2.resources && data2.resources.__isLoadedResources) {
  853. meta = data2.resources as SerializationMetaType
  854. delete data2.resources
  855. }
  856. if (!meta?.__isLoadedResources) {
  857. this.console.error('meta in fromJSON is not available or is not loaded resources, call viewer.loadConfigResources first, or directly use viewer.importConfig')
  858. return null
  859. }
  860. if (Array.isArray(data2.plugins)) {
  861. this.deserializePlugins(data2.plugins, meta)
  862. delete data2.plugins
  863. }
  864. // meta = meta || data.resources
  865. ThreeSerialization.Deserialize(data2, this, meta, true)
  866. // todo: handle
  867. // __useCount set in ThreeSerialization while deserializing resources
  868. // for (const mat of Object.values(resources.materials) as any) {
  869. // if (!mat.__useCount) this.materialManager?.unregisterMaterial(mat) // todo: also dispose?
  870. // else delete mat.__useCount
  871. // }
  872. // for (const tex of Object.values(resources.textures) as any) {
  873. // if (!tex.__useCount) {
  874. // // todo: dispose?
  875. // } else {
  876. // delete tex.__useCount
  877. // }
  878. // }
  879. return this
  880. }
  881. loadConfigResources = async(json: Partial<SerializationMetaType>, extraResources?: Partial<SerializationResourcesType>): Promise<any> => {
  882. // this.console.log(json)
  883. if (json.__isLoadedResources) return json
  884. const meta = metaFromResources(json, this)
  885. return await MetaImporter.ImportMeta(meta, extraResources)
  886. }
  887. async doOnce<TRet>(event: IViewerEventTypes, func: (...args: any[]) => TRet): Promise<TRet> {
  888. return new Promise((resolve) => {
  889. const listener = async(...args: any[]) => {
  890. this.removeEventListener(event, listener)
  891. resolve(await func(...args))
  892. }
  893. this.addEventListener(event, listener)
  894. })
  895. }
  896. private _setActiveCameraView(event: any = {}): void {
  897. if (event.type === 'setView') {
  898. if (!event.camera) {
  899. this.console.warn('Cannot find camera', event)
  900. return
  901. }
  902. this._scene.mainCamera.copy(event.camera)
  903. const worldPos = event.camera.getWorldPosition(this._scene.mainCamera.position)
  904. // camera.getWorldQuaternion(this.quaternion) // todo: do if autoLookAtTarget is false
  905. if (this._scene.mainCamera.parent) {
  906. this._scene.mainCamera.position.copy(this._scene.mainCamera.parent.worldToLocal(worldPos))
  907. // this.quaternion.premultiply(this.parent.quaternion.clone().invert())
  908. }
  909. this._scene.mainCamera.setDirty()
  910. } else if (event.type === 'activateMain')
  911. this._scene.mainCamera = event.camera || undefined // event.camera should have been upgraded when added to the scene.
  912. }
  913. private _resolvePluginOrClass<T extends IViewerPlugin>(plugin: T | Class<T>, ...args: ConstructorParameters<Class<T>>): T {
  914. let p: T
  915. if ((plugin as Class<IViewerPlugin>).prototype) p = new (plugin as Class<T>)(...args)
  916. else p = plugin as T
  917. if ((plugin as Class<IViewerPlugin>).prototype) {
  918. const p1 = this.getPlugin(plugin as Class<T>)
  919. if (p1) {
  920. this.console.error(`Plugin of type ${p1.constructor.PluginType} already exists, no new plugin created`, p1)
  921. return p1
  922. }
  923. p = new (plugin as Class<T>)(...args)
  924. } else p = plugin as T
  925. return p
  926. }
  927. private _renderEnabledChanged(): void {
  928. this.dispatchEvent({type: this.renderEnabled ? 'renderEnabled' : 'renderDisabled'})
  929. }
  930. private readonly _defaultConfig: ISerializedViewerConfig = {
  931. assetType: 'config',
  932. type: this.type,
  933. version: ThreeViewer.VERSION,
  934. metadata: {
  935. generator: 'ThreePipe',
  936. version: 1,
  937. },
  938. plugins: [],
  939. }
  940. // todo: find a better fix for context loss and restore?
  941. private _lastSize = new Vector2()
  942. private _onContextRestore = (_: Event) => {
  943. this.enabled = true
  944. this._canvas.width = this._lastSize.width
  945. this._canvas.height = this._lastSize.height
  946. this.resize()
  947. this._scene.setDirty({refreshScene: true, frameFade: false})
  948. }
  949. private _onContextLost = (_: Event) => {
  950. this._lastSize.set(this._canvas.width, this._canvas.height)
  951. this._canvas.width = 2
  952. this._canvas.height = 2
  953. this.resize()
  954. this.enabled = false
  955. }
  956. // private _addSceneObject = (e: IEvent<any>) => {
  957. // if (!e || !e.object) return
  958. // const config = e.object.__importedViewerConfig // this is set in gltf.ts when gltf file is imported. This is done here so that scene settings are applied whenever the imported object is added to scene.
  959. // if (!config) return
  960. // this.fromJSON(config, config.resources)
  961. // }
  962. // todo
  963. // public async fitToView(selected?: Object3D, distanceMultiplier = 1.5, duration?: number, ease?: Easing|EasingFunctionType) {
  964. // const camViews = this.getPluginByType<CameraViewPlugin>('CameraViews')
  965. // if (!camViews) {
  966. // this.console.error('CameraViews plugin is required for fitToView to work')
  967. // return
  968. // }
  969. // await camViews?.animateToFitObject(selected, distanceMultiplier, duration, ease, {min: (this.scene.activeCamera.getControls<OrbitControls3>()?.minDistance ?? 0.5) + 0.5, max: 1000.0})
  970. // }
  971. // todo: create/load texture utils
  972. // region legacy creation functions
  973. // /**
  974. // * Converts a three.js Camera instance to be used in the viewer.
  975. // * @param camera - The three.js OrthographicCamera or PerspectiveCamera instance
  976. // * @returns {CameraController} - A wrapper around the camera with some useful methods and properties.
  977. // */
  978. // createCamera(camera: OrthographicCamera | PerspectiveCamera): CameraController {
  979. // const cam: CameraController = camera.userData.iCamera ?? new CameraController(camera, {
  980. // controlsMode: '',
  981. // controlsEnabled: false,
  982. // }, this._canvas)
  983. // if (camera.userData.autoLookAtTarget === undefined) {
  984. // cam.autoLookAtTarget = false
  985. // camera.userData.autoLookAtTarget = false
  986. // } else {
  987. // cam.autoLookAtTarget = camera.userData.autoLookAtTarget
  988. // }
  989. // return cam
  990. // }
  991. // /**
  992. // * Create a new empty object in the scene or add an existing three.js object to the scene.
  993. // * @param object
  994. // */
  995. // async createObject3D(object?: Object3D): Promise<Object3DModel | undefined> {
  996. // return this.getManager()?.addImportedSingle<Object3DModel>(object || new Object3D(), {autoScale: false, pseudoCenter: false})
  997. // }
  998. // /**
  999. // * Create a new physical material from a template or another material. It returns the same material if a material is passed created by the material manager.
  1000. // * @param material
  1001. // */
  1002. // createPhysicalMaterial(material?: Material|MeshPhysicalMaterialParameters): MeshStandardMaterial2 | undefined {
  1003. // return this.createMaterial<MeshStandardMaterial2>('standard', material)
  1004. // }
  1005. // /**
  1006. // * Create a new material from a template or another material. It returns the same material if a material is passed created by the material manager.
  1007. // * @param template - template name registered in MaterialManager
  1008. // * @param material - three.js material object or material params to create a new material
  1009. // */
  1010. // createMaterial<T extends IMaterial<any>>(template: 'standard' | 'basic' | 'diamond' | string, material?: Material|any): T | undefined {
  1011. // if ((material as Material)?.isMaterial) {
  1012. // const f = this.getManager()?.materials?.findMaterial((material as Material).uuid)
  1013. // if (f) return f as T
  1014. // }
  1015. // return this.getManager()?.materials?.generateFromTemplate(template, material) as T
  1016. // }
  1017. // endregion
  1018. /**
  1019. * The renderer for the viewer that's attached to the canvas. This is wrapper around WebGLRenderer and EffectComposer and manages post-processing passes and rendering logic
  1020. * @deprecated - use {@link renderManager} instead
  1021. */
  1022. get renderer(): ViewerRenderManager {
  1023. this.console.error('renderer is deprecated, use renderManager instead')
  1024. return this.renderManager
  1025. }
  1026. /**
  1027. * @deprecated use {@link assetManager} instead.
  1028. * Gets the Asset manager, contains useful functions for managing, loading and inserting assets.
  1029. */
  1030. getManager(): AssetManager|undefined {
  1031. return this.assetManager
  1032. }
  1033. /**
  1034. * Get the Plugin by the string type.
  1035. * @deprecated - Use {@link getPlugin} instead.
  1036. * @param type
  1037. * @returns {T | undefined}
  1038. */
  1039. getPluginByType<T extends IViewerPlugin>(type: string): T | undefined {
  1040. return this.plugins[type] as T | undefined
  1041. }
  1042. }