TransformControls.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. <script setup lang="ts">
  2. import { useTres } from '@tresjs/core'
  3. import { Camera, Object3D, Scene, WebGLRenderer, type Event } from 'three'
  4. import { TransformControls as TransformControlsImp } from 'three-stdlib'
  5. import { inject, computed, type Ref, unref, watch, shallowRef, ShallowRef, onUnmounted } from 'vue'
  6. import { pick, hasSetter } from '../utils'
  7. import { useCientos } from './useCientos'
  8. const props = withDefaults(
  9. defineProps<{
  10. object: Object3D
  11. mode?: string
  12. enabled?: boolean
  13. axis?: 'X' | 'Y' | 'Z' | 'XY' | 'YZ' | 'XZ' | 'XYZ'
  14. translationSnap?: number
  15. rotationSnap?: number
  16. scaleSnap?: number
  17. space?: 'local' | 'world'
  18. size?: number
  19. showX?: boolean
  20. showY?: boolean
  21. showZ?: boolean
  22. }>(),
  23. {
  24. enabled: true,
  25. },
  26. )
  27. const emit = defineEmits(['dragging', 'change', 'mouseDown', 'mouseUp', 'objectChange'])
  28. let controls: ShallowRef<TransformControlsImp | undefined> = shallowRef()
  29. const { state } = useCientos()
  30. const transformProps = computed(() =>
  31. pick(props, [
  32. 'enabled',
  33. 'axis',
  34. 'mode',
  35. 'translationSnap',
  36. 'rotationSnap',
  37. 'scaleSnap',
  38. 'space',
  39. 'size',
  40. 'showX',
  41. 'showY',
  42. 'showZ',
  43. ]),
  44. )
  45. const onChange = () => emit('change', controls.value)
  46. const onMouseDown = () => emit('mouseDown', controls.value)
  47. const onMouseUp = () => emit('mouseUp', controls.value)
  48. const onObjectChange = () => emit('objectChange', controls.value)
  49. const onDragingChange = (e: Event) => {
  50. if (state.controls) state.controls.enabled = !e.value
  51. emit('dragging', e.value)
  52. }
  53. function addEventListeners(controls: TransformControlsImp) {
  54. controls.addEventListener('dragging-changed', onDragingChange)
  55. controls.addEventListener('change', onChange)
  56. controls.addEventListener('mouseDown', onMouseDown)
  57. controls.addEventListener('mouseUp', onMouseUp)
  58. controls.addEventListener('objectChange', onObjectChange)
  59. }
  60. watch(
  61. () => props.object,
  62. () => {
  63. if (state.camera?.value && state.renderer && state.scene && props.object) {
  64. controls.value = new TransformControlsImp(state.camera.value, unref(state.renderer).domElement)
  65. controls.value.attach(unref(props.object))
  66. state.scene.add(unref(controls) as TransformControlsImp)
  67. addEventListeners(unref(controls) as TransformControlsImp)
  68. }
  69. },
  70. {
  71. deep: true,
  72. },
  73. )
  74. watch(
  75. [transformProps, controls],
  76. // TODO: properly type this
  77. ([value, controlsValue]: [any, any]) => {
  78. if (value && controlsValue) {
  79. for (const key in value) {
  80. if (!hasSetter(controlsValue, key)) {
  81. controlsValue[key] = value[key]
  82. } else {
  83. const methodName = `set${key[0].toUpperCase()}${key.slice(1)}`
  84. if (typeof controlsValue[methodName] === 'function' && value[key] !== undefined) {
  85. ;(controlsValue[methodName] as (param: any) => void)(value[key])
  86. }
  87. }
  88. }
  89. }
  90. },
  91. {
  92. immediate: true,
  93. },
  94. )
  95. onUnmounted(() => {
  96. if (controls.value) {
  97. controls.value.removeEventListener('dragging-changed', onDragingChange)
  98. controls.value.removeEventListener('change', onChange)
  99. controls.value.removeEventListener('mouseDown', onMouseDown)
  100. controls.value.removeEventListener('mouseUp', onMouseUp)
  101. controls.value.removeEventListener('objectChange', onObjectChange)
  102. }
  103. })
  104. </script>
  105. <template>
  106. <slot />
  107. </template>