|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- import {LinearFilter, WebGLRenderTarget} from 'three'
- import {IPassID, IPipelinePass} from '../../postprocessing'
- import {ThreeViewer} from '../../viewer'
- import {PipelinePassPlugin} from '../base/PipelinePassPlugin'
- import {uiFolderContainer, uiToggle} from 'uiconfig.js'
- import {ITexture, IWebGLRenderer} from '../../core'
- import {AddBlendTexturePass} from '../../postprocessing/AddBlendTexturePass'
- import {now, serialize, timeout, ValOrFunc} from 'ts-browser-helpers'
- import {ProgressivePlugin} from './ProgressivePlugin'
- import {IRenderTarget} from '../../rendering'
-
- export type FrameFadePluginEventTypes = ''
-
- /**
- * FrameFade Plugin
- *
- * Adds a post-render pass to smoothly fade to a new rendered frame over time.
- * This is useful for example when changing the camera position, material, object properties, etc to avoid a sudden jump.
- * @category Plugins
- */
- @uiFolderContainer('FrameFade Plugin')
- export class FrameFadePlugin
- extends PipelinePassPlugin<FrameFadeBlendPass, 'frameFade', FrameFadePluginEventTypes> {
-
- readonly passId = 'frameFade'
- public static readonly PluginType = 'FrameFadePlugin'
-
- dependencies = [ProgressivePlugin]
-
- @serialize() @uiToggle() fadeOnActiveCameraChange = true
- @serialize() @uiToggle() fadeOnMaterialUpdate = true
- @serialize() @uiToggle() fadeOnSceneUpdate = true
-
- protected _pointerEnabled = true
- protected _target?: IRenderTarget
-
- constructor(
- enabled = true,
- ) {
- super()
- this.enabled = enabled
- this.startTransition = this.startTransition.bind(this)
- this.stopTransition = this.stopTransition.bind(this)
- this._fadeCam = this._fadeCam.bind(this)
- this._fadeMat = this._fadeMat.bind(this)
- this.isDisabled = ((sup)=>()=>!this._pointerEnabled || sup())(this.isDisabled)
- }
-
- public async startTransition(duration: number) { // duration in ms
- if (!this._viewer || !this._pass || this.isDisabled()) return
- if (!this._target)
- this._target = this._viewer.renderManager.getTempTarget({
- sizeMultiplier: 1.,
- minFilter: LinearFilter,
- magFilter: LinearFilter,
- colorSpace: (this._viewer.renderManager.composerTarget.texture as ITexture).colorSpace,
- })
- this._pass.fadeTimeState = Math.max(duration, this._pass.fadeTimeState)
- this._pass.fadeTime = this._pass.fadeTimeState
- if (this._pass.fadeTimeState < 500) // only save if very near the end
- this._pass.toSaveFrame = true
- // this._pass.passObject.enabled = true
- this.setDirty()
- await timeout(duration)
- }
- public stopTransition() {
- if (!this._pass) return
- this._pass.fadeTimeState = 0. // will be stopped in update on next frame
- }
-
- onAdded(viewer: ThreeViewer) {
- super.onAdded(viewer)
- viewer.scene.addEventListener('mainCameraUpdate', this.stopTransition)
- viewer.scene.addEventListener('mainCameraChange', this._fadeCam)
- viewer.scene.addEventListener('materialUpdate', this._fadeMat)
- viewer.scene.addEventListener('sceneUpdate', this._fadeScene)
- viewer.scene.addEventListener('objectUpdate', this._fadeObjectUpdate)
- window.addEventListener('pointermove', this._onPointerMove)
- }
-
- onRemove(viewer: ThreeViewer) {
- viewer.scene.removeEventListener('mainCameraUpdate', this.stopTransition)
- viewer.scene.removeEventListener('mainCameraChange', this._fadeCam)
- viewer.scene.removeEventListener('materialUpdate', this._fadeMat)
- viewer.scene.removeEventListener('sceneUpdate', this._fadeScene)
- viewer.scene.removeEventListener('objectUpdate', this._fadeObjectUpdate)
- window.removeEventListener('pointermove', this._onPointerMove)
- super.onRemove(viewer)
- }
-
-
- private _fadeCam = async(ev: any)=>
- ev.frameFade !== false && this.fadeOnActiveCameraChange && this.startTransition(ev.fadeDuration || 1000)
- private _fadeMat = async(ev: any)=>
- ev.frameFade !== false && this.fadeOnMaterialUpdate && this.startTransition(ev.fadeDuration || 200)
- private _fadeScene = async(ev: any)=>
- ev.frameFade !== false && this.fadeOnSceneUpdate && this.startTransition(ev.fadeDuration || 500)
- private _fadeObjectUpdate = async(ev: any)=>
- ev.frameFade && this.startTransition(ev.fadeDuration || 500)
-
- private _onPointerMove = (ev: PointerEvent)=> {
- const canvas = this._viewer?.canvas
- if (!canvas) {
- this._pointerEnabled = false
- return
- }
-
- // no button is pressed
- if (!ev.buttons || ev.target !== canvas) {
- this._pointerEnabled = true
- return
- }
-
- // check if pointer is over canvas
- const rect = canvas.getBoundingClientRect()
- const x = (ev.clientX - rect.left) / rect.width
- const y = (ev.clientY - rect.top) / rect.height
- this._pointerEnabled = x < 0 || x > 1 || y < 0 || y > 1
-
- }
-
- setDirty() {
- if (this.isDisabled()) return
- this._viewer?.setDirty()
- }
-
- get dirty() {
- return !this.isDisabled() && !!this._pass && this._pass.fadeTimeState > 0
- }
-
- set dirty(_: boolean) {
- console.error('FrameFadePlugin.dirty is readonly')
- }
-
- protected _createPass() {
- return new FrameFadeBlendPass(this.passId, this)
- }
-
- get canFrameFade() {
- return this._target && this._pointerEnabled &&
- this.dirty && this._pass &&
- this._pass.fadeTimeState > 0.001 &&
- this._viewer && this._viewer.scene.renderCamera === this._viewer.scene.mainCamera
- }
-
- get lastFrame() {
- return this._viewer?.getPlugin(ProgressivePlugin)?.texture
- }
-
- get target() {
- return this._target
- }
-
- protected _beforeRender(): boolean {
- if (!super._beforeRender() || !this._pass) return false
-
- if (this.isDisabled()) this.stopTransition()
-
- if (this._pass.fadeTimeState < 0.001) {
- this._pass.toSaveFrame = false
- if (this._target && this._viewer) {
- this._viewer.renderManager.releaseTempTarget(this._target)
- this._target = undefined
- }
- }
- return true
- }
-
- }
-
- class FrameFadeBlendPass extends AddBlendTexturePass implements IPipelinePass {
- before = ['progressive', 'taa']
- after = ['render']
- required = ['render', 'progressive']
- dirty: ValOrFunc<boolean> = () => false
-
-
- fadeTime = 0 // ms
- fadeTimeState = 0
- toSaveFrame = false
-
- private _lastTime = 0
-
-
- constructor(public readonly passId: IPassID, public plugin: FrameFadePlugin) {
- super()
- }
- render(renderer: IWebGLRenderer, writeBuffer: WebGLRenderTarget, readBuffer: WebGLRenderTarget, deltaTime: number, maskActive: boolean) {
- this.needsSwap = false
- const target = this.plugin.target
- if (!this.plugin.canFrameFade || !target) return
-
- const lastFrame = this.plugin.lastFrame
- if (this.toSaveFrame && lastFrame) {
- renderer.renderManager.blit(target, {source: lastFrame, respectColorSpace: false})
- this._lastTime = 0
- this.toSaveFrame = false
- }
-
- this.uniforms.tDiffuse2.value = target.texture
-
- const weight = this.fadeTimeState / this.fadeTime
- this.uniforms.weight2.value.setScalar(weight)
- this.uniforms.weight2.value.w = 1
- this.uniforms.weight.value.setScalar(1. - weight)
- this.uniforms.weight.value.w = 1
- super.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive)
- this.needsSwap = true
-
- const time = now()
- if (this._lastTime < 10) this._lastTime = time - 10 // ms
- const dt = time - this._lastTime
- this._lastTime = time
-
- this.fadeTimeState -= dt
- }
-
- }
|