| @uiButton('Reset To First View') | @uiButton('Reset To First View') | ||||
| public async resetToFirstView(duration = 100) { | public async resetToFirstView(duration = 100) { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| this._currentView = undefined | this._currentView = undefined | ||||
| await this.animateToView(0, duration) | await this.animateToView(0, duration) | ||||
| await timeout(2) | await timeout(2) | ||||
| @uiButton('Add Current View') | @uiButton('Add Current View') | ||||
| async addCurrentView() { | async addCurrentView() { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| const camera = this._viewer?.scene.mainCamera | const camera = this._viewer?.scene.mainCamera | ||||
| if (!camera) return | if (!camera) return | ||||
| const view = this.getView(camera) | const view = this.getView(camera) | ||||
| @uiButton('Animate All Views') | @uiButton('Animate All Views') | ||||
| async animateAllViews() { | async animateAllViews() { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| if (this.viewLooping || this._cameraViews.length < 2) return | if (this.viewLooping || this._cameraViews.length < 2) return | ||||
| while (this._viewQueue.length > 0) this._viewQueue.pop() | while (this._viewQueue.length > 0) this._viewQueue.pop() | ||||
| this._viewQueue.push(...this._cameraViews) | this._viewQueue.push(...this._cameraViews) | ||||
| if (this._animationLooping) return | if (this._animationLooping) return | ||||
| this._animationLooping = true | this._animationLooping = true | ||||
| while (this.viewLooping || !this._infiniteLooping) { | while (this.viewLooping || !this._infiniteLooping) { | ||||
| if (!this.enabled) break | |||||
| if (this.isDisabled()) break | |||||
| if (this._cameraViews.length < 1) break | if (this._cameraViews.length < 1) break | ||||
| if (this._viewQueue.length === 0) { | if (this._viewQueue.length === 0) { | ||||
| if (this._infiniteLooping) this._viewQueue.push(...this._cameraViews) | if (this._infiniteLooping) this._viewQueue.push(...this._cameraViews) | ||||
| // * For slight rotation of camera when seekOnScroll is enabled | // * For slight rotation of camera when seekOnScroll is enabled | ||||
| // */ | // */ | ||||
| // private _pointerMove(ev: PointerEvent) { | // private _pointerMove(ev: PointerEvent) { | ||||
| // if (!this.enabled) return | |||||
| // if (this.isDisabled()) return | |||||
| // if (!this._animating && this.seekOnScroll) { | // if (!this._animating && this.seekOnScroll) { | ||||
| // const cam = this._viewer?.scene.mainCamera | // const cam = this._viewer?.scene.mainCamera | ||||
| // if (!cam) return | // if (!cam) return | ||||
| // private _scrollAnimationState = 0 | // private _scrollAnimationState = 0 | ||||
| // scrollAnimationDamping = 0.1 | // scrollAnimationDamping = 0.1 | ||||
| // private _wheel(ev: any | WheelEvent) { | // private _wheel(ev: any | WheelEvent) { | ||||
| // if (!this.enabled) return | |||||
| // if (this.isDisabled()) return | |||||
| // if (this.seekOnScroll && !this._animating) { | // if (this.seekOnScroll && !this._animating) { | ||||
| // // if (ev.deltaY > 0) this.focusNext(false) | // // if (ev.deltaY > 0) this.focusNext(false) | ||||
| // // else this.focusPrevious(false) | // // else this.focusPrevious(false) | ||||
| // todo: same code used in PopmotionPlugin, merge somehow | // todo: same code used in PopmotionPlugin, merge somehow | ||||
| // private _postFrame() { | // private _postFrame() { | ||||
| // if (!this._viewer) return | // if (!this._viewer) return | ||||
| // if (!this.enabled || !this._animating) { | |||||
| // if (this.isDisabled() || !this._animating) { | |||||
| // this._lastFrameTime = 0 | // this._lastFrameTime = 0 | ||||
| // if (this._fadeDisabled) { | // if (this._fadeDisabled) { | ||||
| // this._viewer.getPluginByType<FrameFadePlugin>('FrameFade')?.enable(CameraViewPlugin.PluginType) | // this._viewer.getPluginByType<FrameFadePlugin>('FrameFade')?.enable(CameraViewPlugin.PluginType) | ||||
| // @uiButton('Record All Views') | // @uiButton('Record All Views') | ||||
| // public async recordAllViews(onStart?: ()=>void, downloadOnEnd = true) { | // public async recordAllViews(onStart?: ()=>void, downloadOnEnd = true) { | ||||
| // if (!this.enabled) return | |||||
| // if (this.isDisabled()) return | |||||
| // const recorder = this._viewer?.getPluginByType<CanvasRecorderPlugin>('CanvasRecorder') | // const recorder = this._viewer?.getPluginByType<CanvasRecorderPlugin>('CanvasRecorder') | ||||
| // if (!recorder || !recorder.enabled) return | // if (!recorder || !recorder.enabled) return | ||||
| // if (this._cameraViews.length < 1) return | // if (this._cameraViews.length < 1) return |
| * @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. | * @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> { | async playAnimation(resetOnEnd = false, animations?: AnimationAction[]): Promise<void> { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| let wasPlaying = false | let wasPlaying = false | ||||
| if (this._animationState === 'playing') { | if (this._animationState === 'playing') { | ||||
| this.stopAnimation(false) // stop and play again. reset is done below. | this.stopAnimation(false) // stop and play again. reset is done below. | ||||
| this._lastAnimId = '' | this._lastAnimId = '' | ||||
| if (this._viewer && this._fadeDisabled) { | if (this._viewer && this._fadeDisabled) { | ||||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(GLTFAnimationPlugin.PluginType) | |||||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(this) | |||||
| this._fadeDisabled = false | this._fadeDisabled = false | ||||
| } | } | ||||
| const pageScrollAnimate = this.animateOnPageScroll // && this._animationState === 'paused' | const pageScrollAnimate = this.animateOnPageScroll // && this._animationState === 'paused' | ||||
| const dragAnimate = this.animateOnDrag // && 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 | this._lastFrameTime = 0 | ||||
| // console.log('not anim') | // console.log('not anim') | ||||
| if (this._fadeDisabled) { | if (this._fadeDisabled) { | ||||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(GLTFAnimationPlugin.PluginType) | |||||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(this) | |||||
| this._fadeDisabled = false | this._fadeDisabled = false | ||||
| } | } | ||||
| return | return | ||||
| } | } | ||||
| private _scroll() { | private _scroll() { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| this._pageScrollAnimationState = this.pageScrollTime - this.animationTime | this._pageScrollAnimationState = this.pageScrollTime - this.animationTime | ||||
| } | } | ||||
| private _wheel({deltaY}: any | WheelEvent) { | private _wheel({deltaY}: any | WheelEvent) { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| if (Math.abs(deltaY) > 0.001) | if (Math.abs(deltaY) > 0.001) | ||||
| this._scrollAnimationState = -1. * Math.sign(deltaY) | this._scrollAnimationState = -1. * Math.sign(deltaY) | ||||
| } | } | ||||
| private _drag(ev: any) { | private _drag(ev: any) { | ||||
| if (!this.enabled || !this._viewer) return | |||||
| if (this.isDisabled() || !this._viewer) return | |||||
| this._dragAnimationState = this.dragAxis === 'x' ? | this._dragAnimationState = this.dragAxis === 'x' ? | ||||
| ev.delta.x * this._viewer.canvas.width / 4 : | ev.delta.x * this._viewer.canvas.width / 4 : | ||||
| ev.delta.y * this._viewer.canvas.height / 4 | ev.delta.y * this._viewer.canvas.height / 4 |
| // Same code used in CameraViewPlugin | // Same code used in CameraViewPlugin | ||||
| private _postFrame = ()=>{ | private _postFrame = ()=>{ | ||||
| if (!this._viewer) return | 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 | this._lastFrameTime = 0 | ||||
| // console.log('not anim') | // console.log('not anim') | ||||
| if (this._fadeDisabled) { | if (this._fadeDisabled) { | ||||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(PopmotionPlugin.PluginType) | |||||
| this._viewer.getPlugin<FrameFadePlugin>('FrameFade')?.enable(this) | |||||
| this._fadeDisabled = false | this._fadeDisabled = false | ||||
| } | } | ||||
| return | return | ||||
| if (!this._fadeDisabled && this.disableFrameFade) { | if (!this._fadeDisabled && this.disableFrameFade) { | ||||
| const ff = this._viewer.getPlugin<FrameFadePlugin>('FrameFade') | const ff = this._viewer.getPlugin<FrameFadePlugin>('FrameFade') | ||||
| if (ff) { | if (ff) { | ||||
| ff.disable(PopmotionPlugin.PluginType) | |||||
| ff.disable(this) | |||||
| this._fadeDisabled = true | this._fadeDisabled = true | ||||
| } | } | ||||
| } | } |
| import {IPassID, IPipelinePass} from '../../postprocessing' | import {IPassID, IPipelinePass} from '../../postprocessing' | ||||
| import {AViewerPluginSync, ISerializedConfig, ThreeViewer} from '../../viewer' | 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 {SerializationMetaType} from '../../utils' | ||||
| import {uiConfig, uiToggle} from 'uiconfig.js' | import {uiConfig, uiToggle} from 'uiconfig.js' | ||||
| @serialize() | @serialize() | ||||
| @uiToggle('Enabled') | @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() | @uiConfig() | ||||
| @serialize('pass') | @serialize('pass') | ||||
| /** | /** | ||||
| * This function is called every frame before composer render, if this pass is being used in the pipeline | * This function is called every frame before composer render, if this pass is being used in the pipeline | ||||
| * @param _ | * @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() { | constructor() { | ||||
| super() | super() | ||||
| this._pass.onDirty?.push(viewer.setDirty) | this._pass.onDirty?.push(viewer.setDirty) | ||||
| this._pass.beforeRender = wrapThisFunction(this._beforeRender, this._pass.beforeRender) | this._pass.beforeRender = wrapThisFunction(this._beforeRender, this._pass.beforeRender) | ||||
| viewer.renderManager.registerPass(this._pass) | viewer.renderManager.registerPass(this._pass) | ||||
| this.enabled = this._enabledTemp | |||||
| } | } | ||||
| onRemove(viewer: TViewer): void { | onRemove(viewer: TViewer): void { | ||||
| } | } | ||||
| setDirty() { | setDirty() { | ||||
| if (this._pass) this._pass.enabled = !this.isDisabled() | |||||
| this._viewer?.setDirty() | this._viewer?.setDirty() | ||||
| this.uiConfig?.uiRefresh?.(true, 'postFrame', 100) // adding delay for a few frames, so render target(if any can update) | this.uiConfig?.uiRefresh?.(true, 'postFrame', 100) // adding delay for a few frames, so render target(if any can update) | ||||
| } | } |
| static readonly PluginType = 'HDRiGroundPlugin' | static readonly PluginType = 'HDRiGroundPlugin' | ||||
| @serialize() | @serialize() | ||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||||
| @uiToggle('Enabled') | @uiToggle('Enabled') | ||||
| enabled = false | enabled = false | ||||
| @serialize() | @serialize() | ||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||||
| @uiSlider('World Radius', [1, 1000], 0.01) | @uiSlider('World Radius', [1, 1000], 0.01) | ||||
| worldRadius = 100 | worldRadius = 100 | ||||
| @serialize() | @serialize() | ||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||||
| @uiSlider('Tripod height', [0, 50], 0.01) | @uiSlider('Tripod height', [0, 50], 0.01) | ||||
| tripodHeight = 10 | tripodHeight = 10 | ||||
| @serialize() | @serialize() | ||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||||
| @uiVector('Origin Position', undefined, 0.001, (t: HDRiGroundPlugin)=>({ | @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) | originPosition = new Vector3(0, 0, 0) | ||||
| @serialize() | @serialize() | ||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @onChange(HDRiGroundPlugin.prototype.setDirty) | |||||
| promptOnBackgroundMismatch = true | promptOnBackgroundMismatch = true | ||||
| // todo | // todo | ||||
| // * Automatically set the origin position based on the ground position in GroundPlugin | // * Automatically set the origin position based on the ground position in GroundPlugin | ||||
| // */ | // */ | ||||
| // @serialize() | // @serialize() | ||||
| // @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| // @onChange(HDRiGroundPlugin.prototype.setDirty) | |||||
| // @uiToggle('Auto Ground Position') | // @uiToggle('Auto Ground Position') | ||||
| // autoGroundPosition = false | // autoGroundPosition = false | ||||
| constructor(enabled = false, promptOnBackgroundMismatch = true) { | constructor(enabled = false, promptOnBackgroundMismatch = true) { | ||||
| super() | super() | ||||
| this._paramsChanged = this._paramsChanged.bind(this) | |||||
| this.setDirty = this.setDirty.bind(this) | |||||
| this.enabled = enabled | this.enabled = enabled | ||||
| this.promptOnBackgroundMismatch = promptOnBackgroundMismatch | this.promptOnBackgroundMismatch = promptOnBackgroundMismatch | ||||
| this.addEventListener('deserialize', this._paramsChanged) | |||||
| this.addEventListener('deserialize', this.setDirty) | |||||
| } | } | ||||
| private _paramsChanged() { | |||||
| setDirty() { | |||||
| if (!this._viewer) return | if (!this._viewer) return | ||||
| const bg = this._viewer.scene.background | const bg = this._viewer.scene.background | ||||
| if (this.enabled && bg !== this._viewer.scene.environment && bg !== 'environment') { | if (this.enabled && bg !== this._viewer.scene.environment && bg !== 'environment') { | ||||
| unif.worldRadius.value = this.worldRadius | unif.worldRadius.value = this.worldRadius | ||||
| unif.originPosition.value.copy(this.originPosition) | unif.originPosition.value.copy(this.originPosition) | ||||
| if (cubeMat) { | if (cubeMat) { | ||||
| if (!this.enabled && cubeMat.defines.HDRi_GROUND_PROJ) | |||||
| if (this.isDisabled() && cubeMat.defines.HDRi_GROUND_PROJ) | |||||
| delete 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.defines.HDRi_GROUND_PROJ = '1' | ||||
| cubeMat.needsUpdate = true | cubeMat.needsUpdate = true | ||||
| } | } | ||||
| `) | `) | ||||
| } | } | ||||
| viewer.scene.addEventListener('environmentChanged', this._paramsChanged) | |||||
| viewer.scene.addEventListener('environmentChanged', this.setDirty) | |||||
| } | } | ||||
| } | } |
| */ | */ | ||||
| @uiButton('Select files') | @uiButton('Select files') | ||||
| public promptForFile(): void { | public promptForFile(): void { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| this.allowedExtensions = this._allowedExtensions | this.allowedExtensions = this._allowedExtensions | ||||
| this._inputEl?.click() | this._inputEl?.click() | ||||
| } | } | ||||
| private async _onFileDrop({files, nativeEvent}: {files: Map<string, File>, nativeEvent: DragEvent}) { | private async _onFileDrop({files, nativeEvent}: {files: Map<string, File>, nativeEvent: DragEvent}) { | ||||
| if (!files) return | if (!files) return | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| const viewer = this._viewer | const viewer = this._viewer | ||||
| if (!viewer) return | if (!viewer) return | ||||
| if (this._allowedExtensions !== undefined) { | if (this._allowedExtensions !== undefined) { |
| public static readonly PluginType = 'EditorViewWidgetPlugin' | public static readonly PluginType = 'EditorViewWidgetPlugin' | ||||
| @uiToggle() | @uiToggle() | ||||
| @onChange(EditorViewWidgetPlugin.prototype._enableChange) | |||||
| @onChange(EditorViewWidgetPlugin.prototype.setDirty) | |||||
| enabled = true | enabled = true | ||||
| protected _enableChange() { | |||||
| setDirty() { | |||||
| if (!this._viewer || !this.widget) return | 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) { | constructor(public readonly placement: DomPlacement = 'top-left', public readonly size = 128) { | ||||
| protected _needsRender = false | protected _needsRender = false | ||||
| protected _viewerListeners = { | protected _viewerListeners = { | ||||
| postRender: (_: IViewerEvent)=>{ | postRender: (_: IViewerEvent)=>{ | ||||
| if (!this._viewer || !this.widget || !this.enabled) return | |||||
| if (!this._viewer || !this.widget || this.isDisabled()) return | |||||
| this._needsRender = true | this._needsRender = true | ||||
| }, | }, | ||||
| postFrame: (_: IViewerEvent)=>{ | 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.update() | ||||
| this.widget.render() | this.widget.render() | ||||
| if (this.widget.animating) this._viewer.scene.mainCamera.setDirty() | if (this.widget.animating) this._viewer.scene.mainCamera.setDirty() |
| export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> { | export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> { | ||||
| @serialize() | @serialize() | ||||
| @onChange(PickingPlugin.prototype._enableChange) | |||||
| @onChange(PickingPlugin.prototype.setDirty) | |||||
| enabled = true | enabled = true | ||||
| private _enableChange() { | |||||
| if (!this._viewer) return | |||||
| if (!this.enabled) this.setSelectedObject(undefined) // todo | |||||
| } | |||||
| get picker(): ObjectPicker|undefined { | get picker(): ObjectPicker|undefined { | ||||
| return this._picker | return this._picker | ||||
| } | } | ||||
| this.uiConfig?.uiRefresh?.(true) | 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) { | constructor(selection: Class<SelectionWidget>|undefined = BoxSelectionWidget, pickUi = true, autoFocus = false) { | ||||
| super() | super() | ||||
| } | } | ||||
| getSelectedObject<T extends IObject3D = IObject3D>(): T|undefined { | getSelectedObject<T extends IObject3D = IObject3D>(): T|undefined { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| return this._picker?.selectedObject as T || undefined | return this._picker?.selectedObject as T || undefined | ||||
| } | } | ||||
| setSelectedObject(object: IObject3D|undefined, focusCamera = false) { // todo: listen to object dispose | setSelectedObject(object: IObject3D|undefined, focusCamera = false) { // todo: listen to object dispose | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| if (!this._picker) return | if (!this._picker) return | ||||
| const t = this.autoFocus | const t = this.autoFocus | ||||
| this.autoFocus = false | this.autoFocus = false | ||||
| onAdded(viewer: ThreeViewer): void { | onAdded(viewer: ThreeViewer): void { | ||||
| super.onAdded(viewer) | super.onAdded(viewer) | ||||
| this._enableChange() | |||||
| this.setDirty() | |||||
| this._picker = new ObjectPicker(viewer.scene.modelRoot, viewer.canvas, viewer.scene.mainCamera, (obj)=>{ | this._picker = new ObjectPicker(viewer.scene.modelRoot, viewer.canvas, viewer.scene.mainCamera, (obj)=>{ | ||||
| const hasMat = obj.material | const hasMat = obj.material | ||||
| if (!hasMat) return false | if (!hasMat) return false | ||||
| const frameFade = this._viewer.getPlugin(FrameFadePlugin) | const frameFade = this._viewer.getPlugin(FrameFadePlugin) | ||||
| if (frameFade) { | 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 | this._viewer.scene.autoNearFarEnabled = !selected // for widgets etc, this can be removed when they are rendered in a separate pass | ||||
| private _onObjectHit = (e: any)=>{ | private _onObjectHit = (e: any)=>{ | ||||
| if (!this._viewer) return | if (!this._viewer) return | ||||
| if (!this.enabled) { | |||||
| if (this.isDisabled()) { | |||||
| e.intersects.selectedObject = null | e.intersects.selectedObject = null | ||||
| return | return | ||||
| } | } |
| import {OrbitControls3, TransformControls2} from '../../three' | import {OrbitControls3, TransformControls2} from '../../three' | ||||
| import {PickingPlugin} from './PickingPlugin' | import {PickingPlugin} from './PickingPlugin' | ||||
| import {onChange} from 'ts-browser-helpers' | import {onChange} from 'ts-browser-helpers' | ||||
| import {TransformControls} from '../../three/controls/TransformControls' | |||||
| import {UnlitLineMaterial, UnlitMaterial} from '../../core' | |||||
| @uiPanelContainer('Transform Controls') | @uiPanelContainer('Transform Controls') | ||||
| export class TransformControlsPlugin extends AViewerPluginSync<''> { | export class TransformControlsPlugin extends AViewerPluginSync<''> { | ||||
| public static readonly PluginType = 'TransformControlsPlugin' | public static readonly PluginType = 'TransformControlsPlugin' | ||||
| @uiToggle() | @uiToggle() | ||||
| @onChange(TransformControlsPlugin.prototype._enableChange) | |||||
| @onChange(TransformControlsPlugin.prototype.setDirty) | |||||
| enabled = true | enabled = true | ||||
| private _pickingWidgetDisabled = false | private _pickingWidgetDisabled = false | ||||
| private _enableChange() { | |||||
| setDirty() { | |||||
| if (!this._viewer) return | if (!this._viewer) return | ||||
| const picking = this._viewer.getPlugin(PickingPlugin)! | const picking = this._viewer.getPlugin(PickingPlugin)! | ||||
| if (this.enabled && picking.widgetEnabled) { | |||||
| const enabled = !this.isDisabled() | |||||
| if (enabled && picking.widgetEnabled) { | |||||
| picking.widgetEnabled = false | picking.widgetEnabled = false | ||||
| this._pickingWidgetDisabled = true | this._pickingWidgetDisabled = true | ||||
| } else if (!this.enabled && this._pickingWidgetDisabled) { | |||||
| } else if (!enabled && this._pickingWidgetDisabled) { | |||||
| picking.widgetEnabled = true | picking.widgetEnabled = true | ||||
| this._pickingWidgetDisabled = false | this._pickingWidgetDisabled = false | ||||
| } | } | ||||
| if (this.transformControls) { | 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() | else this.transformControls.detach() | ||||
| } | } | ||||
| this._viewer.setDirty() | |||||
| } | |||||
| constructor() { | |||||
| super() | |||||
| TransformControls.ObjectConstructors.MeshBasicMaterial = UnlitMaterial as any | |||||
| TransformControls.ObjectConstructors.LineBasicMaterial = UnlitLineMaterial as any | |||||
| } | } | ||||
| toJSON: any = undefined | toJSON: any = undefined | ||||
| onAdded(viewer: ThreeViewer) { | onAdded(viewer: ThreeViewer) { | ||||
| super.onAdded(viewer) | super.onAdded(viewer) | ||||
| this._enableChange() | |||||
| this.setDirty() | |||||
| this.transformControls = new TransformControls2(viewer.scene.mainCamera, viewer.canvas) | this.transformControls = new TransformControls2(viewer.scene.mainCamera, viewer.canvas) | ||||
| this._mainCameraChange = this._mainCameraChange.bind(this) | this._mainCameraChange = this._mainCameraChange.bind(this) | ||||
| viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange) | viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange) | ||||
| const picking = viewer.getPlugin(PickingPlugin)! | const picking = viewer.getPlugin(PickingPlugin)! | ||||
| picking.addEventListener('selectedObjectChanged', (event) => { | picking.addEventListener('selectedObjectChanged', (event) => { | ||||
| if (!this.transformControls) return | if (!this.transformControls) return | ||||
| if (!this.enabled) { | |||||
| if (this.isDisabled()) { | |||||
| if (this.transformControls.object) this.transformControls.detach() | if (this.transformControls.object) this.transformControls.detach() | ||||
| return | return | ||||
| } | } |
| // private _multiplyPass?: MultiplyPass | // private _multiplyPass?: MultiplyPass | ||||
| readonly materialExtension: MaterialExtension = { | readonly materialExtension: MaterialExtension = { | ||||
| parsFragmentSnippet: (_, material: PhysicalMaterial)=>{ | 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` | return glsl` | ||||
| uniform vec3 ccTintColor; | uniform vec3 ccTintColor; | ||||
| uniform float ccThickness; | uniform float ccThickness; | ||||
| ` | ` | ||||
| }, | }, | ||||
| shaderExtender: (shader, material: PhysicalMaterial) => { | 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 | // Note: clearcoat only considers specular, not diffuse | ||||
| updateMaterialDefines({ | updateMaterialDefines({ | ||||
| // ...this._defines, | // ...this._defines, | ||||
| ['CLEARCOAT_TINT_ENABLED']: +this.enabled, | |||||
| ['CLEARCOAT_TINT_ENABLED']: +!this.isDisabled(), | |||||
| }, material) | }, material) | ||||
| }, | }, | ||||
| extraUniforms: { | extraUniforms: { | ||||
| ...this._uniforms, | ...this._uniforms, | ||||
| }, | }, | ||||
| computeCacheKey: (material1: PhysicalMaterial) => { | 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) => { | isCompatible: (material1: PhysicalMaterial) => { | ||||
| return material1.isPhysicalMaterial | return material1.isPhysicalMaterial |
| readonly materialExtension: MaterialExtension = { | readonly materialExtension: MaterialExtension = { | ||||
| parsFragmentSnippet: (_, material: PhysicalMaterial)=>{ | parsFragmentSnippet: (_, material: PhysicalMaterial)=>{ | ||||
| if (!this.enabled || !material?.userData._hasCustomBump) return '' | |||||
| if (this.isDisabled() || !material?.userData._hasCustomBump) return '' | |||||
| return CustomBumpMapPluginShader | return CustomBumpMapPluginShader | ||||
| }, | }, | ||||
| shaderExtender: (shader, material: PhysicalMaterial) => { | shaderExtender: (shader, material: PhysicalMaterial) => { | ||||
| if (!this.enabled || !material?.userData._hasCustomBump) return | |||||
| if (this.isDisabled() || !material?.userData._hasCustomBump) return | |||||
| const customBumpMap = material.userData._customBumpMap | const customBumpMap = material.userData._customBumpMap | ||||
| if (!customBumpMap) return | if (!customBumpMap) return | ||||
| readonly materialExtension: MaterialExtension = { | readonly materialExtension: MaterialExtension = { | ||||
| parsFragmentSnippet: (_, material: PhysicalMaterial)=>{ | parsFragmentSnippet: (_, material: PhysicalMaterial)=>{ | ||||
| if (!this.enabled || !material?.userData._noiseBumpMat?.hasBump) return '' | |||||
| if (this.isDisabled() || !material?.userData._noiseBumpMat?.hasBump) return '' | |||||
| return NoiseBumpMaterialPluginPars | return NoiseBumpMaterialPluginPars | ||||
| }, | }, | ||||
| shaderExtender: (shader, material: PhysicalMaterial) => { | 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.fragmentShader = shaderReplaceString(shader.fragmentShader, '#glMarker beforeAccumulation', NoiseBumpMaterialPluginPatch, {prepend: true}) | ||||
| ;(shader as any).defines.USE_UV = '' | ;(shader as any).defines.USE_UV = '' | ||||
| ;(shader as any).extensionDerivatives = true | ;(shader as any).extensionDerivatives = true | ||||
| updateMaterialDefines({ | updateMaterialDefines({ | ||||
| // ...this._defines, | // ...this._defines, | ||||
| ['NOISE_BUMP_MATERIAL_ENABLED']: +this.enabled, | |||||
| ['NOISE_BUMP_MATERIAL_ENABLED']: +!this.isDisabled(), | |||||
| }, material) | }, material) | ||||
| }, | }, | ||||
| extraUniforms: { | extraUniforms: { | ||||
| ...this._uniforms, | ...this._uniforms, | ||||
| }, | }, | ||||
| computeCacheKey: (material1: PhysicalMaterial) => { | 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, | isCompatible: (material1: PhysicalMaterial) => material1.isPhysicalMaterial, | ||||
| getUiConfig: material => { | getUiConfig: material => { |
| this.stopTransition = this.stopTransition.bind(this) | this.stopTransition = this.stopTransition.bind(this) | ||||
| this._fadeCam = this._fadeCam.bind(this) | this._fadeCam = this._fadeCam.bind(this) | ||||
| this._fadeMat = this._fadeMat.bind(this) | this._fadeMat = this._fadeMat.bind(this) | ||||
| this.isDisabled = ((sup)=>()=>!this._pointerEnabled || sup())(this.isDisabled) | |||||
| } | } | ||||
| public async startTransition(duration: number) { // duration in ms | public async startTransition(duration: number) { // duration in ms | ||||
| } | } | ||||
| 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() { | setDirty() { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| this._viewer?.setDirty() | this._viewer?.setDirty() | ||||
| } | } | ||||
| get dirty() { | get dirty() { | ||||
| return this.enabled && !!this._pass && this._pass.fadeTimeState > 0 | |||||
| return !this.isDisabled() && !!this._pass && this._pass.fadeTimeState > 0 | |||||
| } | } | ||||
| set dirty(_: boolean) { | set dirty(_: boolean) { | ||||
| get canFrameFade() { | get canFrameFade() { | ||||
| return this._target && this._pointerEnabled && | return this._target && this._pointerEnabled && | ||||
| this.enabled && this.dirty && this._pass && | |||||
| this.dirty && this._pass && | |||||
| this._pass.fadeTimeState > 0.001 && | this._pass.fadeTimeState > 0.001 && | ||||
| this._viewer && this._viewer.scene.renderCamera === this._viewer.scene.mainCamera | this._viewer && this._viewer.scene.renderCamera === this._viewer.scene.mainCamera | ||||
| } | } |
| import {AViewerPlugin, AViewerPluginSync, ThreeViewer} from '../../viewer' | |||||
| import {type AViewerPlugin, AViewerPluginSync} from '../../viewer/AViewerPlugin' | |||||
| import type {ThreeViewer} from '../../viewer' | |||||
| import {MaterialExtension} from '../../materials' | import {MaterialExtension} from '../../materials' | ||||
| import {Shader, Vector4, WebGLRenderer} from 'three' | import {Shader, Vector4, WebGLRenderer} from 'three' | ||||
| import {IMaterial} from '../../core' | import {IMaterial} from '../../core' | ||||
| protected _shaderPatch = '' | protected _shaderPatch = '' | ||||
| shaderExtender(shader: Shader, _: IMaterial, _1: WebGLRenderer): void { | shaderExtender(shader: Shader, _: IMaterial, _1: WebGLRenderer): void { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| shader.fragmentShader = shaderReplaceString( | shader.fragmentShader = shaderReplaceString( | ||||
| shader.fragmentShader, | shader.fragmentShader, | ||||
| return this.uiConfig | return this.uiConfig | ||||
| } | } | ||||
| computeCacheKey = (_: IMaterial) => this.enabled ? '1' : '0' | |||||
| computeCacheKey = (_: IMaterial) => this.isDisabled() ? '0' : '1' | |||||
| isCompatible(_: IMaterial): boolean { | isCompatible(_: IMaterial): boolean { | ||||
| return true // (material as MeshStandardMaterial2).isMeshStandardMaterial2 | return true // (material as MeshStandardMaterial2).isMeshStandardMaterial2 |
| priority = -50 | priority = -50 | ||||
| parsFragmentSnippet = () => { | parsFragmentSnippet = () => { | ||||
| if (!this.enabled) return '' | |||||
| if (this.isDisabled()) return '' | |||||
| return glsl` | return glsl` | ||||
| uniform float aberrationIntensity; | uniform float aberrationIntensity; |
| priority = -50 | priority = -50 | ||||
| parsFragmentSnippet = () => { | parsFragmentSnippet = () => { | ||||
| if (!this.enabled) return '' | |||||
| if (this.isDisabled()) return '' | |||||
| return glsl` | return glsl` | ||||
| uniform float grainIntensity; | uniform float grainIntensity; |
| priority = -100 | priority = -100 | ||||
| parsFragmentSnippet = () => { | parsFragmentSnippet = () => { | ||||
| if (!this.enabled) return '' | |||||
| if (this.isDisabled()) return '' | |||||
| return glsl` | return glsl` | ||||
| uniform float toneMappingContrast; | uniform float toneMappingContrast; | ||||
| private _rendererState: any = {} | private _rendererState: any = {} | ||||
| onObjectRender(_: Object3D, material: IMaterial, renderer: WebGLRenderer): void { | onObjectRender(_: Object3D, material: IMaterial, renderer: WebGLRenderer): void { | ||||
| if (!this.enabled) return | |||||
| if (this.isDisabled()) return | |||||
| const {toneMapping, toneMappingExposure} = renderer | const {toneMapping, toneMappingExposure} = renderer | ||||
| this._rendererState.toneMapping = toneMapping | this._rendererState.toneMapping = toneMapping | ||||
| this._rendererState.toneMappingExposure = toneMappingExposure | this._rendererState.toneMappingExposure = toneMappingExposure |
| priority = -50 | priority = -50 | ||||
| parsFragmentSnippet = () => { | parsFragmentSnippet = () => { | ||||
| if (!this.enabled) return '' | |||||
| if (this.isDisabled()) return '' | |||||
| return glsl` | return glsl` | ||||
| uniform float power; | uniform float power; |
| protected _viewerListeners = { | protected _viewerListeners = { | ||||
| preRender: () => { | preRender: () => { | ||||
| if (!this.enabled || !this._viewer) return | |||||
| if (this.isDisabled() || !this._viewer) return | |||||
| const viewer = this._viewer | const viewer = this._viewer | ||||
| for (const v of this.cameras) { | for (const v of this.cameras) { | ||||
| if (!v.enabled) continue | if (!v.enabled) continue |
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | import {AViewerPluginSync, ThreeViewer} from '../../viewer' | ||||
| import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers' | 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 {CustomContextMenu} from '../../utils' | ||||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | import {uiFolderContainer, uiToggle} from 'uiconfig.js' | ||||
| import {IGeometry} from '../../core' | import {IGeometry} from '../../core' | ||||
| return | return | ||||
| } | } | ||||
| if (!this.mainDiv.parentElement) this._viewer.container?.appendChild(this.mainDiv) | 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.mainDiv.style.zIndex = parseInt(this._viewer.canvas.style.zIndex || '0') + 1 + '' | ||||
| this._viewer?.setDirty() | this._viewer?.setDirty() | ||||
| } | } | ||||
| setDirty() { // for enable/disable functions | |||||
| this.refreshUi() | |||||
| } | |||||
| dispose() { | dispose() { | ||||
| for (const target of this.targetBlocks) { | for (const target of this.targetBlocks) { | ||||
| this.removeGeometry(target.target) | this.removeGeometry(target.target) |
| import {IRenderTarget} from '../../rendering' | import {IRenderTarget} from '../../rendering' | ||||
| import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers' | import {createDiv, createStyles, getOrCall, onChange, ValOrFunc} from 'ts-browser-helpers' | ||||
| import {SRGBColorSpace, Vector4, WebGLRenderTarget} from 'three' | import {SRGBColorSpace, Vector4, WebGLRenderTarget} from 'three' | ||||
| import styles from './RenderTargetPreviewPlugin.css' | |||||
| import styles from './RenderTargetPreviewPlugin.css?inline' | |||||
| import {CustomContextMenu} from '../../utils' | import {CustomContextMenu} from '../../utils' | ||||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | import {uiFolderContainer, uiToggle} from 'uiconfig.js' | ||||
| return | return | ||||
| } | } | ||||
| if (!this.mainDiv.parentElement) this._viewer.container?.appendChild(this.mainDiv) | 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.mainDiv.style.zIndex = parseInt(this._viewer.canvas.style.zIndex || '0') + 1 + '' | ||||
| this._viewer?.setDirty() | this._viewer?.setDirty() | ||||
| } | } | ||||
| setDirty() { // for enable/disable functions | |||||
| this.refreshUi() | |||||
| } | |||||
| dispose() { | dispose() { | ||||
| for (const target of this.targetBlocks) { | for (const target of this.targetBlocks) { | ||||
| this.removeTarget(target.target) | this.removeTarget(target.target) |
| return e | 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 | // todo: move to ThreeViewer | ||||
| // storeState(prefix?: string, storage?: Storage, data?: any): void { | // storeState(prefix?: string, storage?: Storage, data?: any): void { |