threepipe
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

RenderTargetPreviewPlugin.ts 7.7KB

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