threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

PickingPlugin.ts 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import {Object3D} from 'three'
  2. import {Class, serialize} from 'ts-browser-helpers'
  3. import {AViewerPluginSync, ThreeViewer} from '../../viewer'
  4. import {ObjectPicker} from '../../three/utils/ObjectPicker'
  5. import {IObject3D, IObject3DEvent, ISceneEvent} from '../../core'
  6. import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js'
  7. import {BoxSelectionWidget, SelectionWidget} from '../../three/utils/SelectionWidget'
  8. export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> {
  9. @serialize() enabled = true
  10. private _enableWidget = true
  11. get picker(): ObjectPicker|undefined {
  12. return this._picker
  13. }
  14. static readonly PluginType = 'Picking'
  15. private _picker?: ObjectPicker
  16. private _widget?: SelectionWidget
  17. private _pickUi: boolean
  18. get hoverEnabled() {
  19. return this._picker?.hoverEnabled ?? false
  20. }
  21. set hoverEnabled(v: boolean) {
  22. if (!this._picker) return
  23. this._picker.hoverEnabled = v
  24. this.uiConfig && this.uiConfig.uiRefresh?.()
  25. }
  26. @serialize()
  27. autoFocus = false
  28. public setDirty() {
  29. this._viewer?.setDirty()
  30. }
  31. constructor(selection: Class<SelectionWidget>|undefined = BoxSelectionWidget, pickUi = true, autoFocus = false) {
  32. super()
  33. if (selection) {
  34. this._widget = new selection()
  35. }
  36. this._pickUi = pickUi
  37. this.autoFocus = autoFocus
  38. this.dispatchEvent = this.dispatchEvent.bind(this)
  39. }
  40. getSelectedObject<T extends IObject3D = IObject3D>(): T|undefined {
  41. if (!this.enabled) return
  42. return this._picker?.selectedObject as T || undefined
  43. }
  44. setSelectedObject(object: IObject3D|undefined, focusCamera = false) { // todo: listen to object dispose
  45. if (!this.enabled) return
  46. if (!this._picker) return
  47. const t = this.autoFocus
  48. this.autoFocus = false
  49. this._picker.selectedObject = object || null
  50. this.autoFocus = t
  51. if (t || focusCamera) this.focusObject(object)
  52. }
  53. onAdded(viewer: ThreeViewer): void {
  54. super.onAdded(viewer)
  55. this._picker = new ObjectPicker(viewer.scene.modelRoot, viewer.canvas, viewer.scene.mainCamera, (obj)=>{
  56. const hasMat = obj.material
  57. if (!hasMat) return false
  58. let o: IObject3D|null = obj
  59. let ret = false
  60. while (o) {
  61. if (!o.visible) return false
  62. if (o.assetType === 'model' || o.assetType === 'light') ret = true
  63. if (o.assetType === 'widget') return false
  64. if (o.userData.userSelectable === false) return false
  65. if (o.userData.bboxVisible === false) return false
  66. o = o.parent
  67. }
  68. return ret
  69. })
  70. if (this._widget) viewer.scene.addObject(this._widget, {addToRoot: true})
  71. this._picker.addEventListener('selectedObjectChanged', this._selectedObjectChanged)
  72. this._picker.addEventListener('hoverObjectChanged', this.dispatchEvent)
  73. this._picker.addEventListener('hitObject', this._onObjectHit)
  74. // on material drop on selected object
  75. // viewer.scene.addEventListener('addSceneObject', async(e) => {
  76. // const obj = e.object
  77. // const selected: IModel<Mesh> = this.getSelectedObject()! as any
  78. // if (selected
  79. // && obj?.assetType === 'material'
  80. // && typeof selected?.setMaterial === 'function'
  81. // && selected?.modelObject?.isMesh
  82. // && await viewer.confirm('Applying material: Apply material to the selected object?')
  83. // ) {
  84. // const oldMat = selected.material
  85. // if (Array.isArray(oldMat)) {
  86. // console.warn('Dropping on material array not yet fully supported.')
  87. // selected.setMaterial(obj)
  88. // } else {
  89. // let meshes: IModel<Mesh>[] = Array.from(oldMat?.userData.__appliedMeshes ?? [])
  90. // const c = meshes.length > 1 ? !await viewer.confirm('Applying material: Apply to all objects using this material?') : meshes.length < 1
  91. // if (c) meshes = [selected]
  92. // for (const mesh of meshes) {
  93. // if (mesh) mesh.setMaterial?.(obj)
  94. // }
  95. // }
  96. // }
  97. // })
  98. viewer.scene.addEventListener('select', this._onObjectSelectEvent)
  99. viewer.scene.addEventListener('sceneUpdate', this._onSceneUpdate)
  100. viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange)
  101. }
  102. onRemove(viewer: ThreeViewer) {
  103. viewer.scene.removeEventListener('select', this._onObjectSelectEvent)
  104. viewer.scene.removeEventListener('sceneUpdate', this._onSceneUpdate)
  105. viewer.scene.removeEventListener('mainCameraChange', this._mainCameraChange)
  106. this._widget?.removeFromParent()
  107. if (this._picker) {
  108. this._picker.removeEventListener('selectedObjectChanged', this._selectedObjectChanged)
  109. this._picker.removeEventListener('hoverObjectChanged', this.dispatchEvent)
  110. this._picker.removeEventListener('hitObject', this._onObjectHit)
  111. this._picker.dispose()
  112. this._picker = undefined
  113. }
  114. super.onRemove(viewer)
  115. }
  116. dispose() {
  117. super.dispose()
  118. this._widget?.dispose()
  119. }
  120. private _mainCameraChange = ()=>{
  121. if (!this._picker || !this._viewer) return
  122. this._picker.camera = this._viewer.scene.mainCamera
  123. }
  124. private _onSceneUpdate = (e: ISceneEvent)=>{
  125. if (!e.hierarchyChanged) return
  126. const s = this.getSelectedObject()
  127. let inScene = false
  128. s?.traverseAncestors((o)=>{
  129. if (o === this._viewer?.scene) inScene = true
  130. })
  131. if (!inScene) this.setSelectedObject(undefined)
  132. }
  133. private _onObjectSelectEvent = (e: IObject3DEvent)=>{
  134. if (e.source === PickingPlugin.PluginType) return
  135. if (e.object === undefined && e.value === undefined) console.error('e.object or e.value must be set for picking, can be null to unselect')
  136. else this.setSelectedObject(e.value, this.autoFocus || e.focusCamera)
  137. }
  138. private _selectedObjectChanged = (e: any) => {
  139. this.dispatchEvent(e)
  140. const selected = this._picker?.selectedObject || undefined
  141. if (this._pickUi) {
  142. const sUiConfig = (selected as IUiConfigContainer)?.uiConfig
  143. const ui = this.uiConfig
  144. ui.children = [...this._uiConfigChildren]
  145. if (sUiConfig) ui.children.push(sUiConfig)
  146. ui.uiRefresh?.()
  147. }
  148. const widget = this._widget
  149. if (widget && this._enableWidget) {
  150. if (selected) widget.attach(selected)
  151. else widget.detach()
  152. }
  153. // if (selected) selected.dispatchEvent({type: 'selected', source: PickingPlugin.PluginType, object: selected})
  154. this._viewer?.setDirty()
  155. if (this.autoFocus) {
  156. // this._viewer?.resetCamera({rootObject: selected, centerOffset: new Vector3(4, 4, 4)})
  157. this.focusObject(selected)
  158. }
  159. }
  160. private _onObjectHit = (e: any)=>{
  161. if (!this._viewer) return
  162. if (!this.enabled) {
  163. e.intersects.selectedObject = null
  164. return
  165. }
  166. this.dispatchEvent(e)
  167. }
  168. // @ts-expect-error temporary
  169. public async focusObject(selected?: Object3D) {
  170. // const camViews = this._viewer?.getPluginByType<CameraViewPlugin>('CameraViews')
  171. // await camViews?.animateToFitObject(selected, 1.25, 1000, 'easeOut', {min: (this._viewer?.scene.activeCamera.getControls<OrbitControls3>()?.minDistance ?? 0.5) + 0.5, max: 50.0})
  172. }
  173. public enableWidget(enable: boolean): void {
  174. this._enableWidget = enable
  175. if (enable) {
  176. const selected = this._picker?.selectedObject || undefined
  177. if (selected)
  178. this._widget?.attach(selected)
  179. } else {
  180. this._widget?.detach()
  181. }
  182. }
  183. private _uiConfigChildren: UiObjectConfig[] = [
  184. {
  185. label: 'Enabled',
  186. type: 'checkbox',
  187. property: [this, 'enabled'],
  188. },
  189. {
  190. label: 'Hover Enabled',
  191. type: 'checkbox',
  192. property: [this, 'hoverEnabled'],
  193. },
  194. {
  195. label: 'AutoFocus',
  196. type: 'checkbox',
  197. property: [this, 'autoFocus'],
  198. onChange: ()=>{
  199. const o = this.getSelectedObject()
  200. if (this.autoFocus && o) this.setSelectedObject(o, true)
  201. },
  202. },
  203. ]
  204. uiConfig: UiObjectConfig = {
  205. type: 'panel',
  206. label: 'Picker',
  207. expanded: true,
  208. children: [
  209. ...this._uiConfigChildren,
  210. ],
  211. }
  212. get widget(): SelectionWidget | undefined {
  213. return this._widget
  214. }
  215. }