PlaceTool.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. * PlaceTool.js
  3. *
  4. * @author realor
  5. */
  6. import { Tool } from './Tool.js'
  7. import { Controls } from '../ui/Controls.js'
  8. import { I18N } from '../i18n/I18N.js'
  9. import { GeometryUtils } from '../utils/GeometryUtils.js'
  10. import * as THREE from '../lib/three.module.js'
  11. class PlaceTool extends Tool {
  12. static CHANGED_PROPERTIES = ['position', 'rotation']
  13. static GLOBAL_MODE = 0
  14. static LOCAL_MODE = 1
  15. static PERPENDICULAR_MODE = 2
  16. constructor(application, options) {
  17. super(application)
  18. this.name = 'place'
  19. this.label = 'tool.place.label'
  20. this.help = 'tool.place.help'
  21. this.className = 'place'
  22. this.setOptions(options)
  23. this.stage = 0
  24. this.snap = null
  25. this.mode = 0
  26. this.localMatrix = new THREE.Matrix4()
  27. this.axisMatrixWorld = new THREE.Matrix4()
  28. this._onPointerDown = this.onPointerDown.bind(this)
  29. this._onPointerUp = this.onPointerUp.bind(this)
  30. this._onPointerMove = this.onPointerMove.bind(this)
  31. this._onContextMenu = this.onContextMenu.bind(this)
  32. this.createPanel()
  33. }
  34. createPanel() {
  35. this.panel = this.application.createPanel(this.label, 'left', 'panel_place')
  36. this.panel.preferredHeight = 140
  37. this.helpElem = document.createElement('div')
  38. this.panel.bodyElem.appendChild(this.helpElem)
  39. I18N.set(this.helpElem, 'innerHTML', 'tool.place.help')
  40. this.modeElem = Controls.addSelectField(this.panel.bodyElem, 'place_mode', 'label.place_mode', [
  41. [PlaceTool.GLOBAL_MODE, 'Global'],
  42. [PlaceTool.LOCAL_MODE, 'Local'],
  43. [PlaceTool.PERPENDICULAR_MODE, 'Perpendicular']
  44. ])
  45. this.modeElem.addEventListener('change', event => {
  46. this.mode = parseInt(this.modeElem.value)
  47. this.placeObject()
  48. })
  49. }
  50. activate() {
  51. this.panel.visible = true
  52. const application = this.application
  53. const container = application.container
  54. container.addEventListener('contextmenu', this._onContextMenu, false)
  55. container.addEventListener('pointerdown', this._onPointerDown, false)
  56. container.addEventListener('pointerup', this._onPointerUp, false)
  57. container.addEventListener('pointermove', this._onPointerMove, false)
  58. this.setStage(this.stage)
  59. application.pointSelector.auxiliaryLines = []
  60. application.pointSelector.activate()
  61. application.pointSelector.excludeSelection = true
  62. application.pointSelector.clearAxisGuides()
  63. this.preparePlacement()
  64. }
  65. deactivate() {
  66. this.panel.visible = false
  67. const application = this.application
  68. const container = application.container
  69. container.removeEventListener('contextmenu', this._onContextMenu, false)
  70. container.removeEventListener('pointerdown', this._onPointerDown, false)
  71. container.removeEventListener('pointerup', this._onPointerUp, false)
  72. container.removeEventListener('pointermove', this._onPointerMove, false)
  73. application.pointSelector.deactivate()
  74. }
  75. onPointerDown(event) {
  76. const pointSelector = this.application.pointSelector
  77. if (!pointSelector.isPointSelectionEvent(event)) return
  78. event.preventDefault()
  79. this.preparePlacement()
  80. this.snap = pointSelector.snap
  81. this.placeObject()
  82. this.setStage(1)
  83. }
  84. onPointerMove(event) {
  85. if (this.stage === 0) return
  86. const pointSelector = this.application.pointSelector
  87. if (!pointSelector.isPointSelectionEvent(event)) return
  88. event.preventDefault()
  89. this.snap = pointSelector.snap
  90. this.placeObject()
  91. }
  92. onPointerUp(event) {
  93. if (this.stage === 1) {
  94. event.preventDefault()
  95. this.setStage(0)
  96. }
  97. }
  98. onContextMenu(event) {
  99. const pointSelector = this.application.pointSelector
  100. if (!pointSelector.isPointSelectionEvent(event)) return
  101. event.preventDefault()
  102. }
  103. setStage(stage) {
  104. this.stage = stage
  105. }
  106. preparePlacement() {
  107. const application = this.application
  108. const object = application.selection.object
  109. if (application.selection.size > 1) {
  110. application.selection.set(object)
  111. }
  112. }
  113. placeObject() {
  114. const application = this.application
  115. const object = application.selection.object
  116. const snap = this.snap
  117. if (object === null || object.parent === null || snap === null) return
  118. const axisMatrixWorld = this.axisMatrixWorld
  119. switch (this.mode) {
  120. case PlaceTool.GLOBAL_MODE:
  121. axisMatrixWorld.identity()
  122. break
  123. case PlaceTool.LOCAL_MODE:
  124. axisMatrixWorld.copy(snap.object.matrixWorld)
  125. break
  126. case PlaceTool.PERPENDICULAR_MODE:
  127. if (snap.normalWorld) {
  128. this.setPerpendicularMatrix(snap, axisMatrixWorld)
  129. } else {
  130. axisMatrixWorld.identity()
  131. }
  132. break
  133. }
  134. axisMatrixWorld.setPosition(snap.positionWorld)
  135. const matrix = this.localMatrix
  136. matrix.copy(object.parent.matrixWorld).invert()
  137. matrix.multiply(axisMatrixWorld)
  138. matrix.decompose(object.position, object.rotation, new THREE.Vector3())
  139. object.updateMatrix()
  140. application.notifyObjectsChanged(object, this, 'nodeChanged', PlaceTool.CHANGED_PROPERTIES)
  141. }
  142. setPerpendicularMatrix(snap, axisMatrixWorld) {
  143. const objectMatrixWorld = snap.object.matrixWorld
  144. const xAxis = new THREE.Vector3()
  145. const yAxis = new THREE.Vector3()
  146. const zAxis = new THREE.Vector3()
  147. const upVector = new THREE.Vector3(0, 0, 1)
  148. zAxis.copy(snap.normalWorld)
  149. if (Math.abs(upVector.dot(zAxis)) < 0.999) {
  150. xAxis.crossVectors(upVector, zAxis)
  151. yAxis.crossVectors(zAxis, xAxis)
  152. } else {
  153. GeometryUtils.orthogonalVector(zAxis, yAxis)
  154. xAxis.crossVectors(yAxis, zAxis).normalize()
  155. }
  156. xAxis.normalize()
  157. yAxis.normalize()
  158. zAxis.normalize()
  159. axisMatrixWorld.makeBasis(xAxis, yAxis, zAxis)
  160. }
  161. vectorToGlobal(vector, matrixWorld) {
  162. vector.applyMatrix4(matrixWorld)
  163. let origin = new THREE.Vector3()
  164. origin.setFromMatrixPosition(matrixWorld)
  165. vector.sub(origin)
  166. }
  167. }
  168. export { PlaceTool }