瀏覽代碼

Add UndoManagerPlugin with support in PickingPlugin, TransformControls, skip calling target.setDirty in makeSetterFor, it must be passed as parameter.

master
Palash Bansal 1 年之前
父節點
當前提交
0aa148a26d
沒有連結到貢獻者的電子郵件帳戶。

+ 1
- 0
src/plugins/index.ts 查看文件

export {DeviceOrientationControlsPlugin} from './interaction/DeviceOrientationControlsPlugin' export {DeviceOrientationControlsPlugin} from './interaction/DeviceOrientationControlsPlugin'
export {PointerLockControlsPlugin} from './interaction/PointerLockControlsPlugin' export {PointerLockControlsPlugin} from './interaction/PointerLockControlsPlugin'
export {ThreeFirstPersonControlsPlugin} from './interaction/ThreeFirstPersonControlsPlugin' export {ThreeFirstPersonControlsPlugin} from './interaction/ThreeFirstPersonControlsPlugin'
export {UndoManagerPlugin} from './interaction/UndoManagerPlugin'


// import // import
export {Rhino3dmLoadPlugin} from './import/Rhino3dmLoadPlugin' export {Rhino3dmLoadPlugin} from './import/Rhino3dmLoadPlugin'

+ 10
- 0
src/plugins/interaction/PickingPlugin.ts 查看文件

import {IObject3D, IObject3DEvent, ISceneEvent} from '../../core' import {IObject3D, IObject3DEvent, ISceneEvent} from '../../core'
import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js'
import {FrameFadePlugin} from '../pipeline/FrameFadePlugin' import {FrameFadePlugin} from '../pipeline/FrameFadePlugin'
import {type UndoManagerPlugin} from './UndoManagerPlugin'


export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> { export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> {
@serialize() @serialize()
viewer.scene.addEventListener('sceneUpdate', this._onSceneUpdate) viewer.scene.addEventListener('sceneUpdate', this._onSceneUpdate)
viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange) viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange)


viewer.forPlugin<UndoManagerPlugin>('UndoManagerPlugin', (um)=>{
if (!this._picker) return
this._picker.undoManager = um.undoManager
}, ()=>{
if (!this._picker) return
this._picker.undoManager = undefined
})

} }


onRemove(viewer: ThreeViewer) { onRemove(viewer: ThreeViewer) {
this._picker.removeEventListener('hoverObjectChanged', this._hoverObjectChanged) this._picker.removeEventListener('hoverObjectChanged', this._hoverObjectChanged)
this._picker.removeEventListener('hitObject', this._onObjectHit) this._picker.removeEventListener('hitObject', this._onObjectHit)
this._picker.dispose() this._picker.dispose()
this._picker.undoManager = undefined // because setting above
this._picker = undefined this._picker = undefined
} }
super.onRemove(viewer) super.onRemove(viewer)

+ 57
- 1
src/plugins/interaction/TransformControlsPlugin.ts 查看文件

import {AViewerPluginSync, ThreeViewer} from '../../viewer' import {AViewerPluginSync, ThreeViewer} from '../../viewer'
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 {JSUndoManager, onChange} from 'ts-browser-helpers'
import {TransformControls} from '../../three/controls/TransformControls' import {TransformControls} from '../../three/controls/TransformControls'
import {UnlitLineMaterial, UnlitMaterial} from '../../core' import {UnlitLineMaterial, UnlitMaterial} from '../../core'
import {Euler, Object3D, Vector3} from 'three'
import type {UndoManagerPlugin} from './UndoManagerPlugin'


@uiPanelContainer('Transform Controls') @uiPanelContainer('Transform Controls')
export class TransformControlsPlugin extends AViewerPluginSync<''> { export class TransformControlsPlugin extends AViewerPluginSync<''> {
}, },
} }



private _transformState = {
obj: null as Object3D|null,
position: new Vector3(),
rotation: new Euler(),
scale: new Vector3(),
}
undoManager?: JSUndoManager

onAdded(viewer: ThreeViewer) { onAdded(viewer: ThreeViewer) {
super.onAdded(viewer) super.onAdded(viewer)
this.setDirty() this.setDirty()
event.object ? this.transformControls.attach(event.object) : this.transformControls.detach() event.object ? this.transformControls.attach(event.object) : this.transformControls.detach()
}) })


viewer.forPlugin<UndoManagerPlugin>('UndoManagerPlugin', (um)=> {
this.undoManager = um.undoManager
}, ()=> this.undoManager = undefined)

// same logic for undo as three.js editor. todo It can be made better by syncing with the UI so it supports the hotkeys and other properties inside TransformControls2
this.transformControls.addEventListener('mouseDown', ()=> {
if (!this.transformControls) return
const object = this.transformControls.object
if (!object) return

this._transformState.obj = object
this._transformState.position = object.position.clone()
this._transformState.rotation = object.rotation.clone()
this._transformState.scale = object.scale.clone()
})

