| @@ -120,7 +120,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| @uiButton('Reset To First View') | |||
| public async resetToFirstView(duration = 100) { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| this._currentView = undefined | |||
| await this.animateToView(0, duration) | |||
| await timeout(2) | |||
| @@ -128,7 +128,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| @uiButton('Add Current View') | |||
| async addCurrentView() { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| const camera = this._viewer?.scene.mainCamera | |||
| if (!camera) return | |||
| const view = this.getView(camera) | |||
| @@ -280,7 +280,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| @uiButton('Animate All Views') | |||
| async animateAllViews() { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| if (this.viewLooping || this._cameraViews.length < 2) return | |||
| while (this._viewQueue.length > 0) this._viewQueue.pop() | |||
| this._viewQueue.push(...this._cameraViews) | |||
| @@ -374,7 +374,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| if (this._animationLooping) return | |||
| this._animationLooping = true | |||
| while (this.viewLooping || !this._infiniteLooping) { | |||
| if (!this.enabled) break | |||
| if (this.isDisabled()) break | |||
| if (this._cameraViews.length < 1) break | |||
| if (this._viewQueue.length === 0) { | |||
| if (this._infiniteLooping) this._viewQueue.push(...this._cameraViews) | |||
| @@ -422,7 +422,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| // * For slight rotation of camera when seekOnScroll is enabled | |||
| // */ | |||
| // private _pointerMove(ev: PointerEvent) { | |||
| // if (!this.enabled) return | |||
| // if (this.isDisabled()) return | |||
| // if (!this._animating && this.seekOnScroll) { | |||
| // const cam = this._viewer?.scene.mainCamera | |||
| // if (!cam) return | |||
| @@ -453,7 +453,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| // private _scrollAnimationState = 0 | |||
| // scrollAnimationDamping = 0.1 | |||
| // private _wheel(ev: any | WheelEvent) { | |||
| // if (!this.enabled) return | |||
| // if (this.isDisabled()) return | |||
| // if (this.seekOnScroll && !this._animating) { | |||
| // // if (ev.deltaY > 0) this.focusNext(false) | |||
| // // else this.focusPrevious(false) | |||
| @@ -475,7 +475,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| // todo: same code used in PopmotionPlugin, merge somehow | |||
| // private _postFrame() { | |||
| // if (!this._viewer) return | |||
| // if (!this.enabled || !this._animating) { | |||
| // if (this.isDisabled() || !this._animating) { | |||
| // this._lastFrameTime = 0 | |||
| // if (this._fadeDisabled) { | |||
| // this._viewer.getPluginByType<FrameFadePlugin>('FrameFade')?.enable(CameraViewPlugin.PluginType) | |||
| @@ -526,7 +526,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| // @uiButton('Record All Views') | |||
| // public async recordAllViews(onStart?: ()=>void, downloadOnEnd = true) { | |||
| // if (!this.enabled) return | |||
| // if (this.isDisabled()) return | |||
| // const recorder = this._viewer?.getPluginByType<CanvasRecorderPlugin>('CanvasRecorder') | |||
| // if (!recorder || !recorder.enabled) return | |||
| // if (this._cameraViews.length < 1) return | |||
| @@ -237,7 +237,7 @@ export class GLTFAnimationPlugin extends AViewerPluginSync<'checkpointEnd'|'chec | |||
| * @param animations - play specific animations, otherwise play all animations. Note: the promise returned (if this is set) from this will resolve before time if the animations was ever paused, or converged mode is on in recorder. | |||
| */ | |||
| async playAnimation(resetOnEnd = false, animations?: AnimationAction[]): Promise<void> { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| let wasPlaying = false | |||
| if (this._animationState === 'playing') { | |||
| this.stopAnimation(false) // stop and play again. reset is done below. | |||
| @@ -349,7 +349,7 @@ export class GLTFAnimationPlugin extends AViewerPluginSync<'checkpointEnd'|'chec | |||
| this._lastAnimId = '' | |||
| if (this._viewer && this._fadeDisabled) { | |||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(GLTFAnimationPlugin.PluginType) | |||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(this) | |||
| this._fadeDisabled = false | |||
| } | |||
| @@ -378,11 +378,11 @@ export class GLTFAnimationPlugin extends AViewerPluginSync<'checkpointEnd'|'chec | |||
| const pageScrollAnimate = this.animateOnPageScroll // && this._animationState === 'paused' | |||
| const dragAnimate = this.animateOnDrag // && this._animationState === 'paused' | |||
| if (!this.enabled || this.animations.length < 1 || this._animationState !== 'playing' && !scrollAnimate && !dragAnimate && !pageScrollAnimate) { | |||
| if (this.isDisabled() || this.animations.length < 1 || this._animationState !== 'playing' && !scrollAnimate && !dragAnimate && !pageScrollAnimate) { | |||
| this._lastFrameTime = 0 | |||
| // console.log('not anim') | |||
| if (this._fadeDisabled) { | |||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(GLTFAnimationPlugin.PluginType) | |||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(this) | |||
| this._fadeDisabled = false | |||
| } | |||
| return | |||
| @@ -520,18 +520,18 @@ export class GLTFAnimationPlugin extends AViewerPluginSync<'checkpointEnd'|'chec | |||
| } | |||
| private _scroll() { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| this._pageScrollAnimationState = this.pageScrollTime - this.animationTime | |||
| } | |||
| private _wheel({deltaY}: any | WheelEvent) { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| if (Math.abs(deltaY) > 0.001) | |||
| this._scrollAnimationState = -1. * Math.sign(deltaY) | |||
| } | |||
| private _drag(ev: any) { | |||
| if (!this.enabled || !this._viewer) return | |||
| if (this.isDisabled() || !this._viewer) return | |||
| this._dragAnimationState = this.dragAxis === 'x' ? | |||
| ev.delta.x * this._viewer.canvas.width / 4 : | |||
| ev.delta.y * this._viewer.canvas.height / 4 | |||
| @@ -57,11 +57,11 @@ export class PopmotionPlugin extends AViewerPluginSync<''> { | |||
| // Same code used in CameraViewPlugin | |||
| private _postFrame = ()=>{ | |||
| if (!this._viewer) return | |||
| if (!this.enabled || Object.keys(this.animations).length < 1) { | |||
| if (this.isDisabled() || Object.keys(this.animations).length < 1) { | |||
| this._lastFrameTime = 0 | |||
| // console.log('not anim') | |||
| if (this._fadeDisabled) { | |||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(PopmotionPlugin.PluginType) | |||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(this) | |||
| this._fadeDisabled = false | |||
| } | |||
| return | |||
| @@ -96,7 +96,7 @@ export class PopmotionPlugin extends AViewerPluginSync<''> { | |||
| if (!this._fadeDisabled && this.disableFrameFade) { | |||
| const ff = this._viewer.getPlugin<FrameFadePlugin>('FrameFade') | |||
| if (ff) { | |||
| ff.disable(PopmotionPlugin.PluginType) | |||
| ff.disable(this) | |||
| this._fadeDisabled = true | |||
| } | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| import {IPassID, IPipelinePass} from '../../postprocessing' | |||
| import {AViewerPluginSync, ISerializedConfig, ThreeViewer} from '../../viewer' | |||
| import {serialize, wrapThisFunction} from 'ts-browser-helpers' | |||
| import {onChange, serialize, wrapThisFunction} from 'ts-browser-helpers' | |||
| import {SerializationMetaType} from '../../utils' | |||
| import {uiConfig, uiToggle} from 'uiconfig.js' | |||
| @@ -9,15 +9,8 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| @serialize() | |||
| @uiToggle('Enabled') | |||
| get enabled(): boolean { | |||
| return this._pass?.enabled || this._enabledTemp | |||
| } | |||
| set enabled(value: boolean) { | |||
| if (this._pass) this._pass.enabled = value | |||
| this._enabledTemp = value | |||
| this.setDirty() | |||
| } | |||
| @onChange(PipelinePassPlugin.prototype.setDirty) | |||
| enabled = true | |||
| @uiConfig() | |||
| @serialize('pass') | |||
| @@ -27,10 +20,12 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| /** | |||
| * This function is called every frame before composer render, if this pass is being used in the pipeline | |||
| * @param _ | |||
| * @protected | |||
| */ | |||
| protected _beforeRender(): boolean {return this._pass?.enabled && this.enabled || false} | |||
| private _enabledTemp = true // to save enabled state when pass is not yet created | |||
| protected _beforeRender(): boolean { | |||
| if (!this._pass) return false | |||
| this._pass.enabled = !this.isDisabled() | |||
| return this._pass.enabled | |||
| } | |||
| constructor() { | |||
| super() | |||
| @@ -43,7 +38,6 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| this._pass.onDirty?.push(viewer.setDirty) | |||
| this._pass.beforeRender = wrapThisFunction(this._beforeRender, this._pass.beforeRender) | |||
| viewer.renderManager.registerPass(this._pass) | |||
| this.enabled = this._enabledTemp | |||
| } | |||
| onRemove(viewer: TViewer): void { | |||
| @@ -66,6 +60,7 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| } | |||
| setDirty() { | |||
| if (this._pass) this._pass.enabled = !this.isDisabled() | |||
| this._viewer?.setDirty() | |||
| this.uiConfig?.uiRefresh?.(true, 'postFrame', 100) // adding delay for a few frames, so render target(if any can update) | |||
| } | |||
| @@ -10,29 +10,29 @@ export class HDRiGroundPlugin extends AViewerPluginSync<'', ThreeViewer> { | |||
| static readonly PluginType = 'HDRiGroundPlugin' | |||
| @serialize() | |||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||
| @uiToggle('Enabled') | |||
| enabled = false | |||
| @serialize() | |||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||
| @uiSlider('World Radius', [1, 1000], 0.01) | |||
| worldRadius = 100 | |||
| @serialize() | |||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||
| @uiSlider('Tripod height', [0, 50], 0.01) | |||
| tripodHeight = 10 | |||
| @serialize() | |||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||
| @uiVector('Origin Position', undefined, 0.001, (t: HDRiGroundPlugin)=>({ | |||
| onChange: t._paramsChanged, // this is for x, y, z values. | |||
| onChange: t.setDirty, // this is for x, y, z values. | |||
| })) | |||
| originPosition = new Vector3(0, 0, 0) | |||
| @serialize() | |||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||
| promptOnBackgroundMismatch = true | |||
| // todo | |||
| @@ -40,20 +40,20 @@ export class HDRiGroundPlugin extends AViewerPluginSync<'', ThreeViewer> { | |||
| // * Automatically set the origin position based on the ground position in GroundPlugin | |||
| // */ | |||
| // @serialize() | |||
| // @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||
| // @onChange(HDRiGroundPlugin.prototype.setDirty) | |||
| // @uiToggle('Auto Ground Position') | |||
| // autoGroundPosition = false | |||
| constructor(enabled = false, promptOnBackgroundMismatch = true) { | |||
| super() | |||
| this._paramsChanged = this._paramsChanged.bind(this) | |||
| this.setDirty = this.setDirty.bind(this) | |||
| this.enabled = enabled | |||
| this.promptOnBackgroundMismatch = promptOnBackgroundMismatch | |||
| this.addEventListener('deserialize', this._paramsChanged) | |||
| this.addEventListener('deserialize', this.setDirty) | |||
| } | |||
| private _paramsChanged() { | |||
| setDirty() { | |||
| if (!this._viewer) return | |||
| const bg = this._viewer.scene.background | |||
| if (this.enabled && bg !== this._viewer.scene.environment && bg !== 'environment') { | |||
| @@ -80,9 +80,9 @@ export class HDRiGroundPlugin extends AViewerPluginSync<'', ThreeViewer> { | |||
| unif.worldRadius.value = this.worldRadius | |||
| unif.originPosition.value.copy(this.originPosition) | |||
| if (cubeMat) { | |||
| if (!this.enabled && cubeMat.defines.HDRi_GROUND_PROJ) | |||
| if (this.isDisabled() && cubeMat.defines.HDRi_GROUND_PROJ) | |||
| delete cubeMat.defines.HDRi_GROUND_PROJ | |||
| else if (this.enabled) | |||
| else if (!this.isDisabled()) | |||
| cubeMat.defines.HDRi_GROUND_PROJ = '1' | |||
| cubeMat.needsUpdate = true | |||
| } | |||
| @@ -111,7 +111,8 @@ vWorldDirection | |||
| `) | |||
| } | |||
| viewer.scene.addEventListener('environmentChanged', this._paramsChanged) | |||
| viewer.scene.addEventListener('environmentChanged', this.setDirty) | |||
| } | |||
| } | |||
| @@ -84,7 +84,7 @@ export class DropzonePlugin extends AViewerPluginSync<'drop'> { | |||
| */ | |||
| @uiButton('Select files') | |||
| public promptForFile(): void { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| this.allowedExtensions = this._allowedExtensions | |||
| this._inputEl?.click() | |||
| } | |||
| @@ -121,7 +121,7 @@ export class DropzonePlugin extends AViewerPluginSync<'drop'> { | |||
| private async _onFileDrop({files, nativeEvent}: {files: Map<string, File>, nativeEvent: DragEvent}) { | |||
| if (!files) return | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| const viewer = this._viewer | |||
| if (!viewer) return | |||
| if (this._allowedExtensions !== undefined) { | |||
| @@ -8,12 +8,12 @@ export class EditorViewWidgetPlugin extends AViewerPluginSync<''> { | |||
| public static readonly PluginType = 'EditorViewWidgetPlugin' | |||
| @uiToggle() | |||
| @onChange(EditorViewWidgetPlugin.prototype._enableChange) | |||
| @onChange(EditorViewWidgetPlugin.prototype.setDirty) | |||
| enabled = true | |||
| protected _enableChange() { | |||
| setDirty() { | |||
| if (!this._viewer || !this.widget) return | |||
| this.widget.domContainer.style.display = this.enabled ? 'block' : 'none' | |||
| this.widget.domContainer.style.display = !this.isDisabled() ? 'block' : 'none' | |||
| } | |||
| constructor(public readonly placement: DomPlacement = 'top-left', public readonly size = 128) { | |||
| @@ -56,11 +56,11 @@ export class EditorViewWidgetPlugin extends AViewerPluginSync<''> { | |||
| protected _needsRender = false | |||
| protected _viewerListeners = { | |||
| postRender: (_: IViewerEvent)=>{ | |||
| if (!this._viewer || !this.widget || !this.enabled) return | |||
| if (!this._viewer || !this.widget || this.isDisabled()) return | |||
| this._needsRender = true | |||
| }, | |||
| postFrame: (_: IViewerEvent)=>{ | |||
| if (!this._viewer || !this.widget || !this.enabled || !this._needsRender) return | |||
| if (!this._viewer || !this.widget || this.isDisabled() || !this._needsRender) return | |||
| this.widget.update() | |||
| this.widget.render() | |||
| if (this.widget.animating) this._viewer.scene.mainCamera.setDirty() | |||
| @@ -8,14 +8,9 @@ import {FrameFadePlugin} from '../pipeline/FrameFadePlugin' | |||
| export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> { | |||
| @serialize() | |||
| @onChange(PickingPlugin.prototype._enableChange) | |||
| @onChange(PickingPlugin.prototype.setDirty) | |||
| enabled = true | |||
| private _enableChange() { | |||
| if (!this._viewer) return | |||
| if (!this.enabled) this.setSelectedObject(undefined) // todo | |||
| } | |||
| get picker(): ObjectPicker|undefined { | |||
| return this._picker | |||
| } | |||
| @@ -54,8 +49,10 @@ export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'ho | |||
| this.uiConfig?.uiRefresh?.(true) | |||
| } | |||
| public setDirty() { | |||
| this._viewer?.setDirty() | |||
| setDirty() { | |||
| if (!this._viewer) return | |||
| if (this.isDisabled()) this.setSelectedObject(undefined) // todo | |||
| this._viewer.setDirty() | |||
| } | |||
| constructor(selection: Class<SelectionWidget>|undefined = BoxSelectionWidget, pickUi = true, autoFocus = false) { | |||
| super() | |||
| @@ -73,12 +70,12 @@ export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'ho | |||
| } | |||
| getSelectedObject<T extends IObject3D = IObject3D>(): T|undefined { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| return this._picker?.selectedObject as T || undefined | |||
| } | |||
| setSelectedObject(object: IObject3D|undefined, focusCamera = false) { // todo: listen to object dispose | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| if (!this._picker) return | |||
| const t = this.autoFocus | |||
| this.autoFocus = false | |||
| @@ -89,7 +86,7 @@ export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'ho | |||
| onAdded(viewer: ThreeViewer): void { | |||
| super.onAdded(viewer) | |||
| this._enableChange() | |||
| this.setDirty() | |||
| this._picker = new ObjectPicker(viewer.scene.modelRoot, viewer.canvas, viewer.scene.mainCamera, (obj)=>{ | |||
| const hasMat = obj.material | |||
| if (!hasMat) return false | |||
| @@ -194,8 +191,8 @@ export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'ho | |||
| const frameFade = this._viewer.getPlugin(FrameFadePlugin) | |||
| if (frameFade) { | |||
| if (selected) frameFade.disable(PickingPlugin.PluginType) | |||
| else frameFade.enable(PickingPlugin.PluginType) | |||
| if (selected) frameFade.disable(this) | |||
| else frameFade.enable(this) | |||
| } | |||
| this._viewer.scene.autoNearFarEnabled = !selected // for widgets etc, this can be removed when they are rendered in a separate pass | |||
| @@ -250,7 +247,7 @@ export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'ho | |||
| private _onObjectHit = (e: any)=>{ | |||
| if (!this._viewer) return | |||
| if (!this.enabled) { | |||
| if (this.isDisabled()) { | |||
| e.intersects.selectedObject = null | |||
| return | |||
| } | |||
| @@ -3,30 +3,40 @@ import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {OrbitControls3, TransformControls2} from '../../three' | |||
| import {PickingPlugin} from './PickingPlugin' | |||
| import {onChange} from 'ts-browser-helpers' | |||
| import {TransformControls} from '../../three/controls/TransformControls' | |||
| import {UnlitLineMaterial, UnlitMaterial} from '../../core' | |||
| @uiPanelContainer('Transform Controls') | |||
| export class TransformControlsPlugin extends AViewerPluginSync<''> { | |||
| public static readonly PluginType = 'TransformControlsPlugin' | |||
| @uiToggle() | |||
| @onChange(TransformControlsPlugin.prototype._enableChange) | |||
| @onChange(TransformControlsPlugin.prototype.setDirty) | |||
| enabled = true | |||
| private _pickingWidgetDisabled = false | |||
| private _enableChange() { | |||
| setDirty() { | |||
| if (!this._viewer) return | |||
| const picking = this._viewer.getPlugin(PickingPlugin)! | |||
| if (this.enabled && picking.widgetEnabled) { | |||
| const enabled = !this.isDisabled() | |||
| if (enabled && picking.widgetEnabled) { | |||
| picking.widgetEnabled = false | |||
| this._pickingWidgetDisabled = true | |||
| } else if (!this.enabled && this._pickingWidgetDisabled) { | |||
| } else if (!enabled && this._pickingWidgetDisabled) { | |||
| picking.widgetEnabled = true | |||
| this._pickingWidgetDisabled = false | |||
| } | |||
| if (this.transformControls) { | |||
| if (this.enabled && picking.getSelectedObject()) this.transformControls.attach(picking.getSelectedObject()!) | |||
| if (enabled && picking.getSelectedObject()) this.transformControls.attach(picking.getSelectedObject()!) | |||
| else this.transformControls.detach() | |||
| } | |||
| this._viewer.setDirty() | |||
| } | |||
| constructor() { | |||
| super() | |||
| TransformControls.ObjectConstructors.MeshBasicMaterial = UnlitMaterial as any | |||
| TransformControls.ObjectConstructors.LineBasicMaterial = UnlitLineMaterial as any | |||
| } | |||
| toJSON: any = undefined | |||
| @@ -46,7 +56,7 @@ export class TransformControlsPlugin extends AViewerPluginSync<''> { | |||
| onAdded(viewer: ThreeViewer) { | |||
| super.onAdded(viewer) | |||
| this._enableChange() | |||
| this.setDirty() | |||
| this.transformControls = new TransformControls2(viewer.scene.mainCamera, viewer.canvas) | |||
| this._mainCameraChange = this._mainCameraChange.bind(this) | |||
| viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange) | |||
| @@ -68,7 +78,7 @@ export class TransformControlsPlugin extends AViewerPluginSync<''> { | |||
| const picking = viewer.getPlugin(PickingPlugin)! | |||
| picking.addEventListener('selectedObjectChanged', (event) => { | |||
| if (!this.transformControls) return | |||
| if (!this.enabled) { | |||
| if (this.isDisabled()) { | |||
| if (this.transformControls.object) this.transformControls.detach() | |||
| return | |||
| } | |||
| @@ -49,7 +49,7 @@ export class ClearcoatTintPlugin extends AViewerPluginSync<''> { | |||
| // private _multiplyPass?: MultiplyPass | |||
| readonly materialExtension: MaterialExtension = { | |||
| parsFragmentSnippet: (_, material: PhysicalMaterial)=>{ | |||
| if (!this.enabled || !material?.userData._clearcoatTint?.enableTint || !(material.clearcoat > 0)) return '' | |||
| if (this.isDisabled() || !material?.userData._clearcoatTint?.enableTint || !(material.clearcoat > 0)) return '' | |||
| return glsl` | |||
| uniform vec3 ccTintColor; | |||
| uniform float ccThickness; | |||
| @@ -62,7 +62,7 @@ vec3 clearcoatTint(const in float dotNV, const in float dotNL, const in float cl | |||
| ` | |||
| }, | |||
| shaderExtender: (shader, material: PhysicalMaterial) => { | |||
| if (!this.enabled || !material?.userData._clearcoatTint?.enableTint || !(material.clearcoat > 0)) return | |||
| if (this.isDisabled() || !material?.userData._clearcoatTint?.enableTint || !(material.clearcoat > 0)) return | |||
| // Note: clearcoat only considers specular, not diffuse | |||
| @@ -88,14 +88,14 @@ vec3 clearcoatTint(const in float dotNV, const in float dotNL, const in float cl | |||
| updateMaterialDefines({ | |||
| // ...this._defines, | |||
| ['CLEARCOAT_TINT_ENABLED']: +this.enabled, | |||
| ['CLEARCOAT_TINT_ENABLED']: +!this.isDisabled(), | |||
| }, material) | |||
| }, | |||
| extraUniforms: { | |||
| ...this._uniforms, | |||
| }, | |||
| computeCacheKey: (material1: PhysicalMaterial) => { | |||
| return (this.enabled ? '1' : '0') + (material1.userData._clearcoatTint?.enableTint ? '1' : '0') + (material1.clearcoat > 0 ? '1' : '0') | |||
| return (this.isDisabled() ? '0' : '1') + (material1.userData._clearcoatTint?.enableTint ? '1' : '0') + (material1.clearcoat > 0 ? '1' : '0') | |||
| }, | |||
| isCompatible: (material1: PhysicalMaterial) => { | |||
| return material1.isPhysicalMaterial | |||
| @@ -68,11 +68,11 @@ export class CustomBumpMapPlugin extends AViewerPluginSync<''> { | |||
| readonly materialExtension: MaterialExtension = { | |||
| parsFragmentSnippet: (_, material: PhysicalMaterial)=>{ | |||
| if (!this.enabled || !material?.userData._hasCustomBump) return '' | |||
| if (this.isDisabled() || !material?.userData._hasCustomBump) return '' | |||
| return CustomBumpMapPluginShader | |||
| }, | |||
| shaderExtender: (shader, material: PhysicalMaterial) => { | |||
| if (!this.enabled || !material?.userData._hasCustomBump) return | |||
| if (this.isDisabled() || !material?.userData._hasCustomBump) return | |||
| const customBumpMap = material.userData._customBumpMap | |||
| if (!customBumpMap) return | |||
| @@ -61,11 +61,11 @@ export class NoiseBumpMaterialPlugin extends AViewerPluginSync<''> { | |||
| readonly materialExtension: MaterialExtension = { | |||
| parsFragmentSnippet: (_, material: PhysicalMaterial)=>{ | |||
| if (!this.enabled || !material?.userData._noiseBumpMat?.hasBump) return '' | |||
| if (this.isDisabled() || !material?.userData._noiseBumpMat?.hasBump) return '' | |||
| return NoiseBumpMaterialPluginPars | |||
| }, | |||
| shaderExtender: (shader, material: PhysicalMaterial) => { | |||
| if (!this.enabled || !material?.userData._noiseBumpMat?.hasBump) return | |||
| if (this.isDisabled() || !material?.userData._noiseBumpMat?.hasBump) return | |||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#glMarker beforeAccumulation', NoiseBumpMaterialPluginPatch, {prepend: true}) | |||
| ;(shader as any).defines.USE_UV = '' | |||
| ;(shader as any).extensionDerivatives = true | |||
| @@ -88,14 +88,14 @@ export class NoiseBumpMaterialPlugin extends AViewerPluginSync<''> { | |||
| updateMaterialDefines({ | |||
| // ...this._defines, | |||
| ['NOISE_BUMP_MATERIAL_ENABLED']: +this.enabled, | |||
| ['NOISE_BUMP_MATERIAL_ENABLED']: +!this.isDisabled(), | |||
| }, material) | |||
| }, | |||
| extraUniforms: { | |||
| ...this._uniforms, | |||
| }, | |||
| computeCacheKey: (material1: PhysicalMaterial) => { | |||
| return (this.enabled ? '1' : '0') + (material1.userData._noiseBumpMat?.hasBump ? '1' : '0') | |||
| return (this.isDisabled() ? '0' : '1') + (material1.userData._noiseBumpMat?.hasBump ? '1' : '0') | |||
| }, | |||
| isCompatible: (material1: PhysicalMaterial) => material1.isPhysicalMaterial, | |||
| getUiConfig: material => { | |||
| @@ -43,6 +43,7 @@ export class FrameFadePlugin | |||
| 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 | |||
| @@ -117,31 +118,13 @@ export class FrameFadePlugin | |||
| } | |||
| private _disabledBy: string[] = [] | |||
| disable(name: string) { | |||
| if (!this._disabledBy.includes(name)) { | |||
| this._disabledBy.push(name) | |||
| } | |||
| } | |||
| enable(name: string) { | |||
| const i = this._disabledBy.indexOf(name) | |||
| if (i >= 0) { | |||
| this._disabledBy.splice(i, 1) | |||
| } | |||
| } | |||
| isDisabled() { | |||
| return !this._pointerEnabled || this._disabledBy.length > 0 || !this.enabled | |||
| } | |||
| setDirty() { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| this._viewer?.setDirty() | |||
| } | |||
| get dirty() { | |||
| return this.enabled && !!this._pass && this._pass.fadeTimeState > 0 | |||
| return !this.isDisabled() && !!this._pass && this._pass.fadeTimeState > 0 | |||
| } | |||
| set dirty(_: boolean) { | |||
| @@ -154,7 +137,7 @@ export class FrameFadePlugin | |||
| get canFrameFade() { | |||
| return this._target && this._pointerEnabled && | |||
| this.enabled && this.dirty && this._pass && | |||
| this.dirty && this._pass && | |||
| this._pass.fadeTimeState > 0.001 && | |||
| this._viewer && this._viewer.scene.renderCamera === this._viewer.scene.mainCamera | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| import {AViewerPlugin, AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {type AViewerPlugin, AViewerPluginSync} from '../../viewer/AViewerPlugin' | |||
| import type {ThreeViewer} from '../../viewer' | |||
| import {MaterialExtension} from '../../materials' | |||
| import {Shader, Vector4, WebGLRenderer} from 'three' | |||
| import {IMaterial} from '../../core' | |||
| @@ -41,7 +42,7 @@ export abstract class AScreenPassExtensionPlugin<T extends string> extends AView | |||
| protected _shaderPatch = '' | |||
| shaderExtender(shader: Shader, _: IMaterial, _1: WebGLRenderer): void { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| shader.fragmentShader = shaderReplaceString( | |||
| shader.fragmentShader, | |||
| @@ -54,7 +55,7 @@ export abstract class AScreenPassExtensionPlugin<T extends string> extends AView | |||
| return this.uiConfig | |||
| } | |||
| computeCacheKey = (_: IMaterial) => this.enabled ? '1' : '0' | |||
| computeCacheKey = (_: IMaterial) => this.isDisabled() ? '0' : '1' | |||
| isCompatible(_: IMaterial): boolean { | |||
| return true // (material as MeshStandardMaterial2).isMeshStandardMaterial2 | |||
| @@ -35,7 +35,7 @@ export class ChromaticAberrationPlugin extends AScreenPassExtensionPlugin<''> { | |||
| priority = -50 | |||
| parsFragmentSnippet = () => { | |||
| if (!this.enabled) return '' | |||
| if (this.isDisabled()) return '' | |||
| return glsl` | |||
| uniform float aberrationIntensity; | |||
| @@ -41,7 +41,7 @@ export class FilmicGrainPlugin extends AScreenPassExtensionPlugin<''> { | |||
| priority = -50 | |||
| parsFragmentSnippet = () => { | |||
| if (!this.enabled) return '' | |||
| if (this.isDisabled()) return '' | |||
| return glsl` | |||
| uniform float grainIntensity; | |||
| @@ -89,7 +89,7 @@ export class TonemapPlugin extends AScreenPassExtensionPlugin<''> { | |||
| priority = -100 | |||
| parsFragmentSnippet = () => { | |||
| if (!this.enabled) return '' | |||
| if (this.isDisabled()) return '' | |||
| return glsl` | |||
| uniform float toneMappingContrast; | |||
| @@ -103,7 +103,7 @@ export class TonemapPlugin extends AScreenPassExtensionPlugin<''> { | |||
| private _rendererState: any = {} | |||
| onObjectRender(_: Object3D, material: IMaterial, renderer: WebGLRenderer): void { | |||
| if (!this.enabled) return | |||
| if (this.isDisabled()) return | |||
| const {toneMapping, toneMappingExposure} = renderer | |||
| this._rendererState.toneMapping = toneMapping | |||
| this._rendererState.toneMappingExposure = toneMappingExposure | |||
| @@ -43,7 +43,7 @@ export class VignettePlugin extends AScreenPassExtensionPlugin<''> { | |||
| priority = -50 | |||
| parsFragmentSnippet = () => { | |||
| if (!this.enabled) return '' | |||
| if (this.isDisabled()) return '' | |||
| return glsl` | |||
| uniform float power; | |||
| @@ -26,7 +26,7 @@ export class VirtualCamerasPlugin extends AViewerPluginSync<'preRenderCamera' | | |||
| protected _viewerListeners = { | |||
| preRender: () => { | |||
| if (!this.enabled || !this._viewer) return | |||
| if (this.isDisabled() || !this._viewer) return | |||
| const viewer = this._viewer | |||
| for (const v of this.cameras) { | |||
| if (!v.enabled) continue | |||
| @@ -1,6 +1,6 @@ | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers' | |||
| import styles from './GeometryUVPreviewPlugin.css' | |||
| import styles from './GeometryUVPreviewPlugin.css?inline' | |||
| import {CustomContextMenu} from '../../utils' | |||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | |||
| import {IGeometry} from '../../core' | |||
| @@ -151,11 +151,15 @@ export class GeometryUVPreviewPlugin<TEvent extends string> extends AViewerPlugi | |||
| return | |||
| } | |||
| if (!this.mainDiv.parentElement) this._viewer.container?.appendChild(this.mainDiv) | |||
| this.mainDiv.style.display = this.enabled ? 'flex' : 'none' | |||
| this.mainDiv.style.display = !this.isDisabled() ? 'flex' : 'none' | |||
| this.mainDiv.style.zIndex = parseInt(this._viewer.canvas.style.zIndex || '0') + 1 + '' | |||
| this._viewer?.setDirty() | |||
| } | |||
| setDirty() { // for enable/disable functions | |||
| this.refreshUi() | |||
| } | |||
| dispose() { | |||
| for (const target of this.targetBlocks) { | |||
| this.removeGeometry(target.target) | |||
| @@ -2,7 +2,7 @@ import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {IRenderTarget} from '../../rendering' | |||
| import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers' | |||
| import {SRGBColorSpace, Vector4, WebGLRenderTarget} from 'three' | |||
| import styles from './RenderTargetPreviewPlugin.css' | |||
| import styles from './RenderTargetPreviewPlugin.css?inline' | |||
| import {CustomContextMenu} from '../../utils' | |||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | |||
| @@ -154,11 +154,15 @@ export class RenderTargetPreviewPlugin<TEvent extends string> extends AViewerPlu | |||
| return | |||
| } | |||
| if (!this.mainDiv.parentElement) this._viewer.container?.appendChild(this.mainDiv) | |||
| this.mainDiv.style.display = this.enabled ? 'flex' : 'none' | |||
| this.mainDiv.style.display = !this.isDisabled() ? 'flex' : 'none' | |||
| this.mainDiv.style.zIndex = parseInt(this._viewer.canvas.style.zIndex || '0') + 1 + '' | |||
| this._viewer?.setDirty() | |||
| } | |||
| setDirty() { // for enable/disable functions | |||
| this.refreshUi() | |||
| } | |||
| dispose() { | |||
| for (const target of this.targetBlocks) { | |||
| this.removeTarget(target.target) | |||
| @@ -65,6 +65,22 @@ export abstract class AViewerPlugin<T extends string = string, TViewer extends T | |||
| return e | |||
| } | |||
| private _disabledBy = new Set<any>() | |||
| disable = (key: any) => { | |||
| const size = this._disabledBy.size | |||
| this._disabledBy.add(key) | |||
| if (this.setDirty && size !== this._disabledBy.size) this.setDirty() | |||
| } | |||
| enable = (key: any) => { | |||
| const size = this._disabledBy.size | |||
| this._disabledBy.delete(key) | |||
| if (this.setDirty && size !== this._disabledBy.size) this.setDirty() | |||
| } | |||
| isDisabled = () => { | |||
| return this._disabledBy.size > 0 || !this.enabled | |||
| } | |||
| setDirty?(...args: any[]): any | |||
| // todo: move to ThreeViewer | |||
| // storeState(prefix?: string, storage?: Storage, data?: any): void { | |||