UNPKG

threepipe

Version:

A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.

209 lines 9.25 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { LinearFilter } from 'three'; import { PipelinePassPlugin } from '../base/PipelinePassPlugin'; import { uiFolderContainer, uiToggle } from 'uiconfig.js'; import { AddBlendTexturePass } from '../../postprocessing/AddBlendTexturePass'; import { now, serialize, timeout } from 'ts-browser-helpers'; import { ProgressivePlugin } from './ProgressivePlugin'; /** * 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 */ let FrameFadePlugin = class FrameFadePlugin extends PipelinePassPlugin { constructor(enabled = true) { super(); this.passId = 'frameFade'; this.dependencies = [ProgressivePlugin]; // disables fadeOn... options but not serialized this.isEditor = false; this.fadeOnActiveCameraChange = true; this.fadeOnMaterialUpdate = true; this.fadeOnSceneUpdate = true; this._pointerEnabled = true; this.saveFrameTimeThreshold = 500; // ms this._fadeCam = async (ev) => ev.frameFade !== false && !this.isEditor && this.fadeOnActiveCameraChange && this.startTransition(ev.fadeDuration || 1000); this._fadeMat = async (ev) => ev.frameFade !== false && !this.isEditor && this.fadeOnMaterialUpdate && this.startTransition(ev.fadeDuration || 200); this._fadeScene = async (ev) => ev.frameFade !== false && !this.isEditor && this.fadeOnSceneUpdate && this.startTransition(ev.fadeDuration || 500); this._fadeObjectUpdate = async (ev) => ev.frameFade && !this.isEditor && this.startTransition(ev.fadeDuration || 500); this._onPointerMove = (ev) => { 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; }; 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); } /** * Start a frame fade transition. * Note that the current frame data will only be used if the last running transition is ended or near the end. To do it anyway, call {@link stopTransition} first * @param duration */ async startTransition(duration) { 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.colorSpace, }); if (this._pass.fadeTimeState < this.saveFrameTimeThreshold) // only save if very near the end this._pass.toSaveFrame = true; this._pass.fadeTimeState = Math.max(duration, this._pass.fadeTimeState); this._pass.fadeTime = this._pass.fadeTimeState; // this._pass.enabled = true this.setDirty(); await timeout(duration); } /** * Stop a frame fade transition if running. Note that it will be stopped next frame. */ stopTransition() { if (!this._pass) return; this._pass.fadeTimeState = 0.; // will be stopped in update on next frame } onAdded(viewer) { 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); // has to be on window } onRemove(viewer) { 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); } setDirty() { super.setDirty(); if (this.isDisabled()) return; this._viewer?.setDirty(); } get dirty() { return !this.isDisabled() && !!this._pass && this._pass.fadeTimeState > 0; } set dirty(_) { console.error('FrameFadePlugin.dirty is readonly'); } _createPass() { return new FrameFadeBlendPass(this.passId, this, this._viewer?.renderManager.maxHDRIntensity); } 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; } _beforeRender() { 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; } }; FrameFadePlugin.PluginType = 'FrameFadePlugin'; __decorate([ serialize(), uiToggle() ], FrameFadePlugin.prototype, "fadeOnActiveCameraChange", void 0); __decorate([ serialize(), uiToggle() ], FrameFadePlugin.prototype, "fadeOnMaterialUpdate", void 0); __decorate([ serialize(), uiToggle() ], FrameFadePlugin.prototype, "fadeOnSceneUpdate", void 0); FrameFadePlugin = __decorate([ uiFolderContainer('FrameFade Plugin') ], FrameFadePlugin); export { FrameFadePlugin }; export class FrameFadeBlendPass extends AddBlendTexturePass { constructor(passId, plugin, maxIntensity = 120) { super(undefined, maxIntensity); this.passId = passId; this.plugin = plugin; this.before = ['progressive', 'taa']; this.after = ['render']; this.required = ['render', 'progressive']; this.dirty = () => false; this.fadeTime = 0; // ms this.fadeTimeState = 0; this.toSaveFrame = false; this._lastTime = 0; } render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) { 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; } } //# sourceMappingURL=FrameFadePlugin.js.map