this.transformControls.addEventListener('mouseUp', ()=> {
if (!this.transformControls) return
const object = this.transformControls.object
if (!object) return

if (this._transformState.obj !== object || !this.undoManager) return

const key = ({
'translate': 'position',
'rotate': 'rotation',
'scale': 'scale',
} as const)[this.transformControls.getMode()]
if (!key) return
if (this._transformState[key].equals(object[key] as any)) return

const command = {
last: this._transformState[key].clone(), current: object[key].clone(),
set: (value: any) => {
object[key].copy(value)
object.updateMatrixWorld(true)
this.transformControls?.dispatchEvent({type: 'change'} as any)
this.transformControls?.dispatchEvent({type: 'objectChange'} as any)
},
undo: () => command.set(command.last),
redo: () => command.set(command.current),
}
this.undoManager.record(command)
})

} }


onRemove(viewer: ThreeViewer) { onRemove(viewer: ThreeViewer) {

+ 43
- 0
src/plugins/interaction/UndoManagerPlugin.ts 查看文件

import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {getUrlQueryParam, JSUndoManager, onChange} from 'ts-browser-helpers'

// @uiPanelContainer('Undo Manager')
export class UndoManagerPlugin extends AViewerPluginSync<''> {
public static readonly PluginType = 'UndoManagerPlugin'

// @uiToggle()
@onChange(UndoManagerPlugin.prototype._refresh)
enabled = true

undoManager?: JSUndoManager

@onChange(UndoManagerPlugin.prototype._refresh)
limit = 1000

constructor(enabled = true, limit = 1000) {
super()
this.enabled = enabled
this.limit = limit
}

protected _refresh() {
if (!this.undoManager) return
this.undoManager.enabled = this.enabled
this.undoManager.limit = this.limit
this.undoManager.options.debug = this._viewer?.debug || this.undoManager.options.debug
}

toJSON: any = undefined

onAdded(viewer: ThreeViewer) {
super.onAdded(viewer)
this.undoManager = new JSUndoManager({bindHotKeys: true, limit: this.limit, debug: viewer.debug || getUrlQueryParam('debugUndo') !== null, hotKeyRoot: document as any})
}

onRemove(viewer: ThreeViewer) {
this.undoManager?.dispose()
this.undoManager = undefined
super.onRemove(viewer)
}

}

+ 1
- 0
src/three/controls/TransformControls2.ts 查看文件

private _keyDownListener(event: KeyboardEvent) { private _keyDownListener(event: KeyboardEvent) {
if (!this.enabled) return if (!this.enabled) return
if (!this.object) return if (!this.object) return
if (event.metaKey || event.ctrlKey) return


switch (event.code) { switch (event.code) {



+ 11
- 1
src/three/utils/ObjectPicker.ts 查看文件

import {Event, EventDispatcher, Intersection, Raycaster, Vector2} from 'three' import {Event, EventDispatcher, Intersection, Raycaster, Vector2} from 'three'
import {now} from 'ts-browser-helpers'
import {JSUndoManager, now} from 'ts-browser-helpers'
import {ICamera, IObject3D} from '../../core' import {ICamera, IObject3D} from '../../core'


export class ObjectPicker extends EventDispatcher<Event, 'hoverObjectChanged'|'selectedObjectChanged'|'hitObject'> { export class ObjectPicker extends EventDispatcher<Event, 'hoverObjectChanged'|'selectedObjectChanged'|'hitObject'> {
*/ */
static PointerClickMaxDistance = 0.1 // 1/20 of the canvas static PointerClickMaxDistance = 0.1 // 1/20 of the canvas


undoManager?: JSUndoManager
private _root: IObject3D private _root: IObject3D
private _camera: ICamera private _camera: ICamera
private _mouseDownTime: number private _mouseDownTime: number
} }


set selectedObject(object) { set selectedObject(object) {
this._setSelected(object)
}

private _setSelected(object: IObject3D|null, record = true) {
if (!this._selected.length && !object || this._selected.length === 1 && this._selected[0] === object) return if (!this._selected.length && !object || this._selected.length === 1 && this._selected[0] === object) return
const current = [...this._selected]
this._selected = object ? Array.isArray(object) ? [...object] : [object] : [] this._selected = object ? Array.isArray(object) ? [...object] : [object] : []
this.dispatchEvent({type: 'selectedObjectChanged', object: this.selectedObject}) this.dispatchEvent({type: 'selectedObjectChanged', object: this.selectedObject})
record && this.undoManager?.record({
undo: () => this._setSelected(current.length ? current[0] : null, false),
redo: () => this._setSelected(object, false),
})
} }


get hoverObject(): IObject3D | null { get hoverObject(): IObject3D | null {

+ 1
- 1
src/utils/animation.ts 查看文件

export function makeSetterFor<V>(target: any, key: string, setDirty?: ()=>void) { export function makeSetterFor<V>(target: any, key: string, setDirty?: ()=>void) {
const v = target[key] as any const v = target[key] as any
const dirty = ()=>{ const dirty = ()=>{
if (typeof target?.setDirty === 'function') target.setDirty()
// if (typeof target?.setDirty === 'function') target.setDirty()
setDirty?.() setDirty?.()
} }
const isBool = typeof v === 'boolean' const isBool = typeof v === 'boolean'

Loading…
取消
儲存