Loading files
0f2686274This file is pinned to the selected release. Download the original to open it in your design tool.
import {EventDispatcher, MathUtils, MOUSE, Quaternion, Vector2, Vector3} from "three"
const _changeEvent = {type: "change"}
const _startEvent = {type: "start"}
const _endEvent = {type: "end"}
class TrackballControls extends EventDispatcher {
constructor(object, domElement) {
super()
console.log("HELLO CAD CONTROLS")
const scope = this
const STATE = {NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4}
this.object = object
this.domElement = domElement
this.domElement.style.touchAction = "none" // disable touch scroll
// API
this.enabled = true
this.screen = {left: 0, top: 0, width: 0, height: 0}
this.rotateSpeed = 1.0
this.zoomSpeed = 1.2
this.panSpeed = 0.3
this.noRotate = false
this.noZoom = false
this.noPan = false
this.staticMoving = false
this.dynamicDampingFactor = 0.2
this.minDistance = 0
this.maxDistance = Infinity
this.minZoom = 0
this.maxZoom = Infinity
this.keys = ["KeyA" /*A*/, "KeyS" /*S*/, "KeyD" /*D*/]
this.mouseButtons = {LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN}
// internals
this.target = new Vector3()
const EPS = 0.000001
const lastPosition = new Vector3()
let lastZoom = 1
let _state = STATE.NONE,
_keyState = STATE.NONE,
_touchZoomDistanceStart = 0,
_touchZoomDistanceEnd = 0,
_lastAngle = 0
const _eye = new Vector3(),
_movePrev = new Vector2(),
_moveCurr = new Vector2(),
_lastAxis = new Vector3(),
_zoomStart = new Vector2(),
_zoomEnd = new Vector2(),
_panStart = new Vector2(),
_panEnd = new Vector2(),
_pointers = [],
_pointerPositions = {}
// for reset
this.target0 = this.target.clone()
this.position0 = this.object.position.clone()
this.up0 = this.object.up.clone()
this.zoom0 = this.object.zoom
// methods
this.handleResize = function () {
const box = scope.domElement.getBoundingClientRect()
// adjustments come from similar code in the jquery offset() function
const d = scope.domElement.ownerDocument.documentElement
scope.screen.left = box.left + window.pageXOffset - d.clientLeft
scope.screen.top = box.top + window.pageYOffset - d.clientTop
scope.screen.width = box.width
scope.screen.height = box.height
}
const getMouseOnScreen = (function () {
const vector = new Vector2()
return function getMouseOnScreen(pageX, pageY) {
vector.set((pageX - scope.screen.left) / scope.screen.width, (pageY - scope.screen.top) / scope.screen.height)
return vector
}
})()
const getMouseOnCircle = (function () {
const vector = new Vector2()
return function getMouseOnCircle(pageX, pageY) {
vector.set(
(pageX - scope.screen.width * 0.5 - scope.screen.left) / (scope.screen.width * 0.5),
(scope.screen.height + 2 * (scope.screen.top - pageY)) / scope.screen.width, // screen.width intentional
)
return vector
}
})()
this.rotateCamera = (function () {
const axis = new Vector3(),
quaternion = new Quaternion(),
eyeDirection = new Vector3(),
objectUpDirection = new Vector3(),
objectSidewaysDirection = new Vector3(),
moveDirection = new Vector3()
return function rotateCamera() {
moveDirection.set(_moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0)
let angle = moveDirection.length()
if (angle) {
_eye.copy(scope.object.position).sub(scope.target)
eyeDirection.copy(_eye).normalize()
objectUpDirection.copy(scope.object.up).normalize()
objectSidewaysDirection.crossVectors(objectUpDirection, eyeDirection).normalize()
objectUpDirection.setLength(_moveCurr.y - _movePrev.y)
objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x)
moveDirection.copy(objectUpDirection.add(objectSidewaysDirection))
axis.crossVectors(moveDirection, _eye).normalize()
angle *= scope.rotateSpeed
quaternion.setFromAxisAngle(axis, angle)
_eye.applyQuaternion(quaternion)
scope.object.up.applyQuaternion(quaternion)
_lastAxis.copy(axis)
_lastAngle = angle
} else if (!scope.staticMoving && _lastAngle) {
_lastAngle *= Math.sqrt(1.0 - scope.dynamicDampingFactor)
_eye.copy(scope.object.position).sub(scope.target)
quaternion.setFromAxisAngle(_lastAxis, _lastAngle)
_eye.applyQuaternion(quaternion)
scope.object.up.applyQuaternion(quaternion)
}
_movePrev.copy(_moveCurr)
}
})()
this.zoomCamera = function () {
let factor
if (_state === STATE.TOUCH_ZOOM_PAN) {
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd
_touchZoomDistanceStart = _touchZoomDistanceEnd
if (scope.object.isPerspectiveCamera) {
_eye.multiplyScalar(factor)
} else if (scope.object.isOrthographicCamera) {
scope.object.zoom = MathUtils.clamp(scope.object.zoom / factor, scope.minZoom, scope.maxZoom)
if (lastZoom !== scope.object.zoom) {
scope.object.updateProjectionMatrix()
}
} else {
console.warn("THREE.TrackballControls: Unsupported camera type")
}
} else {
factor = 1.0 + (_zoomEnd.y - _zoomStart.y) * scope.zoomSpeed
if (factor !== 1.0 && factor > 0.0) {
if (scope.object.isPerspectiveCamera) {
_eye.multiplyScalar(factor)
} else if (scope.object.isOrthographicCamera) {
scope.object.zoom = MathUtils.clamp(scope.object.zoom / factor, scope.minZoom, scope.maxZoom)
if (lastZoom !== scope.object.zoom) {
scope.object.updateProjectionMatrix()
}
} else {
console.warn("THREE.TrackballControls: Unsupported camera type")
}
}
if (scope.staticMoving) {
_zoomStart.copy(_zoomEnd)
} else {
_zoomStart.y += (_zoomEnd.y - _zoomStart.y) * this.dynamicDampingFactor
}
}
}
this.panCamera = (function () {
const mouseChange = new Vector2(),
objectUp = new Vector3(),
pan = new Vector3()
return function panCamera() {
mouseChange.copy(_panEnd).sub(_panStart)
if (mouseChange.lengthSq()) {
if (scope.object.isOrthographicCamera) {
const scale_x = (scope.object.right - scope.object.left) / scope.object.zoom / scope.domElement.clientWidth
const scale_y = (scope.object.top - scope.object.bottom) / scope.object.zoom / scope.domElement.clientWidth
mouseChange.x *= scale_x
mouseChange.y *= scale_y
}
mouseChange.multiplyScalar(_eye.length() * scope.panSpeed)
pan.copy(_eye).cross(scope.object.up).setLength(mouseChange.x)
pan.add(objectUp.copy(scope.object.up).setLength(mouseChange.y))
scope.object.position.add(pan)
scope.target.add(pan)
if (scope.staticMoving) {
_panStart.copy(_panEnd)
} else {
_panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(scope.dynamicDampingFactor))
}
}
}
})()
this.checkDistances = function () {
if (!scope.noZoom || !scope.noPan) {
if (_eye.lengthSq() > scope.maxDistance * scope.maxDistance) {
scope.object.position.addVectors(scope.target, _eye.setLength(scope.maxDistance))
_zoomStart.copy(_zoomEnd)
}
if (_eye.lengthSq() < scope.minDistance * scope.minDistance) {
scope.object.position.addVectors(scope.target, _eye.setLength(scope.minDistance))
_zoomStart.copy(_zoomEnd)
}
}
}
this.update = function () {
_eye.subVectors(scope.object.position, scope.target)
if (!scope.noRotate) {
scope.rotateCamera()
}
if (!scope.noZoom) {
scope.zoomCamera()
}
if (!scope.noPan) {
scope.panCamera()
}
scope.object.position.addVectors(scope.target, _eye)
if (scope.object.isPerspectiveCamera) {
scope.checkDistances()
scope.object.lookAt(scope.target)
if (lastPosition.distanceToSquared(scope.object.position) > EPS) {
scope.dispatchEvent(_changeEvent)
lastPosition.copy(scope.object.position)
}
} else if (scope.object.isOrthographicCamera) {
scope.object.lookAt(scope.target)
if (lastPosition.distanceToSquared(scope.object.position) > EPS || lastZoom !== scope.object.zoom) {
scope.dispatchEvent(_changeEvent)
lastPosition.copy(scope.object.position)
lastZoom = scope.object.zoom
}
} else {
console.warn("THREE.TrackballControls: Unsupported camera type")
}
}
this.reset = function () {
_state = STATE.NONE
_keyState = STATE.NONE
scope.target.copy(scope.target0)
scope.object.position.copy(scope.position0)
scope.object.up.copy(scope.up0)
scope.object.zoom = scope.zoom0
scope.object.updateProjectionMatrix()
_eye.subVectors(scope.object.position, scope.target)
scope.object.lookAt(scope.target)
scope.dispatchEvent(_changeEvent)
lastPosition.copy(scope.object.position)
lastZoom = scope.object.zoom
}
// listeners
function onPointerDown(event) {
if (scope.enabled === false) return
if (_pointers.length === 0) {
scope.domElement.setPointerCapture(event.pointerId)
scope.domElement.addEventListener("pointermove", onPointerMove)
scope.domElement.addEventListener("pointerup", onPointerUp)
}
//
addPointer(event)
if (event.pointerType === "touch") {
onTouchStart(event)
} else {
onMouseDown(event)
}
}
function onPointerMove(event) {
if (scope.enabled === false) return
if (event.pointerType === "touch") {
onTouchMove(event)
} else {
onMouseMove(event)
}
}
function onPointerUp(event) {
if (scope.enabled === false) return
if (event.pointerType === "touch") {
onTouchEnd(event)
} else {
onMouseUp()
}
//
removePointer(event)
if (_pointers.length === 0) {
scope.domElement.releasePointerCapture(event.pointerId)
scope.domElement.removeEventListener("pointermove", onPointerMove)
scope.domElement.removeEventListener("pointerup", onPointerUp)
}
}
function onPointerCancel(event) {
removePointer(event)
}
function keydown(event) {
if (scope.enabled === false) return
window.removeEventListener("keydown", keydown)
if (_keyState !== STATE.NONE) {
return
} else if (event.code === scope.keys[STATE.ROTATE] && !scope.noRotate) {
_keyState = STATE.ROTATE
} else if (event.code === scope.keys[STATE.ZOOM] && !scope.noZoom) {
_keyState = STATE.ZOOM
} else if (event.code === scope.keys[STATE.PAN] && !scope.noPan) {
_keyState = STATE.PAN
}
}
function keyup() {
if (scope.enabled === false) return
_keyState = STATE.NONE
window.addEventListener("keydown", keydown)
}
function onMouseDown(event) {
if (_state === STATE.NONE) {
switch (event.button) {
case scope.mouseButtons.LEFT:
_state = STATE.ROTATE
break
case scope.mouseButtons.MIDDLE:
_state = STATE.ZOOM
break
case scope.mouseButtons.RIGHT:
_state = STATE.PAN
break
}
}
const state = _keyState !== STATE.NONE ? _keyState : _state
if (state === STATE.ROTATE && !scope.noRotate) {
_moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY))
_movePrev.copy(_moveCurr)
} else if (state === STATE.ZOOM && !scope.noZoom) {
_zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY))
_zoomEnd.copy(_zoomStart)
} else if (state === STATE.PAN && !scope.noPan) {
_panStart.copy(getMouseOnScreen(event.pageX, event.pageY))
_panEnd.copy(_panStart)
}
scope.dispatchEvent(_startEvent)
}
function onMouseMove(event) {
const state = _keyState !== STATE.NONE ? _keyState : _state
if (state === STATE.ROTATE && !scope.noRotate) {
_movePrev.copy(_moveCurr)
_moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY))
} else if (state === STATE.ZOOM && !scope.noZoom) {
_zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY))
} else if (state === STATE.PAN && !scope.noPan) {
_panEnd.copy(getMouseOnScreen(event.pageX, event.pageY))
}
}
function onMouseUp() {
_state = STATE.NONE
scope.dispatchEvent(_endEvent)
}
function onMouseWheel(event) {
if (scope.enabled === false) return
if (scope.noZoom === true) return
event.preventDefault()
switch (event.deltaMode) {
case 2:
// Zoom in pages
_zoomStart.y -= event.deltaY * 0.025
break
case 1:
// Zoom in lines
_zoomStart.y -= event.deltaY * 0.01
break
default:
// undefined, 0, assume pixels
_zoomStart.y -= event.deltaY * 0.00025
break
}
scope.dispatchEvent(_startEvent)
scope.dispatchEvent(_endEvent)
}
function onTouchStart(event) {
trackPointer(event)
switch (_pointers.length) {
case 1:
_state = STATE.TOUCH_ROTATE
_moveCurr.copy(getMouseOnCircle(_pointers[0].pageX, _pointers[0].pageY))
_movePrev.copy(_moveCurr)
break
default: // 2 or more
_state = STATE.TOUCH_ZOOM_PAN
const dx = _pointers[0].pageX - _pointers[1].pageX
const dy = _pointers[0].pageY - _pointers[1].pageY
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy)
const x = (_pointers[0].pageX + _pointers[1].pageX) / 2
const y = (_pointers[0].pageY + _pointers[1].pageY) / 2
_panStart.copy(getMouseOnScreen(x, y))
_panEnd.copy(_panStart)
break
}
scope.dispatchEvent(_startEvent)
}
function onTouchMove(event) {
trackPointer(event)
switch (_pointers.length) {
case 1:
_movePrev.copy(_moveCurr)
_moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY))
break
default: // 2 or more
const position = getSecondPointerPosition(event)
const dx = event.pageX - position.x
const dy = event.pageY - position.y
_touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy)
const x = (event.pageX + position.x) / 2
const y = (event.pageY + position.y) / 2
_panEnd.copy(getMouseOnScreen(x, y))
break
}
}
function onTouchEnd(event) {
switch (_pointers.length) {
case 0:
_state = STATE.NONE
break
case 1:
_state = STATE.TOUCH_ROTATE
_moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY))
_movePrev.copy(_moveCurr)
break
case 2:
_state = STATE.TOUCH_ZOOM_PAN
for (let i = 0; i < _pointers.length; i++) {
if (_pointers[i].pointerId !== event.pointerId) {
const position = _pointerPositions[_pointers[i].pointerId]
_moveCurr.copy(getMouseOnCircle(position.x, position.y))
_movePrev.copy(_moveCurr)
break
}
}
break
}
scope.dispatchEvent(_endEvent)
}
function contextmenu(event) {
if (scope.enabled === false) return
event.preventDefault()
}
function addPointer(event) {
_pointers.push(event)
}
function removePointer(event) {
delete _pointerPositions[event.pointerId]
for (let i = 0; i < _pointers.length; i++) {
if (_pointers[i].pointerId == event.pointerId) {
_pointers.splice(i, 1)
return
}
}
}
function trackPointer(event) {
let position = _pointerPositions[event.pointerId]
if (position === undefined) {
position = new Vector2()
_pointerPositions[event.pointerId] = position
}
position.set(event.pageX, event.pageY)
}
function getSecondPointerPosition(event) {
const pointer = event.pointerId === _pointers[0].pointerId ? _pointers[1] : _pointers[0]
return _pointerPositions[pointer.pointerId]
}
this.dispose = function () {
scope.domElement.removeEventListener("contextmenu", contextmenu)
scope.domElement.removeEventListener("pointerdown", onPointerDown)
scope.domElement.removeEventListener("pointercancel", onPointerCancel)
scope.domElement.removeEventListener("wheel", onMouseWheel)
scope.domElement.removeEventListener("pointermove", onPointerMove)
scope.domElement.removeEventListener("pointerup", onPointerUp)
window.removeEventListener("keydown", keydown)
window.removeEventListener("keyup", keyup)
}
this.domElement.addEventListener("contextmenu", contextmenu)
this.domElement.addEventListener("pointerdown", onPointerDown)
this.domElement.addEventListener("pointercancel", onPointerCancel)
this.domElement.addEventListener("wheel", onMouseWheel, {passive: false})
window.addEventListener("keydown", keydown)
window.addEventListener("keyup", keyup)
this.handleResize()
// force an update at start
this.update()
}
}
export {TrackballControls}