PointSelector.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. /**
  2. * PointSelector.js
  3. *
  4. * @author realor
  5. */
  6. import { Application } from '../ui/Application.js'
  7. import { GeometryUtils } from './GeometryUtils.js'
  8. import { ObjectUtils } from './ObjectUtils.js'
  9. import { Solid } from '../core/Solid.js'
  10. import { Profile } from '../core/Profile.js'
  11. import { Cord } from '../core/Cord.js'
  12. import { I18N } from '../i18n/I18N.js'
  13. import { SolidGeometry } from '../core/SolidGeometry.js'
  14. import * as THREE from '../lib/three.module.js'
  15. class PointSelector {
  16. static VERTEX_SNAP = 0
  17. static INTERSECTION_SNAP = 1
  18. static PROJECTION_SNAP = 2
  19. static EDGE_SNAP = 3
  20. static GUIDE_SNAP = 4
  21. static FACE_SNAP = 5
  22. constructor(application) {
  23. this.application = application
  24. this.activated = false
  25. this.snapDistance = 16
  26. this.snapSize = 8
  27. this.snapColors = ['black', 'purple', 'green', 'blue', 'orange', 'red']
  28. this.snaps = []
  29. this.snap = null
  30. this.projectionSnap = null
  31. this.temporalSnap = null
  32. this.snapTimestamp = 0
  33. this.projectionSnapTime = 500 // 1/2 second to set the projection vertex
  34. this.auxiliaryPoints = [] // array of global Vector3
  35. this.auxiliaryLines = [] // array of global Line3
  36. this.touchPointerOffsetX = -40
  37. this.touchPointerOffsetY = -40
  38. this.excludeSelection = false
  39. this.debug = false
  40. this.axisGuides = [
  41. {
  42. label: 'label.on_x_axis',
  43. startPoint: new THREE.Vector3(),
  44. endPoint: new THREE.Vector3(),
  45. startLocal: new THREE.Vector3(-1, 0, 0),
  46. endLocal: new THREE.Vector3(1, 0, 0),
  47. material: new THREE.LineBasicMaterial({ color: new THREE.Color(1, 0, 0), transparent: true, opacity: 0.4 })
  48. },
  49. {
  50. label: 'label.on_y_axis',
  51. startPoint: new THREE.Vector3(),
  52. endPoint: new THREE.Vector3(),
  53. startLocal: new THREE.Vector3(0, -1, 0),
  54. endLocal: new THREE.Vector3(0, 1, 0),
  55. material: new THREE.LineBasicMaterial({ color: new THREE.Color(0, 1, 0), transparent: true, opacity: 0.4 })
  56. },
  57. {
  58. label: 'label.on_z_axis',
  59. startPoint: new THREE.Vector3(),
  60. endPoint: new THREE.Vector3(),
  61. startLocal: new THREE.Vector3(0, 0, -1),
  62. endLocal: new THREE.Vector3(0, 0, 1),
  63. material: new THREE.LineBasicMaterial({ color: new THREE.Color(0, 0, 1), transparent: true, opacity: 0.4 })
  64. }
  65. ]
  66. this.axisGuidesEnabled = false
  67. this.axisMatrixWorld = new THREE.Matrix4()
  68. this.axisMatrixWorldInverse = new THREE.Matrix4()
  69. this.snapElem = document.createElement('div')
  70. const snapElem = this.snapElem
  71. snapElem.style.position = 'absolute'
  72. snapElem.style.display = 'none'
  73. snapElem.style.width = this.snapSize + 'px'
  74. snapElem.style.height = this.snapSize + 'px'
  75. application.container.appendChild(snapElem)
  76. this.projectionSnapElem = document.createElement('div')
  77. const projectionSnapElem = this.projectionSnapElem
  78. projectionSnapElem.style.position = 'absolute'
  79. projectionSnapElem.style.display = 'none'
  80. projectionSnapElem.style.width = this.snapSize + 'px'
  81. projectionSnapElem.style.height = this.snapSize + 'px'
  82. application.container.appendChild(projectionSnapElem)
  83. this._onPointerMove = this.onPointerMove.bind(this)
  84. this._onPointerUp = this.onPointerUp.bind(this)
  85. }
  86. activate() {
  87. if (!this.activated) {
  88. const application = this.application
  89. const container = application.container
  90. container.addEventListener('pointermove', this._onPointerMove, false)
  91. container.addEventListener('pointerup', this._onPointerUp, false)
  92. this.activated = true
  93. }
  94. }
  95. deactivate() {
  96. if (this.activated) {
  97. const application = this.application
  98. const container = application.container
  99. container.removeEventListener('pointermove', this._onPointerMove, false)
  100. container.removeEventListener('pointerup', this._onPointerUp, false)
  101. this.snapElem.style.display = 'none'
  102. this.activated = false
  103. }
  104. }
  105. onPointerUp(event) {
  106. if (!this.isPointSelectionEvent(event)) return
  107. this.snapElem.style.display = 'none'
  108. this.projectionSnapElem.style.display = 'none'
  109. }
  110. onPointerMove(event) {
  111. if (!this.isPointSelectionEvent(event)) return
  112. const application = this.application
  113. const container = application.container
  114. const snapElem = this.snapElem
  115. const projectionSnapElem = this.projectionSnapElem
  116. const projectionSnap = this.projectionSnap
  117. let rect = container.getBoundingClientRect()
  118. const pointerPosition = new THREE.Vector2()
  119. pointerPosition.x = event.clientX - rect.left
  120. pointerPosition.y = event.clientY - rect.top
  121. if (event.pointerType === 'touch') {
  122. pointerPosition.x += this.touchPointerOffsetX
  123. pointerPosition.y += this.touchPointerOffsetY
  124. }
  125. const snaps = this.findSnaps(pointerPosition)
  126. const snap = this.selectRelevantSnap(snaps)
  127. let updateTimestamp = true
  128. if (snap) {
  129. snapElem.style.left = snap.positionScreen.x - this.snapSize / 2 + 'px'
  130. snapElem.style.top = snap.positionScreen.y - this.snapSize / 2 + 'px'
  131. snapElem.style.display = ''
  132. snapElem.style.border = '1px solid white'
  133. snapElem.style.borderRadius = '0'
  134. snapElem.style.backgroundColor = this.snapColors[snap.type]
  135. I18N.set(snapElem, 'title', snap.label)
  136. application.i18n.update(snapElem)
  137. if (this.temporalSnap) {
  138. if (snap.positionScreen.equals(this.temporalSnap.positionScreen)) {
  139. // do not update timestamp if the snap position does not change
  140. updateTimestamp = false
  141. }
  142. if (Date.now() - this.snapTimestamp > this.projectionSnapTime) {
  143. // if pointer is on snap for more than projectionSnapTime then
  144. // save projectionSnap
  145. this.projectionSnap = this.temporalSnap
  146. }
  147. }
  148. this.snap = snap
  149. if (snap.type === PointSelector.VERTEX_SNAP || snap.type === PointSelector.INTERSECTION_SNAP) {
  150. this.temporalSnap = snap
  151. } else if (snap.type === PointSelector.PROJECTION_SNAP && projectionSnap) {
  152. const clientWidth = container.clientWidth
  153. const clientHeight = container.clientHeight
  154. let vector = new THREE.Vector3()
  155. vector.copy(projectionSnap.positionWorld).project(application.camera)
  156. let screenPosition = new THREE.Vector3()
  157. screenPosition.x = 0.5 * clientWidth * (vector.x + 1)
  158. screenPosition.y = 0.5 * clientHeight * (1 - vector.y)
  159. projectionSnapElem.style.left = screenPosition.x - this.snapSize / 2 + 'px'
  160. projectionSnapElem.style.top = screenPosition.y - this.snapSize / 2 + 'px'
  161. projectionSnapElem.style.display = ''
  162. projectionSnapElem.style.backgroundColor = 'green'
  163. projectionSnapElem.style.borderRadius = this.snapSize + 'px'
  164. projectionSnapElem.style.border = '1px solid white'
  165. this.temporalSnap = null
  166. } else {
  167. projectionSnapElem.style.display = 'none'
  168. this.temporalSnap = null
  169. }
  170. } else {
  171. if (event.pointerType === 'touch') {
  172. snapElem.style.left = pointerPosition.x - this.snapSize / 2 + 'px'
  173. snapElem.style.top = pointerPosition.y - this.snapSize / 2 + 'px'
  174. snapElem.style.display = ''
  175. snapElem.style.border = '1px solid black'
  176. snapElem.style.borderRadius = this.snapSize + 'px'
  177. snapElem.style.backgroundColor = 'transparent'
  178. snapElem.title = ''
  179. } else {
  180. snapElem.style.display = 'none'
  181. }
  182. projectionSnapElem.style.display = 'none'
  183. this.snap = null
  184. this.temporalSnap = null
  185. }
  186. if (updateTimestamp) this.snapTimestamp = Date.now()
  187. this.snaps = this.debug ? snaps : null
  188. }
  189. setAxisGuides(axisMatrixWorld, visible = false) {
  190. this.axisGuidesEnabled = true
  191. this.axisMatrixWorld.copy(axisMatrixWorld)
  192. this.axisMatrixWorldInverse.copy(axisMatrixWorld).invert()
  193. const scale = axisMatrixWorld.getMaxScaleOnAxis()
  194. const factor = 1 / scale
  195. let scaledAxisMatrixWorld = new THREE.Matrix4()
  196. scaledAxisMatrixWorld.makeScale(factor, factor, factor)
  197. scaledAxisMatrixWorld.premultiply(axisMatrixWorld)
  198. let k = 1000
  199. for (let guide of this.axisGuides) {
  200. guide.startPoint
  201. .copy(guide.startLocal)
  202. .multiplyScalar(k)
  203. .applyMatrix4(scaledAxisMatrixWorld)
  204. guide.endPoint
  205. .copy(guide.endLocal)
  206. .multiplyScalar(k)
  207. .applyMatrix4(scaledAxisMatrixWorld)
  208. }
  209. if (this.axisGroup) {
  210. this.application.removeObject(this.axisGroup)
  211. this.axisGroup = null
  212. }
  213. if (visible) {
  214. this.axisGroup = new THREE.Group()
  215. this.axisGroup.name = 'Axis guides'
  216. for (let guide of this.axisGuides) {
  217. let geometryPoints = []
  218. geometryPoints.push(guide.startPoint)
  219. geometryPoints.push(guide.endPoint)
  220. let geometry = new THREE.BufferGeometry()
  221. geometry.setFromPoints(geometryPoints)
  222. let line = new THREE.Line(geometry, guide.material)
  223. line.name = guide.label
  224. line.raycast = function() {}
  225. this.axisGroup.add(line)
  226. }
  227. this.application.addObject(this.axisGroup, this.application.overlays)
  228. }
  229. }
  230. clearAxisGuides() {
  231. if (this.axisGroup) {
  232. this.application.removeObject(this.axisGroup)
  233. this.axisGroup = null
  234. }
  235. this.axisGuidesEnabled = false
  236. }
  237. findSnaps(pointerPosition) {
  238. const camera = this.application.camera
  239. const container = this.application.container
  240. const clientWidth = container.clientWidth
  241. const clientHeight = container.clientHeight
  242. const baseObject = this.application.baseObject
  243. const raycaster = new THREE.Raycaster()
  244. const positionWorld = new THREE.Vector3()
  245. const positionScreen = new THREE.Vector2()
  246. const triangleWorld = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()]
  247. const vector = new THREE.Vector3()
  248. const point1 = new THREE.Vector3()
  249. const point2 = new THREE.Vector3()
  250. const sphere = new THREE.Sphere()
  251. const snapKeySet = new Set()
  252. let snaps = []
  253. let pointercc = new THREE.Vector2()
  254. pointercc.x = (pointerPosition.x / container.clientWidth) * 2 - 1
  255. pointercc.y = -(pointerPosition.y / container.clientHeight) * 2 + 1
  256. raycaster.setFromCamera(pointercc, camera)
  257. raycaster.far = Math.Infinity
  258. raycaster.camera = camera
  259. const worldToScreen = (positionWorld, screenPosition) => {
  260. vector.copy(positionWorld).project(camera)
  261. screenPosition.x = 0.5 * clientWidth * (vector.x + 1)
  262. screenPosition.y = 0.5 * clientHeight * (1 - vector.y)
  263. }
  264. const rayIntersectsObject = object => {
  265. const geometry = object.geometry
  266. const matrixWorld = object.matrixWorld
  267. if (geometry === undefined) return false
  268. if (geometry.boundingSphere === null) geometry.computeBoundingSphere()
  269. sphere.copy(geometry.boundingSphere)
  270. sphere.radius *= 1.2
  271. sphere.applyMatrix4(matrixWorld)
  272. return raycaster.ray.intersectsSphere(sphere)
  273. }
  274. const isNewSnap = (type, snapPositionWorld) => {
  275. const k = 10000
  276. const snapKey = type + ':' + Math.round(snapPositionWorld.x * k) / k + ',' + Math.round(snapPositionWorld.y * k) / k + ',' + Math.round(snapPositionWorld.z * k) / k
  277. if (snapKeySet.has(snapKey)) {
  278. return false
  279. } else {
  280. snapKeySet.add(snapKey)
  281. return true
  282. }
  283. }
  284. const addVertexSnap = (object, vertex, label, type) => {
  285. positionWorld.copy(vertex)
  286. if (object) {
  287. positionWorld.applyMatrix4(object.matrixWorld)
  288. }
  289. worldToScreen(positionWorld, positionScreen)
  290. let distanceScreen = positionScreen.distanceTo(pointerPosition)
  291. if (distanceScreen < this.snapDistance) {
  292. if (isNewSnap(type, positionWorld)) {
  293. snaps.push({
  294. label: label,
  295. type: type,
  296. object: object,
  297. positionScreen: positionScreen.clone(),
  298. distanceScreen: distanceScreen,
  299. positionWorld: positionWorld.clone(),
  300. distanceWorld: positionWorld.distanceTo(camera.position)
  301. })
  302. }
  303. return true
  304. }
  305. return false
  306. }
  307. const addEdgeSnap = (object, vertex1, vertex2, label, type) => {
  308. point1.copy(vertex1)
  309. point2.copy(vertex2)
  310. if (object) {
  311. const matrixWorld = object.matrixWorld
  312. point1.applyMatrix4(matrixWorld)
  313. point2.applyMatrix4(matrixWorld)
  314. }
  315. const ds = raycaster.ray.distanceSqToSegment(point1, point2, null, positionWorld)
  316. if (ds < 0.1) {
  317. worldToScreen(positionWorld, positionScreen)
  318. let distanceScreen = positionScreen.distanceTo(pointerPosition)
  319. if (distanceScreen < this.snapDistance) {
  320. if (isNewSnap(type, positionWorld)) {
  321. snaps.push({
  322. label: label,
  323. type: type,
  324. object: object,
  325. positionScreen: positionScreen.clone(),
  326. distanceScreen: distanceScreen,
  327. positionWorld: positionWorld.clone(),
  328. distanceWorld: positionWorld.distanceTo(camera.position),
  329. line: new THREE.Line3(point1.clone(), point2.clone())
  330. })
  331. }
  332. return true
  333. }
  334. }
  335. return false
  336. }
  337. const addTriangleSnap = (object, face, vertex1, vertex2, vertex3, label, type) => {
  338. triangleWorld[0].copy(vertex1)
  339. triangleWorld[1].copy(vertex2)
  340. triangleWorld[2].copy(vertex3)
  341. if (object) {
  342. for (let i = 0; i < 3; i++) {
  343. triangleWorld[i].applyMatrix4(object.matrixWorld)
  344. }
  345. }
  346. if (raycaster.ray.intersectTriangle(triangleWorld[0], triangleWorld[1], triangleWorld[2], false, positionWorld) !== null) {
  347. if (isNewSnap(type, positionWorld)) {
  348. let plane = new THREE.Plane()
  349. plane.setFromCoplanarPoints(triangleWorld[0], triangleWorld[1], triangleWorld[2])
  350. snaps.push({
  351. label: label,
  352. type: type,
  353. object: object,
  354. positionScreen: pointerPosition.clone(),
  355. distanceScreen: 0,
  356. positionWorld: positionWorld.clone(),
  357. distanceWorld: positionWorld.distanceTo(camera.position),
  358. normalWorld: GeometryUtils.calculateNormal(triangleWorld),
  359. face: face,
  360. triangle: [triangleWorld[0].clone(), triangleWorld[1].clone(), triangleWorld[2].clone()],
  361. plane: plane
  362. })
  363. }
  364. return true
  365. }
  366. return false
  367. }
  368. const addSolidVertexSnaps = object => {
  369. const vertices = object.geometry.vertices
  370. for (let vertex of vertices) {
  371. addVertexSnap(object, vertex, 'label.on_vertex', PointSelector.VERTEX_SNAP)
  372. }
  373. }
  374. const addSolidEdgeSnaps = object => {
  375. const matrixWorld = object.matrixWorld
  376. const geometry = object.geometry
  377. for (let face of geometry.faces) {
  378. addSolidLoopEdgeSnaps(object, face.outerLoop)
  379. for (let hole of face.holes) {
  380. addSolidLoopEdgeSnaps(object, hole)
  381. }
  382. }
  383. }
  384. const addSolidLoopEdgeSnaps = (object, loop) => {
  385. const isManifold = object.geometry.isManifold
  386. const vertices = object.geometry.vertices
  387. const matrixWorld = object.matrixWorld
  388. const size = loop.getVertexCount()
  389. for (let i = 0; i < size; i++) {
  390. let index1 = loop.indices[i]
  391. let index2 = loop.indices[(i + 1) % size]
  392. if (isManifold && index1 > index2) continue
  393. let vertex1 = vertices[index1]
  394. let vertex2 = vertices[index2]
  395. addEdgeSnap(object, vertex1, vertex2, 'label.on_edge', PointSelector.EDGE_SNAP)
  396. }
  397. }
  398. const addSolidFaceSnaps = object => {
  399. const matrixWorld = object.matrixWorld
  400. const geometry = object.geometry
  401. const vertices = geometry.vertices
  402. for (let face of geometry.faces) {
  403. for (let indices of face.getTriangles()) {
  404. if (addTriangleSnap(object, face, vertices[indices[0]], vertices[indices[1]], vertices[indices[2]], 'label.on_face', PointSelector.FACE_SNAP)) {
  405. break
  406. }
  407. }
  408. }
  409. }
  410. const addProfileSnaps = object => {
  411. const path = object.geometry.path
  412. const points = path.getPoints(object.geometry.divisions)
  413. let vertex1 = point1
  414. let vertex2 = point2
  415. for (let i = 0; i < points.length; i++) {
  416. let p1 = points[i]
  417. let p2 = points[(i + 1) % points.length]
  418. vertex1.x = p1.x
  419. vertex1.y = p1.y
  420. vertex1.z = 0
  421. vertex2.x = p2.x
  422. vertex2.y = p2.y
  423. vertex2.z = 0
  424. addVertexSnap(object, vertex1, 'label.on_vertex', PointSelector.VERTEX_SNAP)
  425. addEdgeSnap(object, vertex1, vertex2, 'label.on_edge', PointSelector.EDGE_SNAP)
  426. }
  427. }
  428. const addCordSnaps = object => {
  429. const vertices = object.geometry.points
  430. let vertex1 = point1
  431. let vertex2 = point2
  432. for (let i = 0; i < vertices.length - 1; i++) {
  433. vertex1.copy(vertices[i])
  434. vertex2.copy(vertices[i + 1])
  435. addVertexSnap(object, vertex1, 'label.on_vertex', PointSelector.VERTEX_SNAP)
  436. addEdgeSnap(object, vertex1, vertex2, 'label.on_edge', PointSelector.EDGE_SNAP)
  437. }
  438. addVertexSnap(object, vertices[vertices.length - 1], 'label.on_vertex', PointSelector.VERTEX_SNAP)
  439. }
  440. const addBufferGeometrySnaps = object => {
  441. const matrixWorld = object.matrixWorld
  442. const geometry = object.geometry
  443. GeometryUtils.traverseBufferGeometryVertices(geometry, vertex => {
  444. addVertexSnap(object, vertex, 'label.on_vertex', PointSelector.VERTEX_SNAP)
  445. })
  446. }
  447. const addSceneSnaps = () => {
  448. const traverse = object => {
  449. if (object.visible && (!this.excludeSelection || !this.application.selection.contains(object))) {
  450. if (rayIntersectsObject(object)) {
  451. if (object instanceof Solid) {
  452. addSolidVertexSnaps(object)
  453. addSolidEdgeSnaps(object)
  454. addSolidFaceSnaps(object)
  455. } else if (object instanceof Profile) {
  456. addProfileSnaps(object)
  457. } else if (object instanceof Cord) {
  458. addCordSnaps(object)
  459. } else if (object.geometry instanceof THREE.BufferGeometry) {
  460. addBufferGeometrySnaps(object)
  461. }
  462. }
  463. if (!(object instanceof Solid)) {
  464. for (let child of object.children) {
  465. traverse(child)
  466. }
  467. }
  468. }
  469. }
  470. traverse(baseObject)
  471. }
  472. const addProjectionSnaps = () => {
  473. if (this.projectionSnap === null || !this.axisGuidesEnabled) return
  474. let axisMatrixWorld = this.axisMatrixWorld
  475. let axisMatrixWorldInverse = this.axisMatrixWorldInverse
  476. let snapPositionWorld = this.projectionSnap.positionWorld
  477. let snapPosition = new THREE.Vector3()
  478. snapPosition.copy(snapPositionWorld).applyMatrix4(axisMatrixWorldInverse)
  479. let point = new THREE.Vector3()
  480. point.set(snapPosition.x, 0, 0)
  481. point.applyMatrix4(axisMatrixWorld)
  482. addVertexSnap(null, point, 'label.on_projected_vertex', PointSelector.PROJECTION_SNAP)
  483. point.set(0, snapPosition.y, 0)
  484. point.applyMatrix4(axisMatrixWorld)
  485. addVertexSnap(null, point, 'label.on_projected_vertex', PointSelector.PROJECTION_SNAP)
  486. point.set(0, 0, snapPosition.z)
  487. point.applyMatrix4(axisMatrixWorld)
  488. addVertexSnap(null, point, 'label.on_projected_vertex', PointSelector.PROJECTION_SNAP)
  489. point.set(0, snapPosition.y, snapPosition.z)
  490. point.applyMatrix4(axisMatrixWorld)
  491. addVertexSnap(null, point, 'label.on_projected_vertex', PointSelector.PROJECTION_SNAP)
  492. point.set(snapPosition.x, 0, snapPosition.z)
  493. point.applyMatrix4(axisMatrixWorld)
  494. addVertexSnap(null, point, 'label.on_projected_vertex', PointSelector.PROJECTION_SNAP)
  495. point.set(snapPosition.x, snapPosition.y, 0)
  496. point.applyMatrix4(axisMatrixWorld)
  497. addVertexSnap(null, point, 'label.on_projected_vertex', PointSelector.PROJECTION_SNAP)
  498. }
  499. const addAuxiliaryPointSnaps = () => {
  500. for (let auxiliaryPoint of this.auxiliaryPoints) {
  501. addVertexSnap(null, auxiliaryPoint, 'label.on_vertex', PointSelector.VERTEX_SNAP)
  502. }
  503. }
  504. const addAuxiliaryLineSnaps = () => {
  505. for (let auxiliaryLine of this.auxiliaryLines) {
  506. addEdgeSnap(null, auxiliaryLine.start, auxiliaryLine.end, 'label.on_edge', PointSelector.EDGE_SNAP)
  507. }
  508. }
  509. const addAxisGuideSnaps = () => {
  510. if (this.axisGuidesEnabled) {
  511. for (let guide of this.axisGuides) {
  512. addEdgeSnap(null, guide.startPoint, guide.endPoint, guide.label, PointSelector.GUIDE_SNAP)
  513. }
  514. }
  515. }
  516. const filterHiddenSnaps = () => {
  517. // find the first face snap (closest to observer)
  518. let firstFaceSnap = null
  519. for (let snap of snaps) {
  520. if (snap.type === PointSelector.FACE_SNAP) {
  521. if (firstFaceSnap === null || snap.distanceWorld < firstFaceSnap.distanceWorld) {
  522. firstFaceSnap = snap
  523. }
  524. }
  525. }
  526. if (firstFaceSnap === null) return
  527. // discard snaps behind the plane of the first face snap
  528. const visibleSnaps = []
  529. let plane = firstFaceSnap.plane
  530. for (let snap of snaps) {
  531. if (plane.distanceToPoint(snap.positionWorld) >= -0.0001) {
  532. visibleSnaps.push(snap)
  533. }
  534. }
  535. snaps = visibleSnaps
  536. }
  537. const addIntersectionSnaps = () => {
  538. const interSnaps = []
  539. const ray = new THREE.Ray()
  540. for (let snap1 of snaps) {
  541. if (!(snap1.type === PointSelector.EDGE_SNAP || snap1.type === PointSelector.GUIDE_SNAP)) continue
  542. for (let snap2 of snaps) {
  543. if (snap1 === snap2 || (snap1.object === snap2.object && snap1.object)) continue
  544. if (snap2.type === PointSelector.FACE_SNAP) {
  545. // edge/guide - face intersection
  546. let edgeSnap = snap1
  547. let faceSnap = snap2
  548. let plane = faceSnap.plane
  549. if (plane.intersectsLine(edgeSnap.line)) {
  550. vector.subVectors(edgeSnap.line.end, edgeSnap.line.start)
  551. vector.normalize()
  552. ray.set(edgeSnap.line.start, vector)
  553. if (ray.intersectTriangle(faceSnap.triangle[0], faceSnap.triangle[1], faceSnap.triangle[2], false, positionWorld) !== null) {
  554. worldToScreen(positionWorld, positionScreen)
  555. let distanceScreen = positionScreen.distanceTo(pointerPosition)
  556. if (distanceScreen < this.snapDistance) {
  557. let label = snap1.type === PointSelector.EDGE_SNAP ? 'label.on_edge_face' : 'label.on_guide_face'
  558. interSnaps.push({
  559. label: label,
  560. type: PointSelector.INTERSECTION_SNAP,
  561. object: faceSnap.object || edgeSnap.object,
  562. positionScreen: positionScreen.clone(),
  563. distanceScreen: distanceScreen,
  564. positionWorld: positionWorld.clone(),
  565. distanceWorld: positionWorld.distanceTo(camera.position),
  566. normalWorld: snap2.normalWorld,
  567. snap1: snap1,
  568. snap2: snap2
  569. })
  570. }
  571. }
  572. }
  573. } else if (snap2.type === PointSelector.EDGE_SNAP) {
  574. // guide - edge intersection
  575. let distance = GeometryUtils.intersectLines(snap1.line, snap2.line, point1, point2)
  576. if (Math.abs(distance) < 0.0001) {
  577. positionWorld
  578. .copy(point1)
  579. .add(point2)
  580. .multiplyScalar(0.5)
  581. worldToScreen(positionWorld, positionScreen)
  582. let distanceScreen = positionScreen.distanceTo(pointerPosition)
  583. if (distanceScreen < this.snapDistance) {
  584. let label = snap1.type === PointSelector.EDGE_SNAP ? 'label.on_edge_edge' : 'label.on_guide_edge'
  585. interSnaps.push({
  586. label: label,
  587. type: PointSelector.INTERSECTION_SNAP,
  588. object: snap1.object || snap2.object,
  589. positionScreen: positionScreen.clone(),
  590. distanceScreen: distanceScreen,
  591. positionWorld: positionWorld.clone(),
  592. distanceWorld: positionWorld.distanceTo(camera.position),
  593. snap1: snap1,
  594. snap2: snap2
  595. })
  596. continue
  597. }
  598. }
  599. }
  600. }
  601. }
  602. snaps.push(...interSnaps)
  603. }
  604. const setSnapNormals = () => {
  605. for (let snap1 of snaps) {
  606. if (snap1.type === PointSelector.FACE_SNAP) {
  607. for (let snap2 of snaps) {
  608. if (snap1.object === snap2.object) continue
  609. if (snap2.type === PointSelector.VERTEX_SNAP || snap2.type === PointSelector.EDGE_SNAP || snap2.type === PointSelector.INTERSECTION_SNAP) {
  610. // does the face contains the vertex/egde ?
  611. let plane = snap1.plane
  612. let distance = plane.distanceToPoint(snap2.positionWorld)
  613. if (Math.abs(distance) < 0.000001) {
  614. // copy face normal to vertex/edge snap
  615. snap2.normalWorld = snap1.normalWorld
  616. }
  617. }
  618. }
  619. }
  620. }
  621. }
  622. addSceneSnaps()
  623. addProjectionSnaps()
  624. addAuxiliaryPointSnaps()
  625. addAuxiliaryLineSnaps()
  626. addAxisGuideSnaps()
  627. addIntersectionSnaps()
  628. filterHiddenSnaps()
  629. setSnapNormals()
  630. return snaps
  631. }
  632. selectRelevantSnap(snaps) {
  633. if (snaps.length === 0) return null
  634. let selectedSnap = snaps[0]
  635. for (let snap of snaps) {
  636. if (snap.type < selectedSnap.type || (snap.type === selectedSnap.type && snap.distanceScreen < selectedSnap.distanceScreen)) {
  637. selectedSnap = snap
  638. }
  639. }
  640. return selectedSnap
  641. }
  642. isPointSelectionEvent(event) {
  643. if (this.application.menuBar.armed) return false
  644. const target = event.target || event.srcElement
  645. const snapElem = this.snapElem
  646. return target.nodeName.toLowerCase() === 'canvas' || target === snapElem
  647. }
  648. }
  649. export { PointSelector }