123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- /*
- * RotateTool.js
- *
- * @author realor
- */
- import { TransformationTool } from './TransformationTool.js'
- import { Controls } from '../ui/Controls.js'
- import { I18N } from '../i18n/I18N.js'
- import { PointSelector } from '../utils/PointSelector.js'
- import { GeometryUtils } from '../utils/GeometryUtils.js'
- import * as THREE from '../lib/three.module.js'
- class RotateTool extends TransformationTool {
- static CHANGED_PROPERTIES = ['position', 'rotation']
- constructor(application, options) {
- super(application)
- this.name = 'rotate'
- this.label = 'tool.rotate.label'
- this.className = 'rotate'
- this.setOptions(options)
- // internals
- this.firstPointWorld = new THREE.Vector3()
- this.secondPointWorld = new THREE.Vector3()
- this.anchorPointWorld = new THREE.Vector3()
- this.rotation = 0
- this.baseMatrixWorld = new THREE.Matrix4()
- this.baseMatrixWorldInverse = new THREE.Matrix4()
- this._vector1 = new THREE.Vector3()
- this._vector2 = new THREE.Vector3()
- this.wheelPoints = []
- this.wheel = this.createWheel()
- this.createPanel()
- }
- createPanel() {
- this.panel = this.application.createPanel(this.label, 'left', 'panel_rotate')
- this.panel.preferredHeight = 140
- this.helpElem = document.createElement('div')
- this.panel.bodyElem.appendChild(this.helpElem)
- this.rotationInputElem = Controls.addNumberField(this.panel.bodyElem, 'rotate_angle', 'label.rotation', 0)
- this.rotationInputElem.step = 1
- this.rotationInputElem.addEventListener(
- 'change',
- event => {
- this.rotation = this.rotationInputElem.value
- this.rotateObjects()
- },
- false
- )
- this.buttonsPanel = document.createElement('div')
- this.panel.bodyElem.appendChild(this.buttonsPanel)
- this.acceptButton = Controls.addButton(this.buttonsPanel, 'cancel_section', 'button.accept', event => {
- this.rotation = this.rotationInputElem.value
- this.rotateObjects()
- this.setStage(0)
- })
- this.cancelButton = Controls.addButton(this.buttonsPanel, 'cancel_section', 'button.cancel', event => {
- this.rotation = 0
- this.rotateObjects()
- this.setStage(0)
- })
- }
- onPointerMove(event) {
- if (this.stage === 3) {
- const application = this.application
- const pointSelector = application.pointSelector
- if (!pointSelector.isPointSelectionEvent(event)) return
- event.preventDefault()
- const snap = pointSelector.snap
- if (snap) {
- let anchorPoint = this._vector1
- anchorPoint.copy(this.anchorPointWorld)
- anchorPoint.applyMatrix4(this.baseMatrixWorldInverse)
- anchorPoint.z = 0
- let destinationPoint = this._vector2
- destinationPoint.copy(snap.positionWorld)
- destinationPoint.applyMatrix4(this.baseMatrixWorldInverse)
- destinationPoint.z = 0
- let angleRad1 = Math.atan2(anchorPoint.y, anchorPoint.x)
- let angleRad2 = Math.atan2(destinationPoint.y, destinationPoint.x)
- let angleRad = angleRad2 - angleRad1
- let angleDeg = THREE.MathUtils.radToDeg(angleRad)
- const k = 100000
- angleDeg = Math.round(k * angleDeg) / k
- if (angleDeg > 180) angleDeg -= 360
- else if (angleDeg < -180) angleDeg += 360
- this.rotation = angleDeg
- this.rotateObjects()
- this.rotationInputElem.value = this.rotation
- }
- }
- }
- onPointerUp(event) {
- const application = this.application
- const pointSelector = application.pointSelector
- if (!pointSelector.isPointSelectionEvent(event)) return
- const snap = pointSelector.snap
- if (this.stage === 0) {
- // first axis point
- if (snap) {
- this.firstPointWorld.copy(snap.positionWorld)
- if (snap.object) {
- let axisMatrixWorld = this.axisMatrixWorld
- if (snap.object) {
- axisMatrixWorld.copy(snap.object.matrixWorld)
- } else {
- axisMatrixWorld.identity()
- }
- axisMatrixWorld.setPosition(this.firstPointWorld)
- if (application.selection.isEmpty()) {
- application.selection.set(snap.object)
- }
- }
- this.setStage(1)
- } else {
- this.setStage(0)
- }
- application.overlays.remove(this.wheel)
- } else if (this.stage === 1) {
- // second axis point
- if (snap) {
- if (!snap.positionWorld.equals(this.firstPointWorld)) {
- this.secondPointWorld.copy(snap.positionWorld)
- this.createBaseMatrix()
- this.rotationInputElem.value = 0
- this.setStage(2)
- }
- } else {
- this.setStage(0)
- }
- } else if (this.stage === 2 || this.stage === 4) {
- // anchor point
- this.objectMatrices.clear()
- if (snap) {
- if (!snap.positionWorld.equals(this.firstPointWorld) && !snap.positionWorld.equals(this.secondPointWorld)) {
- this.anchorPointWorld.copy(snap.positionWorld)
- this.rotationInputElem.value = 0
- this.setStage(3)
- }
- } else {
- this.setStage(0)
- }
- } else if (this.stage === 3) {
- // destination point
- this.setStage(4)
- }
- }
- setStage(stage) {
- this.stage = stage
- const application = this.application
- switch (stage) {
- case 0: // set first point of rotation axis
- this.rotation = 0
- application.pointSelector.clearAxisGuides()
- application.pointSelector.excludeSelection = false
- application.pointSelector.auxiliaryPoints = []
- application.pointSelector.activate()
- this.rotationInputElem.parentElement.style.display = 'none'
- this.buttonsPanel.style.display = 'none'
- I18N.set(this.helpElem, 'innerHTML', 'tool.rotate.select_first_point')
- application.i18n.update(this.helpElem)
- application.overlays.remove(this.wheel)
- application.repaint()
- break
- case 1: // set second point of rotation axis
- application.pointSelector.setAxisGuides(this.axisMatrixWorld, true)
- application.pointSelector.excludeSelection = false
- application.pointSelector.auxiliaryPoints = []
- application.pointSelector.activate()
- this.rotationInputElem.parentElement.style.display = 'none'
- this.buttonsPanel.style.display = 'none'
- I18N.set(this.helpElem, 'innerHTML', 'tool.rotate.select_second_point')
- application.i18n.update(this.helpElem)
- break
- case 2: // set anchor point
- application.pointSelector.clearAxisGuides()
- application.pointSelector.excludeSelection = false
- application.pointSelector.auxiliaryPoints = this.wheelPoints
- application.pointSelector.activate()
- this.rotationInputElem.parentElement.style.display = 'none'
- this.buttonsPanel.style.display = 'none'
- I18N.set(this.helpElem, 'innerHTML', 'tool.rotate.select_anchor_point')
- application.i18n.update(this.helpElem)
- break
- case 3: // set destination point
- application.pointSelector.clearAxisGuides()
- application.pointSelector.excludeSelection = true
- application.pointSelector.auxiliaryPoints = this.wheelPoints
- application.pointSelector.activate()
- this.rotationInputElem.parentElement.style.display = ''
- this.rotationInputElem.disabled = true
- this.buttonsPanel.style.display = 'none'
- I18N.set(this.helpElem, 'innerHTML', 'tool.rotate.select_destination_point')
- application.i18n.update(this.helpElem)
- break
- case 4: // edit rotation angle
- application.pointSelector.clearAxisGuides()
- application.pointSelector.excludeSelection = false
- application.pointSelector.auxiliaryPoints = this.wheelPoints
- application.pointSelector.activate()
- this.rotationInputElem.parentElement.style.display = ''
- this.rotationInputElem.disabled = false
- this.buttonsPanel.style.display = ''
- I18N.set(this.helpElem, 'innerHTML', 'tool.rotate.edit_rotation')
- application.i18n.update(this.helpElem)
- break
- }
- }
- createBaseMatrix() {
- const rotationAxisWorld = this._vector1
- rotationAxisWorld.subVectors(this.secondPointWorld, this.firstPointWorld)
- rotationAxisWorld.normalize()
- let yVector = GeometryUtils.orthogonalVector(rotationAxisWorld).normalize()
- let xVector = new THREE.Vector3()
- xVector.crossVectors(yVector, rotationAxisWorld)
- const baseMatrixWorld = this.baseMatrixWorld
- baseMatrixWorld.makeBasis(xVector, yVector, rotationAxisWorld)
- baseMatrixWorld.setPosition(this.firstPointWorld)
- const baseMatrixWorldInverse = this.baseMatrixWorldInverse
- baseMatrixWorldInverse.copy(baseMatrixWorld).invert()
- const wheel = this.wheel
- baseMatrixWorld.decompose(wheel.position, wheel.quaternion, wheel.scale)
- wheel.updateMatrix()
- const application = this.application
- application.overlays.add(wheel)
- application.repaint()
- const divisions = 72
- const angleIncr = (2 * Math.PI) / divisions
- for (let i = 0; i < divisions; i++) {
- let angle = i * angleIncr
- let x = 0.5 * Math.cos(angle)
- let y = 0.5 * Math.sin(angle)
- this.wheelPoints[i].set(x, y, 0).applyMatrix4(baseMatrixWorld)
- }
- }
- rotateObjects() {
- const application = this.application
- const baseMatrixWorld = this.baseMatrixWorld
- const baseMatrixWorldInverse = this.baseMatrixWorldInverse
- const angleRad = THREE.MathUtils.degToRad(this.rotation)
- const rotationMatrixWorld = this._matrix1.makeRotationZ(angleRad)
- rotationMatrixWorld.multiply(baseMatrixWorldInverse)
- rotationMatrixWorld.premultiply(baseMatrixWorld)
- this.transformObjects(rotationMatrixWorld, RotateTool.CHANGED_PROPERTIES)
- }
- resetTool() {
- super.resetTool()
- this.rotation = 0
- const application = this.application
- application.overlays.remove(this.wheel)
- application.repaint()
- }
- createWheel() {
- const geometry = new THREE.BufferGeometry()
- const points = []
- points.push(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1))
- points.push(new THREE.Vector3(-0.1, 0, 0), new THREE.Vector3(0.1, 0, 0))
- points.push(new THREE.Vector3(0, -0.1, 0), new THREE.Vector3(0, 0.1, 0))
- const divisions = 72
- const angleIncr = (2 * Math.PI) / divisions
- const wheelPoints = this.wheelPoints
- for (let i = 0; i < divisions; i++) {
- let angle = i * angleIncr
- let x = 0.5 * Math.cos(angle)
- let y = 0.5 * Math.sin(angle)
- wheelPoints.push(new THREE.Vector3(x, y, 0))
- }
- for (let i = 0; i < divisions; i++) {
- let p1 = wheelPoints[i]
- let p2 = wheelPoints[(i + 1) % divisions]
- let p3 = new THREE.Vector3()
- let angle = i * angleIncr
- let r
- if (i % 18 === 0) r = 0.35
- else if (i % 9 === 0) r = 0.4
- else r = 0.45
- p3.x = r * Math.cos(angle)
- p3.y = r * Math.sin(angle)
- points.push(p1, p2)
- points.push(p1, p3)
- }
- geometry.setFromPoints(points)
- const lines = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 1, transparent: true, opacity: 0.8, depthTest: false }))
- lines.name = 'RotationWheel'
- lines.raycast = function() {}
- lines.renderOrder = 10000
- return lines
- }
- }
- export { RotateTool }
|