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

RenderTargetPreviewPlugin.ts 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import {AViewerPluginSync, ThreeViewer} from '../../viewer'
  2. import {IRenderTarget} from '../../rendering'
  3. import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers'
  4. import {SRGBColorSpace, Vector4, WebGLRenderTarget} from 'three'
  5. import styles from './RenderTargetPreviewPlugin.css?inline'
  6. import {CustomContextMenu} from '../../utils'
  7. import {uiFolderContainer, uiToggle} from 'uiconfig.js'
  8. @uiFolderContainer('Render Target Preview Plugin')
  9. export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPluginSync<TEvent> {
  10. static readonly PluginType = 'RenderTargetPreviewPlugin'
  11. @uiToggle('Enabled')
  12. @onChange(RenderTargetPreviewPlugin.prototype.refreshUi) enabled = true
  13. toJSON: any = null
  14. mainDiv: HTMLDivElement = createDiv({id: 'RenderTargetPreviewPluginContainer', addToBody: false})
  15. stylesheet?: HTMLStyleElement
  16. constructor(enabled = true) {
  17. super()
  18. this.enabled = enabled
  19. }
  20. targetBlocks: {
  21. target: ValOrFunc<IRenderTarget|undefined>
  22. name: string
  23. visible: boolean
  24. transparent: boolean
  25. originalColorSpace: boolean
  26. div: HTMLDivElement
  27. }[] = []
  28. onAdded(viewer: ThreeViewer): void {
  29. super.onAdded(viewer)
  30. viewer.addEventListener('postRender', this._postRender)
  31. this.stylesheet = createStyles(styles, viewer.container)
  32. this.refreshUi()
  33. }
  34. onRemove(viewer: ThreeViewer): void {
  35. viewer.removeEventListener('postRender', this._postRender)
  36. this.stylesheet?.remove()
  37. this.stylesheet = undefined
  38. this.refreshUi()
  39. super.onRemove(viewer)
  40. }
  41. private _postRender = () => {
  42. if (!this._viewer) return
  43. for (const target of this.targetBlocks) {
  44. if (!target.visible) continue
  45. const rt = getOrCall(target.target)
  46. if (!rt) {
  47. // todo draw white or pink
  48. continue
  49. }
  50. const rect = target.div.getBoundingClientRect()
  51. const tex = rt.texture
  52. const canvasRect = this._viewer.canvas.getBoundingClientRect()
  53. rect.x = rect.x - canvasRect.x
  54. rect.y = canvasRect.height + canvasRect.y - rect.y - rect.height
  55. if (Array.isArray(tex)) {
  56. // todo support multi target
  57. this._viewer.console.warn('Multi target preview not supported yet')
  58. continue
  59. }
  60. const outputColorSpace = this._viewer.renderManager.webglRenderer.outputColorSpace
  61. if (!target.originalColorSpace) this._viewer.renderManager.webglRenderer.outputColorSpace = SRGBColorSpace
  62. this._viewer.renderManager.blit(null, {
  63. source: tex,
  64. clear: !target.transparent,
  65. respectColorSpace: !target.originalColorSpace,
  66. viewport: new Vector4(rect.x, rect.y, rect.width, rect.height),
  67. })
  68. this._viewer.renderManager.webglRenderer.outputColorSpace = outputColorSpace
  69. }
  70. }
  71. addTarget(target: ValOrFunc<IRenderTarget|undefined>, name: string, transparent = false, originalColorSpace = false, visible = true): this {
  72. if (!target) return this
  73. const div = document.createElement('div')
  74. const targetDef = {target, name, transparent, div, originalColorSpace, visible}
  75. div.classList.add('RenderTargetPreviewPluginTarget')
  76. if (!targetDef.visible) div.classList.add('RenderTargetPreviewPluginCollapsed')
  77. const header = document.createElement('div')
  78. header.classList.add('RenderTargetPreviewPluginTargetHeader')
  79. header.innerText = name
  80. header.onclick = () => {
  81. targetDef.visible = !targetDef.visible
  82. if (!targetDef.visible) div.classList.add('RenderTargetPreviewPluginCollapsed')
  83. else div.classList.remove('RenderTargetPreviewPluginCollapsed')
  84. this._viewer?.setDirty()
  85. }
  86. header.oncontextmenu = (e) => {
  87. e.preventDefault()
  88. e.stopPropagation()
  89. CustomContextMenu.Create({
  90. 'Download': () => this.downloadTarget(target),
  91. 'Remove': () => this.removeTarget(target),
  92. }, e.clientX, e.clientY)
  93. }
  94. div.appendChild(header)
  95. this.mainDiv.appendChild(div)
  96. this.targetBlocks.push(targetDef)
  97. this.refreshUi()
  98. return this
  99. }
  100. removeTarget(target: ValOrFunc<IRenderTarget|undefined>): this {
  101. const index = this.targetBlocks.findIndex(t => t.target === target)
  102. if (index >= 0) {
  103. const t = this.targetBlocks[index]
  104. this.targetBlocks.splice(index, 1)
  105. t.div.remove()
  106. }
  107. this.refreshUi()
  108. return this
  109. }
  110. downloadTarget(target1: ValOrFunc<IRenderTarget|undefined>): this {
  111. if (!this._viewer) return this
  112. const target = getOrCall(target1)
  113. if (!target) return this
  114. const tex = target.texture
  115. if (Array.isArray(tex)) {
  116. // todo support multi target
  117. this._viewer.dialog.alert('Multi target not supported yet')
  118. this._viewer.console.warn('support multi target export')
  119. return this
  120. }
  121. const canvas = this._viewer?.canvas
  122. if (!canvas) return this
  123. const blob = this._viewer.renderManager.exportRenderTarget(target as WebGLRenderTarget)
  124. const url = URL.createObjectURL(blob)
  125. const link = document.createElement('a')
  126. document.body.appendChild(link)
  127. link.style.display = 'none'
  128. link.href = url
  129. link.download = 'renderTarget.' + (blob.ext || 'png')
  130. link.click()
  131. document.body.removeChild(link)
  132. URL.revokeObjectURL(url)
  133. return this
  134. }
  135. refreshUi(): void {
  136. if (!this.mainDiv) return
  137. if (!this._viewer) {
  138. if (this.mainDiv.parentElement) this.mainDiv.remove()
  139. this.mainDiv.style.display = 'none'
  140. this.mainDiv.style.zIndex = '1000'
  141. return
  142. }
  143. if (!this.mainDiv.parentElement) this._viewer.container?.appendChild(this.mainDiv)
  144. this.mainDiv.style.display = !this.isDisabled() ? 'flex' : 'none'
  145. this.mainDiv.style.zIndex = parseInt(this._viewer.canvas.style.zIndex || '0') + 1 + ''
  146. this._viewer?.setDirty()
  147. }
  148. setDirty() { // for enable/disable functions
  149. this.refreshUi()
  150. }
  151. dispose() {
  152. for (const target of this.targetBlocks) {
  153. this.removeTarget(target.target)
  154. }
  155. super.dispose()
  156. }
  157. }