SectionTool.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /*
  2. * SectionTool.js
  3. *
  4. * @author realor
  5. */
  6. import { Tool } from './Tool.js'
  7. import { SolidGeometry } from '../core/SolidGeometry.js'
  8. import { GeometryUtils } from '../utils/GeometryUtils.js'
  9. import { Controls } from '../ui/Controls.js'
  10. import { GestureHandler } from '../ui/GestureHandler.js'
  11. import { I18N } from '../i18n/I18N.js'
  12. import * as THREE from '../lib/three.module.js'
  13. class SectionTool extends Tool {
  14. constructor(application, options) {
  15. super(application)
  16. this.name = 'section'
  17. this.label = 'tool.section.label'
  18. this.help = 'tool.section.help'
  19. this.className = 'section'
  20. this.setOptions(options)
  21. var plane = new THREE.Plane()
  22. this.plane = plane
  23. this.planes = [this.plane]
  24. this.noPlanes = []
  25. this.basePoint = null
  26. this.offset = 0
  27. this.meshes = []
  28. let backFaceStencilMat = new THREE.MeshBasicMaterial()
  29. backFaceStencilMat.depthWrite = false
  30. backFaceStencilMat.depthTest = false
  31. backFaceStencilMat.colorWrite = false
  32. backFaceStencilMat.stencilWrite = true
  33. backFaceStencilMat.stencilFunc = THREE.AlwaysStencilFunc
  34. backFaceStencilMat.side = THREE.BackSide
  35. backFaceStencilMat.stencilFail = THREE.IncrementWrapStencilOp
  36. backFaceStencilMat.stencilZFail = THREE.IncrementWrapStencilOp
  37. backFaceStencilMat.stencilZPass = THREE.IncrementWrapStencilOp
  38. backFaceStencilMat.clippingPlanes = this.planes
  39. this.backFaceStencilMat = backFaceStencilMat
  40. let frontFaceStencilMat = new THREE.MeshBasicMaterial()
  41. frontFaceStencilMat.depthWrite = false
  42. frontFaceStencilMat.depthTest = false
  43. frontFaceStencilMat.colorWrite = false
  44. frontFaceStencilMat.stencilWrite = true
  45. frontFaceStencilMat.stencilFunc = THREE.AlwaysStencilFunc
  46. frontFaceStencilMat.side = THREE.FrontSide
  47. frontFaceStencilMat.stencilFail = THREE.DecrementWrapStencilOp
  48. frontFaceStencilMat.stencilZFail = THREE.DecrementWrapStencilOp
  49. frontFaceStencilMat.stencilZPass = THREE.DecrementWrapStencilOp
  50. frontFaceStencilMat.clippingPlanes = this.planes
  51. this.frontFaceStencilMat = frontFaceStencilMat
  52. let sectionColor = window.localStorage.getItem('bimrocket.sectionColor')
  53. if (sectionColor === null) sectionColor = '#808080'
  54. let planeStencilMat = new THREE.MeshLambertMaterial()
  55. planeStencilMat.color = new THREE.Color(sectionColor)
  56. planeStencilMat.emissive = new THREE.Color(0x404040)
  57. planeStencilMat.side = THREE.DoubleSide
  58. planeStencilMat.stencilWrite = true
  59. planeStencilMat.flatShading = true
  60. planeStencilMat.stencilRef = 0
  61. planeStencilMat.stencilFunc = THREE.NotEqualStencilFunc
  62. planeStencilMat.stencilFail = THREE.ReplaceStencilOp
  63. planeStencilMat.stencilZFail = THREE.ReplaceStencilOp
  64. planeStencilMat.stencilZPass = THREE.ReplaceStencilOp
  65. const manager = this.application.loadingManager
  66. const textureLoader = new THREE.TextureLoader(manager)
  67. textureLoader.load('textures/section.png', texture => {
  68. texture.wrapS = THREE.RepeatWrapping
  69. texture.wrapT = THREE.RepeatWrapping
  70. texture.repeat.set(200, 200)
  71. planeStencilMat.map = texture
  72. planeStencilMat.needsUpdate = true
  73. })
  74. this.planeStencilMat = planeStencilMat
  75. let planeGeom = new THREE.PlaneBufferGeometry(100, 100)
  76. let planeMesh = new THREE.Mesh(planeGeom, planeStencilMat)
  77. planeMesh.renderOrder = 1
  78. planeMesh.raycast = () => {}
  79. planeMesh.name = 'sectionPlane'
  80. this.planeMesh = planeMesh
  81. this._onWheel = this.onWheel.bind(this)
  82. this.createPanel()
  83. this.sectionColorElem.value = sectionColor
  84. this.gestureHandler = new GestureHandler(this)
  85. }
  86. createPanel() {
  87. const application = this.application
  88. this.panel = application.createPanel(this.label, 'left', 'panel_section')
  89. this.panel.preferredHeight = 160
  90. const helpElem = document.createElement('div')
  91. this.panel.bodyElem.appendChild(helpElem)
  92. this.sectionColorElem = Controls.addColorField(this.panel.bodyElem, 'section_color', 'label.section_color')
  93. this.sectionColorElem.addEventListener(
  94. 'change',
  95. event => {
  96. let sectionColor = this.sectionColorElem.value
  97. window.localStorage.setItem('bimrocket.sectionColor', sectionColor)
  98. this.planeStencilMat.color = new THREE.Color(sectionColor)
  99. application.repaint()
  100. },
  101. false
  102. )
  103. this.offsetInputElem = Controls.addNumberField(this.panel.bodyElem, 'section_offset', 'label.offset', 0)
  104. this.offsetElem = this.offsetInputElem.parentElement
  105. this.offsetElem.style.display = 'none'
  106. this.offsetInputElem.addEventListener(
  107. 'change',
  108. event => {
  109. this.offset = parseFloat(this.offsetInputElem.value)
  110. this.updatePlane()
  111. application.repaint()
  112. },
  113. false
  114. )
  115. this.cancelButton = Controls.addButton(this.offsetElem, 'cancel_section', 'button.cancel', event => {
  116. this.disableClipping()
  117. this.updateOffsetLabel()
  118. this.application.repaint()
  119. })
  120. I18N.set(helpElem, 'innerHTML', this.help)
  121. }
  122. activate() {
  123. this.panel.visible = true
  124. const container = this.application.container
  125. container.addEventListener('wheel', this._onWheel, false)
  126. this.gestureHandler.enable()
  127. }
  128. deactivate() {
  129. this.panel.visible = false
  130. const container = this.application.container
  131. container.removeEventListener('wheel', this._onWheel, false)
  132. this.gestureHandler.disable()
  133. }
  134. onTap(position, button) {
  135. const application = this.application
  136. const scene = application.scene
  137. const intersect = this.intersect(position, scene, true)
  138. if (intersect) {
  139. const object = intersect.object
  140. this.basePoint = intersect.point // world
  141. let v1 = new THREE.Vector3(0, 0, 0) // local
  142. let v2 = intersect.face.normal.clone() // local
  143. v1.applyMatrix4(object.matrixWorld)
  144. v2.applyMatrix4(object.matrixWorld)
  145. const normal = new THREE.Vector3().subVectors(v1, v2).normalize()
  146. this.plane.normal = normal
  147. this.offset = 0
  148. this.updatePlane()
  149. this.enableClipping()
  150. } else {
  151. this.disableClipping()
  152. }
  153. this.updateOffsetLabel()
  154. application.repaint()
  155. }
  156. onDrag(position, direction, pointerCount, button) {
  157. const application = this.application
  158. if (!application.renderer.localClippingEnabled) return
  159. let absDir = Math.abs(direction.y)
  160. this.offset += 0.005 * Math.sign(direction.y) * Math.pow(absDir, 1.5)
  161. this.offset = Math.round(1000 * this.offset) / 1000
  162. this.updatePlane()
  163. this.updateOffsetLabel()
  164. application.repaint()
  165. }
  166. onWheel(event) {
  167. if (!this.isCanvasEvent(event)) return
  168. const application = this.application
  169. if (!application.renderer.localClippingEnabled) return
  170. let delta = 0
  171. if (event.wheelDelta) {
  172. // WebKit / Opera / Explorer 9
  173. delta = event.wheelDelta * 0.0005
  174. } else if (event.detail) {
  175. // Firefox
  176. delta = -0.005 * event.detail
  177. }
  178. this.offset += delta
  179. this.offset = Math.round(1000 * this.offset) / 1000
  180. this.updatePlane()
  181. this.updateOffsetLabel()
  182. application.repaint()
  183. }
  184. enableClipping() {
  185. const application = this.application
  186. if (application.renderer.localClippingEnabled) return
  187. application.baseObject.traverse(object => {
  188. let material = object.material
  189. if (material && object.visible) {
  190. material.clippingPlanes = this.planes
  191. if (object instanceof THREE.Mesh) {
  192. if (object.geometry instanceof SolidGeometry) {
  193. let geometry = object.geometry
  194. if (geometry.isManifold && geometry.faces.length >= 4) {
  195. this.meshes.push(object)
  196. }
  197. }
  198. }
  199. }
  200. })
  201. for (let i = 0; i < this.meshes.length; i++) {
  202. let mesh = this.meshes[i]
  203. let backMesh = new THREE.Mesh(mesh.geometry, this.backFaceStencilMat)
  204. backMesh.name = THREE.Object3D.HIDDEN_PREFIX + 'backMesh'
  205. backMesh.raycast = () => {}
  206. mesh.add(backMesh)
  207. backMesh.updateMatrix()
  208. let frontMesh = new THREE.Mesh(mesh.geometry, this.frontFaceStencilMat)
  209. frontMesh.name = THREE.Object3D.HIDDEN_PREFIX + 'frontMesh'
  210. frontMesh.raycast = function() {}
  211. mesh.add(frontMesh)
  212. frontMesh.updateMatrix()
  213. }
  214. application.clippingGroup.add(this.planeMesh)
  215. application.clippingPlane = this.plane
  216. application.renderer.localClippingEnabled = true
  217. }
  218. disableClipping() {
  219. const application = this.application
  220. if (!application.renderer.localClippingEnabled) return
  221. application.clippingGroup.remove(this.planeMesh)
  222. application.clippingPlane = null
  223. for (let i = 0; i < this.meshes.length; i++) {
  224. const mesh = this.meshes[i]
  225. let frontMesh = mesh.getObjectByName(THREE.Object3D.HIDDEN_PREFIX + 'frontMesh')
  226. if (frontMesh) {
  227. mesh.remove(frontMesh)
  228. }
  229. let backMesh = mesh.getObjectByName(THREE.Object3D.HIDDEN_PREFIX + 'backMesh')
  230. if (backMesh) {
  231. mesh.remove(backMesh)
  232. }
  233. }
  234. application.baseObject.traverse(object => {
  235. let material = object.material
  236. if (material && object.visible) {
  237. material.clippingPlanes = this.noPlanes
  238. }
  239. })
  240. this.meshes = []
  241. this.basePoint = null
  242. application.renderer.localClippingEnabled = false
  243. }
  244. updatePlane() {
  245. let planeMesh = this.planeMesh
  246. let normal = this.plane.normal
  247. let position = this.basePoint.clone().addScaledVector(normal, this.offset)
  248. this.plane.setFromNormalAndCoplanarPoint(normal, position)
  249. let vz = normal
  250. let vy = GeometryUtils.orthogonalVector(vz)
  251. let vx = new THREE.Vector3()
  252. vx.crossVectors(vy, vz)
  253. let matrix = new THREE.Matrix4()
  254. matrix.set(vx.x, vy.x, vz.x, position.x, vx.y, vy.y, vz.y, position.y, vx.z, vy.z, vz.z, position.z, 0, 0, 0, 1)
  255. matrix.decompose(planeMesh.position, planeMesh.quaternion, planeMesh.scale)
  256. planeMesh.updateMatrix()
  257. }
  258. updateOffsetLabel() {
  259. if (this.application.renderer.localClippingEnabled) {
  260. this.offsetElem.style.display = 'block'
  261. this.offsetInputElem.value = this.offset.toFixed(3)
  262. } else {
  263. this.offsetElem.style.display = 'none'
  264. }
  265. }
  266. }
  267. export { SectionTool }