瀏覽代碼

[ts][threejs] Added orbital controls to threejs example.

Mario Zechner 3 年之前
父節點
當前提交
0486bbda74

+ 1248 - 0
spine-ts/spine-threejs/example/assets/OrbitControls.js

@@ -0,0 +1,1248 @@
+let EventDispatcher = THREE.EventDispatcher;
+let MOUSE = THREE.MOUSE;
+let Quaternion = THREE.Quaternion;
+let Spherical = THREE.Spherical;
+let TOUCH = THREE.TOUCH;
+let Vector2 = THREE.Vector2;
+let Vector3 = THREE.Vector3;
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+//
+//    Orbit - left mouse / touch: one-finger move
+//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+//    Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+
+const _changeEvent = { type: 'change' };
+const _startEvent = { type: 'start' };
+const _endEvent = { type: 'end' };
+
+class OrbitControls extends EventDispatcher {
+
+	constructor(object, domElement) {
+
+		super();
+
+		if (domElement === undefined) console.warn('THREE.OrbitControls: The second parameter "domElement" is now mandatory.');
+		if (domElement === document) console.error('THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.');
+
+		this.object = object;
+		this.domElement = domElement;
+		this.domElement.style.touchAction = 'none'; // disable touch scroll
+
+		// Set to false to disable this control
+		this.enabled = true;
+
+		// "target" sets the location of focus, where the object orbits around
+		this.target = new Vector3();
+
+		// How far you can dolly in and out ( PerspectiveCamera only )
+		this.minDistance = 0;
+		this.maxDistance = Infinity;
+
+		// How far you can zoom in and out ( OrthographicCamera only )
+		this.minZoom = 0;
+		this.maxZoom = Infinity;
+
+		// How far you can orbit vertically, upper and lower limits.
+		// Range is 0 to Math.PI radians.
+		this.minPolarAngle = 0; // radians
+		this.maxPolarAngle = Math.PI; // radians
+
+		// How far you can orbit horizontally, upper and lower limits.
+		// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
+		this.minAzimuthAngle = - Infinity; // radians
+		this.maxAzimuthAngle = Infinity; // radians
+
+		// Set to true to enable damping (inertia)
+		// If damping is enabled, you must call controls.update() in your animation loop
+		this.enableDamping = false;
+		this.dampingFactor = 0.05;
+
+		// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+		// Set to false to disable zooming
+		this.enableZoom = true;
+		this.zoomSpeed = 1.0;
+
+		// Set to false to disable rotating
+		this.enableRotate = true;
+		this.rotateSpeed = 1.0;
+
+		// Set to false to disable panning
+		this.enablePan = true;
+		this.panSpeed = 1.0;
+		this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
+		this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
+
+		// Set to true to automatically rotate around the target
+		// If auto-rotate is enabled, you must call controls.update() in your animation loop
+		this.autoRotate = false;
+		this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
+
+		// The four arrow keys
+		this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
+
+		// Mouse buttons
+		this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
+
+		// Touch fingers
+		this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
+
+		// for reset
+		this.target0 = this.target.clone();
+		this.position0 = this.object.position.clone();
+		this.zoom0 = this.object.zoom;
+
+		// the target DOM element for key events
+		this._domElementKeyEvents = null;
+
+		//
+		// public methods
+		//
+
+		this.getPolarAngle = function () {
+
+			return spherical.phi;
+
+		};
+
+		this.getAzimuthalAngle = function () {
+
+			return spherical.theta;
+
+		};
+
+		this.getDistance = function () {
+
+			return this.object.position.distanceTo(this.target);
+
+		};
+
+		this.listenToKeyEvents = function (domElement) {
+
+			domElement.addEventListener('keydown', onKeyDown);
+			this._domElementKeyEvents = domElement;
+
+		};
+
+		this.saveState = function () {
+
+			scope.target0.copy(scope.target);
+			scope.position0.copy(scope.object.position);
+			scope.zoom0 = scope.object.zoom;
+
+		};
+
+		this.reset = function () {
+
+			scope.target.copy(scope.target0);
+			scope.object.position.copy(scope.position0);
+			scope.object.zoom = scope.zoom0;
+
+			scope.object.updateProjectionMatrix();
+			scope.dispatchEvent(_changeEvent);
+
+			scope.update();
+
+			state = STATE.NONE;
+
+		};
+
+		// this method is exposed, but perhaps it would be better if we can make it private...
+		this.update = function () {
+
+			const offset = new Vector3();
+
+			// so camera.up is the orbit axis
+			const quat = new Quaternion().setFromUnitVectors(object.up, new Vector3(0, 1, 0));
+			const quatInverse = quat.clone().invert();
+
+			const lastPosition = new Vector3();
+			const lastQuaternion = new Quaternion();
+
+			const twoPI = 2 * Math.PI;
+
+			return function update() {
+
+				const position = scope.object.position;
+
+				offset.copy(position).sub(scope.target);
+
+				// rotate offset to "y-axis-is-up" space
+				offset.applyQuaternion(quat);
+
+				// angle from z-axis around y-axis
+				spherical.setFromVector3(offset);
+
+				if (scope.autoRotate && state === STATE.NONE) {
+
+					rotateLeft(getAutoRotationAngle());
+
+				}
+
+				if (scope.enableDamping) {
+
+					spherical.theta += sphericalDelta.theta * scope.dampingFactor;
+					spherical.phi += sphericalDelta.phi * scope.dampingFactor;
+
+				} else {
+
+					spherical.theta += sphericalDelta.theta;
+					spherical.phi += sphericalDelta.phi;
+
+				}
+
+				// restrict theta to be between desired limits
+
+				let min = scope.minAzimuthAngle;
+				let max = scope.maxAzimuthAngle;
+
+				if (isFinite(min) && isFinite(max)) {
+
+					if (min < - Math.PI) min += twoPI; else if (min > Math.PI) min -= twoPI;
+
+					if (max < - Math.PI) max += twoPI; else if (max > Math.PI) max -= twoPI;
+
+					if (min <= max) {
+
+						spherical.theta = Math.max(min, Math.min(max, spherical.theta));
+
+					} else {
+
+						spherical.theta = (spherical.theta > (min + max) / 2) ?
+							Math.max(min, spherical.theta) :
+							Math.min(max, spherical.theta);
+
+					}
+
+				}
+
+				// restrict phi to be between desired limits
+				spherical.phi = Math.max(scope.minPolarAngle, Math.min(scope.maxPolarAngle, spherical.phi));
+
+				spherical.makeSafe();
+
+
+				spherical.radius *= scale;
+
+				// restrict radius to be between desired limits
+				spherical.radius = Math.max(scope.minDistance, Math.min(scope.maxDistance, spherical.radius));
+
+				// move target to panned location
+
+				if (scope.enableDamping === true) {
+
+					scope.target.addScaledVector(panOffset, scope.dampingFactor);
+
+				} else {
+
+					scope.target.add(panOffset);
+
+				}
+
+				offset.setFromSpherical(spherical);
+
+				// rotate offset back to "camera-up-vector-is-up" space
+				offset.applyQuaternion(quatInverse);
+
+				position.copy(scope.target).add(offset);
+
+				scope.object.lookAt(scope.target);
+
+				if (scope.enableDamping === true) {
+
+					sphericalDelta.theta *= (1 - scope.dampingFactor);
+					sphericalDelta.phi *= (1 - scope.dampingFactor);
+
+					panOffset.multiplyScalar(1 - scope.dampingFactor);
+
+				} else {
+
+					sphericalDelta.set(0, 0, 0);
+
+					panOffset.set(0, 0, 0);
+
+				}
+
+				scale = 1;
+
+				// update condition is:
+				// min(camera displacement, camera rotation in radians)^2 > EPS
+				// using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+				if (zoomChanged ||
+					lastPosition.distanceToSquared(scope.object.position) > EPS ||
+					8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) {
+
+					scope.dispatchEvent(_changeEvent);
+
+					lastPosition.copy(scope.object.position);
+					lastQuaternion.copy(scope.object.quaternion);
+					zoomChanged = false;
+
+					return true;
+
+				}
+
+				return false;
+
+			};
+
+		}();
+
+		this.dispose = function () {
+
+			scope.domElement.removeEventListener('contextmenu', onContextMenu);
+
+			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);
+
+
+			if (scope._domElementKeyEvents !== null) {
+
+				scope._domElementKeyEvents.removeEventListener('keydown', onKeyDown);
+
+			}
+
+			//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+		};
+
+		//
+		// internals
+		//
+
+		const scope = this;
+
+		const STATE = {
+			NONE: - 1,
+			ROTATE: 0,
+			DOLLY: 1,
+			PAN: 2,
+			TOUCH_ROTATE: 3,
+			TOUCH_PAN: 4,
+			TOUCH_DOLLY_PAN: 5,
+			TOUCH_DOLLY_ROTATE: 6
+		};
+
+		let state = STATE.NONE;
+
+		const EPS = 0.000001;
+
+		// current position in spherical coordinates
+		const spherical = new Spherical();
+		const sphericalDelta = new Spherical();
+
+		let scale = 1;
+		const panOffset = new Vector3();
+		let zoomChanged = false;
+
+		const rotateStart = new Vector2();
+		const rotateEnd = new Vector2();
+		const rotateDelta = new Vector2();
+
+		const panStart = new Vector2();
+		const panEnd = new Vector2();
+		const panDelta = new Vector2();
+
+		const dollyStart = new Vector2();
+		const dollyEnd = new Vector2();
+		const dollyDelta = new Vector2();
+
+		const pointers = [];
+		const pointerPositions = {};
+
+		function getAutoRotationAngle() {
+
+			return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+		}
+
+		function getZoomScale() {
+
+			return Math.pow(0.95, scope.zoomSpeed);
+
+		}
+
+		function rotateLeft(angle) {
+
+			sphericalDelta.theta -= angle;
+
+		}
+
+		function rotateUp(angle) {
+
+			sphericalDelta.phi -= angle;
+
+		}
+
+		const panLeft = function () {
+
+			const v = new Vector3();
+
+			return function panLeft(distance, objectMatrix) {
+
+				v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
+				v.multiplyScalar(- distance);
+
+				panOffset.add(v);
+
+			};
+
+		}();
+
+		const panUp = function () {
+
+			const v = new Vector3();
+
+			return function panUp(distance, objectMatrix) {
+
+				if (scope.screenSpacePanning === true) {
+
+					v.setFromMatrixColumn(objectMatrix, 1);
+
+				} else {
+
+					v.setFromMatrixColumn(objectMatrix, 0);
+					v.crossVectors(scope.object.up, v);
+
+				}
+
+				v.multiplyScalar(distance);
+
+				panOffset.add(v);
+
+			};
+
+		}();
+
+		// deltaX and deltaY are in pixels; right and down are positive
+		const pan = function () {
+
+			const offset = new Vector3();
+
+			return function pan(deltaX, deltaY) {
+
+				const element = scope.domElement;
+
+				if (scope.object.isPerspectiveCamera) {
+
+					// perspective
+					const position = scope.object.position;
+					offset.copy(position).sub(scope.target);
+					let targetDistance = offset.length();
+
+					// half of the fov is center to top of screen
+					targetDistance *= Math.tan((scope.object.fov / 2) * Math.PI / 180.0);
+
+					// we use only clientHeight here so aspect ratio does not distort speed
+					panLeft(2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix);
+					panUp(2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix);
+
+				} else if (scope.object.isOrthographicCamera) {
+
+					// orthographic
+					panLeft(deltaX * (scope.object.right - scope.object.left) / scope.object.zoom / element.clientWidth, scope.object.matrix);
+					panUp(deltaY * (scope.object.top - scope.object.bottom) / scope.object.zoom / element.clientHeight, scope.object.matrix);
+
+				} else {
+
+					// camera neither orthographic nor perspective
+					console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.');
+					scope.enablePan = false;
+
+				}
+
+			};
+
+		}();
+
+		function dollyOut(dollyScale) {
+
+			if (scope.object.isPerspectiveCamera) {
+
+				scale /= dollyScale;
+
+			} else if (scope.object.isOrthographicCamera) {
+
+				scope.object.zoom = Math.max(scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom * dollyScale));
+				scope.object.updateProjectionMatrix();
+				zoomChanged = true;
+
+			} else {
+
+				console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.');
+				scope.enableZoom = false;
+
+			}
+
+		}
+
+		function dollyIn(dollyScale) {
+
+			if (scope.object.isPerspectiveCamera) {
+
+				scale *= dollyScale;
+
+			} else if (scope.object.isOrthographicCamera) {
+
+				scope.object.zoom = Math.max(scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom / dollyScale));
+				scope.object.updateProjectionMatrix();
+				zoomChanged = true;
+
+			} else {
+
+				console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.');
+				scope.enableZoom = false;
+
+			}
+
+		}
+
+		//
+		// event callbacks - update the object state
+		//
+
+		function handleMouseDownRotate(event) {
+
+			rotateStart.set(event.clientX, event.clientY);
+
+		}
+
+		function handleMouseDownDolly(event) {
+
+			dollyStart.set(event.clientX, event.clientY);
+
+		}
+
+		function handleMouseDownPan(event) {
+
+			panStart.set(event.clientX, event.clientY);
+
+		}
+
+		function handleMouseMoveRotate(event) {
+
+			rotateEnd.set(event.clientX, event.clientY);
+
+			rotateDelta.subVectors(rotateEnd, rotateStart).multiplyScalar(scope.rotateSpeed);
+
+			const element = scope.domElement;
+
+			rotateLeft(2 * Math.PI * rotateDelta.x / element.clientHeight); // yes, height
+
+			rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight);
+
+			rotateStart.copy(rotateEnd);
+
+			scope.update();
+
+		}
+
+		function handleMouseMoveDolly(event) {
+
+			dollyEnd.set(event.clientX, event.clientY);
+
+			dollyDelta.subVectors(dollyEnd, dollyStart);
+
+			if (dollyDelta.y > 0) {
+
+				dollyOut(getZoomScale());
+
+			} else if (dollyDelta.y < 0) {
+
+				dollyIn(getZoomScale());
+
+			}
+
+			dollyStart.copy(dollyEnd);
+
+			scope.update();
+
+		}
+
+		function handleMouseMovePan(event) {
+
+			panEnd.set(event.clientX, event.clientY);
+
+			panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed);
+
+			pan(panDelta.x, panDelta.y);
+
+			panStart.copy(panEnd);
+
+			scope.update();
+
+		}
+
+		function handleMouseWheel(event) {
+
+			if (event.deltaY < 0) {
+
+				dollyIn(getZoomScale());
+
+			} else if (event.deltaY > 0) {
+
+				dollyOut(getZoomScale());
+
+			}
+
+			scope.update();
+
+		}
+
+		function handleKeyDown(event) {
+
+			let needsUpdate = false;
+
+			switch (event.code) {
+
+				case scope.keys.UP:
+					pan(0, scope.keyPanSpeed);
+					needsUpdate = true;
+					break;
+
+				case scope.keys.BOTTOM:
+					pan(0, - scope.keyPanSpeed);
+					needsUpdate = true;
+					break;
+
+				case scope.keys.LEFT:
+					pan(scope.keyPanSpeed, 0);
+					needsUpdate = true;
+					break;
+
+				case scope.keys.RIGHT:
+					pan(- scope.keyPanSpeed, 0);
+					needsUpdate = true;
+					break;
+
+			}
+
+			if (needsUpdate) {
+
+				// prevent the browser from scrolling on cursor keys
+				event.preventDefault();
+
+				scope.update();
+
+			}
+
+
+		}
+
+		function handleTouchStartRotate() {
+
+			if (pointers.length === 1) {
+
+				rotateStart.set(pointers[0].pageX, pointers[0].pageY);
+
+			} else {
+
+				const x = 0.5 * (pointers[0].pageX + pointers[1].pageX);
+				const y = 0.5 * (pointers[0].pageY + pointers[1].pageY);
+
+				rotateStart.set(x, y);
+
+			}
+
+		}
+
+		function handleTouchStartPan() {
+
+			if (pointers.length === 1) {
+
+				panStart.set(pointers[0].pageX, pointers[0].pageY);
+
+			} else {
+
+				const x = 0.5 * (pointers[0].pageX + pointers[1].pageX);
+				const y = 0.5 * (pointers[0].pageY + pointers[1].pageY);
+
+				panStart.set(x, y);
+
+			}
+
+		}
+
+		function handleTouchStartDolly() {
+
+			const dx = pointers[0].pageX - pointers[1].pageX;
+			const dy = pointers[0].pageY - pointers[1].pageY;
+
+			const distance = Math.sqrt(dx * dx + dy * dy);
+
+			dollyStart.set(0, distance);
+
+		}
+
+		function handleTouchStartDollyPan() {
+
+			if (scope.enableZoom) handleTouchStartDolly();
+
+			if (scope.enablePan) handleTouchStartPan();
+
+		}
+
+		function handleTouchStartDollyRotate() {
+
+			if (scope.enableZoom) handleTouchStartDolly();
+
+			if (scope.enableRotate) handleTouchStartRotate();
+
+		}
+
+		function handleTouchMoveRotate(event) {
+
+			if (pointers.length == 1) {
+
+				rotateEnd.set(event.pageX, event.pageY);
+
+			} else {
+
+				const position = getSecondPointerPosition(event);
+
+				const x = 0.5 * (event.pageX + position.x);
+				const y = 0.5 * (event.pageY + position.y);
+
+				rotateEnd.set(x, y);
+
+			}
+
+			rotateDelta.subVectors(rotateEnd, rotateStart).multiplyScalar(scope.rotateSpeed);
+
+			const element = scope.domElement;
+
+			rotateLeft(2 * Math.PI * rotateDelta.x / element.clientHeight); // yes, height
+
+			rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight);
+
+			rotateStart.copy(rotateEnd);
+
+		}
+
+		function handleTouchMovePan(event) {
+
+			if (pointers.length === 1) {
+
+				panEnd.set(event.pageX, event.pageY);
+
+			} else {
+
+				const position = getSecondPointerPosition(event);
+
+				const x = 0.5 * (event.pageX + position.x);
+				const y = 0.5 * (event.pageY + position.y);
+
+				panEnd.set(x, y);
+
+			}
+
+			panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed);
+
+			pan(panDelta.x, panDelta.y);
+
+			panStart.copy(panEnd);
+
+		}
+
+		function handleTouchMoveDolly(event) {
+
+			const position = getSecondPointerPosition(event);
+
+			const dx = event.pageX - position.x;
+			const dy = event.pageY - position.y;
+
+			const distance = Math.sqrt(dx * dx + dy * dy);
+
+			dollyEnd.set(0, distance);
+
+			dollyDelta.set(0, Math.pow(dollyEnd.y / dollyStart.y, scope.zoomSpeed));
+
+			dollyOut(dollyDelta.y);
+
+			dollyStart.copy(dollyEnd);
+
+		}
+
+		function handleTouchMoveDollyPan(event) {
+
+			if (scope.enableZoom) handleTouchMoveDolly(event);
+
+			if (scope.enablePan) handleTouchMovePan(event);
+
+		}
+
+		function handleTouchMoveDollyRotate(event) {
+
+			if (scope.enableZoom) handleTouchMoveDolly(event);
+
+			if (scope.enableRotate) handleTouchMoveRotate(event);
+
+		}
+
+		//
+		// event handlers - FSM: listen for events and reset state
+		//
+
+		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) {
+
+			removePointer(event);
+
+			if (pointers.length === 0) {
+
+				scope.domElement.releasePointerCapture(event.pointerId);
+
+				scope.domElement.removeEventListener('pointermove', onPointerMove);
+				scope.domElement.removeEventListener('pointerup', onPointerUp);
+
+			}
+
+			scope.dispatchEvent(_endEvent);
+
+			state = STATE.NONE;
+
+		}
+
+		function onPointerCancel(event) {
+
+			removePointer(event);
+
+		}
+
+		function onMouseDown(event) {
+
+			let mouseAction;
+
+			switch (event.button) {
+
+				case 0:
+
+					mouseAction = scope.mouseButtons.LEFT;
+					break;
+
+				case 1:
+
+					mouseAction = scope.mouseButtons.MIDDLE;
+					break;
+
+				case 2:
+
+					mouseAction = scope.mouseButtons.RIGHT;
+					break;
+
+				default:
+
+					mouseAction = - 1;
+
+			}
+
+			switch (mouseAction) {
+
+				case MOUSE.DOLLY:
+
+					if (scope.enableZoom === false) return;
+
+					handleMouseDownDolly(event);
+
+					state = STATE.DOLLY;
+
+					break;
+
+				case MOUSE.ROTATE:
+
+					if (event.ctrlKey || event.metaKey || event.shiftKey) {
+
+						if (scope.enablePan === false) return;
+
+						handleMouseDownPan(event);
+
+						state = STATE.PAN;
+
+					} else {
+
+						if (scope.enableRotate === false) return;
+
+						handleMouseDownRotate(event);
+
+						state = STATE.ROTATE;
+
+					}
+
+					break;
+
+				case MOUSE.PAN:
+
+					if (event.ctrlKey || event.metaKey || event.shiftKey) {
+
+						if (scope.enableRotate === false) return;
+
+						handleMouseDownRotate(event);
+
+						state = STATE.ROTATE;
+
+					} else {
+
+						if (scope.enablePan === false) return;
+
+						handleMouseDownPan(event);
+
+						state = STATE.PAN;
+
+					}
+
+					break;
+
+				default:
+
+					state = STATE.NONE;
+
+			}
+
+			if (state !== STATE.NONE) {
+
+				scope.dispatchEvent(_startEvent);
+
+			}
+
+		}
+
+		function onMouseMove(event) {
+
+			if (scope.enabled === false) return;
+
+			switch (state) {
+
+				case STATE.ROTATE:
+
+					if (scope.enableRotate === false) return;
+
+					handleMouseMoveRotate(event);
+
+					break;
+
+				case STATE.DOLLY:
+
+					if (scope.enableZoom === false) return;
+
+					handleMouseMoveDolly(event);
+
+					break;
+
+				case STATE.PAN:
+
+					if (scope.enablePan === false) return;
+
+					handleMouseMovePan(event);
+
+					break;
+
+			}
+
+		}
+
+		function onMouseWheel(event) {
+
+			if (scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE) return;
+
+			event.preventDefault();
+
+			scope.dispatchEvent(_startEvent);
+
+			handleMouseWheel(event);
+
+			scope.dispatchEvent(_endEvent);
+
+		}
+
+		function onKeyDown(event) {
+
+			if (scope.enabled === false || scope.enablePan === false) return;
+
+			handleKeyDown(event);
+
+		}
+
+		function onTouchStart(event) {
+
+			trackPointer(event);
+
+			switch (pointers.length) {
+
+				case 1:
+
+					switch (scope.touches.ONE) {
+
+						case TOUCH.ROTATE:
+
+							if (scope.enableRotate === false) return;
+
+							handleTouchStartRotate();
+
+							state = STATE.TOUCH_ROTATE;
+
+							break;
+
+						case TOUCH.PAN:
+
+							if (scope.enablePan === false) return;
+
+							handleTouchStartPan();
+
+							state = STATE.TOUCH_PAN;
+
+							break;
+
+						default:
+
+							state = STATE.NONE;
+
+					}
+
+					break;
+
+				case 2:
+
+					switch (scope.touches.TWO) {
+
+						case TOUCH.DOLLY_PAN:
+
+							if (scope.enableZoom === false && scope.enablePan === false) return;
+
+							handleTouchStartDollyPan();
+
+							state = STATE.TOUCH_DOLLY_PAN;
+
+							break;
+
+						case TOUCH.DOLLY_ROTATE:
+
+							if (scope.enableZoom === false && scope.enableRotate === false) return;
+
+							handleTouchStartDollyRotate();
+
+							state = STATE.TOUCH_DOLLY_ROTATE;
+
+							break;
+
+						default:
+
+							state = STATE.NONE;
+
+					}
+
+					break;
+
+				default:
+
+					state = STATE.NONE;
+
+			}
+
+			if (state !== STATE.NONE) {
+
+				scope.dispatchEvent(_startEvent);
+
+			}
+
+		}
+
+		function onTouchMove(event) {
+
+			trackPointer(event);
+
+			switch (state) {
+
+				case STATE.TOUCH_ROTATE:
+
+					if (scope.enableRotate === false) return;
+
+					handleTouchMoveRotate(event);
+
+					scope.update();
+
+					break;
+
+				case STATE.TOUCH_PAN:
+
+					if (scope.enablePan === false) return;
+
+					handleTouchMovePan(event);
+
+					scope.update();
+
+					break;
+
+				case STATE.TOUCH_DOLLY_PAN:
+
+					if (scope.enableZoom === false && scope.enablePan === false) return;
+
+					handleTouchMoveDollyPan(event);
+
+					scope.update();
+
+					break;
+
+				case STATE.TOUCH_DOLLY_ROTATE:
+
+					if (scope.enableZoom === false && scope.enableRotate === false) return;
+
+					handleTouchMoveDollyRotate(event);
+
+					scope.update();
+
+					break;
+
+				default:
+
+					state = STATE.NONE;
+
+			}
+
+		}
+
+		function onContextMenu(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];
+
+		}
+
+		//
+
+		scope.domElement.addEventListener('contextmenu', onContextMenu);
+
+		scope.domElement.addEventListener('pointerdown', onPointerDown);
+		scope.domElement.addEventListener('pointercancel', onPointerCancel);
+		scope.domElement.addEventListener('wheel', onMouseWheel, { passive: false });
+
+		// force an update at start
+
+		this.update();
+
+	}
+
+}
+
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+// This is very similar to OrbitControls, another set of touch behavior
+//
+//    Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
+//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+//    Pan - left mouse, or arrow keys / touch: one-finger move
+
+class MapControls extends OrbitControls {
+
+	constructor(object, domElement) {
+
+		super(object, domElement);
+
+		this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
+
+		this.mouseButtons.LEFT = MOUSE.PAN;
+		this.mouseButtons.RIGHT = MOUSE.ROTATE;
+
+		this.touches.ONE = TOUCH.PAN;
+		this.touches.TWO = TOUCH.DOLLY_ROTATE;
+
+	}
+
+}

+ 27 - 23
spine-ts/spine-threejs/example/index.html

@@ -5,6 +5,7 @@
 	<title>spine-threejs</title>
 	<title>spine-threejs</title>
 	<script src="https://unpkg.com/[email protected]/build/three.js"></script>
 	<script src="https://unpkg.com/[email protected]/build/three.js"></script>
 	<script src="../dist/iife/spine-threejs.js"></script>
 	<script src="../dist/iife/spine-threejs.js"></script>
+	<script src="assets/OrbitControls.js"></script>
 </head>
 </head>
 <style>
 <style>
 	* {
 	* {
@@ -27,20 +28,21 @@
 <body>
 <body>
 	<script>
 	<script>
 		(function () {
 		(function () {
-			var scene, camera, renderer;
-			var geometry, material, mesh, skeletonMesh;
-			var assetManager;
-			var canvas;
-			var lastFrameTime = Date.now() / 1000;
-
-			var baseUrl = "assets/";
-			var skeletonFile = "raptor-pro.json";
-			var atlasFile = skeletonFile.replace("-pro", "").replace("-ess", "").replace(".json", ".atlas");
-			var animation = "walk";
+			let scene, camera, renderer;
+			let geometry, material, mesh, skeletonMesh;
+			let assetManager;
+			let canvas;
+			let controls;
+			let lastFrameTime = Date.now() / 1000;
+
+			let baseUrl = "assets/";
+			let skeletonFile = "raptor-pro.json";
+			let atlasFile = skeletonFile.replace("-pro", "").replace("-ess", "").replace(".json", ".atlas");
+			let animation = "walk";
 
 
 			function init() {
 			function init() {
 				// create the THREE.JS camera, scene and renderer (WebGL)
 				// create the THREE.JS camera, scene and renderer (WebGL)
-				var width = window.innerWidth, height = window.innerHeight;
+				let width = window.innerWidth, height = window.innerHeight;
 				camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
 				camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
 				camera.position.y = 100;
 				camera.position.y = 100;
 				camera.position.z = 400;
 				camera.position.z = 400;
@@ -49,6 +51,7 @@
 				renderer.setSize(width, height);
 				renderer.setSize(width, height);
 				document.body.appendChild(renderer.domElement);
 				document.body.appendChild(renderer.domElement);
 				canvas = renderer.domElement;
 				canvas = renderer.domElement;
+				controls = new OrbitControls(camera, renderer.domElement);
 
 
 				// load the assets required to display the Raptor model
 				// load the assets required to display the Raptor model
 				assetManager = new spine.AssetManager(baseUrl);
 				assetManager = new spine.AssetManager(baseUrl);
@@ -74,37 +77,38 @@
 					atlasLoader = new spine.AtlasAttachmentLoader(atlas);
 					atlasLoader = new spine.AtlasAttachmentLoader(atlas);
 
 
 					// Create a SkeletonJson instance for parsing the .json file.
 					// Create a SkeletonJson instance for parsing the .json file.
-					var skeletonJson = new spine.SkeletonJson(atlasLoader);
+					let skeletonJson = new spine.SkeletonJson(atlasLoader);
 
 
 					// Set the scale to apply during parsing, parse the file, and create a new skeleton.
 					// Set the scale to apply during parsing, parse the file, and create a new skeleton.
 					skeletonJson.scale = 0.4;
 					skeletonJson.scale = 0.4;
-					var skeletonData = skeletonJson.readSkeletonData(assetManager.require(skeletonFile));
+					let skeletonData = skeletonJson.readSkeletonData(assetManager.require(skeletonFile));
 
 
 					// Create a SkeletonMesh from the data and attach it to the scene
 					// Create a SkeletonMesh from the data and attach it to the scene
 					skeletonMesh = new spine.SkeletonMesh(skeletonData, (parameters) => {
 					skeletonMesh = new spine.SkeletonMesh(skeletonData, (parameters) => {
-						parameters.depthTest = false;
-						parameters.alphaTest = 0.5;
+						parameters.depthTest = true;
+						parameters.depthWrite = true;
+						parameters.alphaTest = 0.001;
 					});
 					});
 					skeletonMesh.state.setAnimation(0, animation, true);
 					skeletonMesh.state.setAnimation(0, animation, true);
 					mesh.add(skeletonMesh);
 					mesh.add(skeletonMesh);
 
 
+
 					requestAnimationFrame(render);
 					requestAnimationFrame(render);
 				} else requestAnimationFrame(load);
 				} else requestAnimationFrame(load);
 			}
 			}
 
 
-			var lastTime = Date.now();
+			let lastTime = Date.now();
 			function render() {
 			function render() {
 				// calculate delta time for animation purposes
 				// calculate delta time for animation purposes
-				var now = Date.now() / 1000;
-				var delta = now - lastFrameTime;
+				let now = Date.now() / 1000;
+				let delta = now - lastFrameTime;
 				lastFrameTime = now;
 				lastFrameTime = now;
 
 
 				// resize canvas to use full page, adjust camera/renderer
 				// resize canvas to use full page, adjust camera/renderer
 				resize();
 				resize();
 
 
-				// rotate the cube
-				mesh.rotation.x = Math.sin(now) * Math.PI * 0.2;
-				mesh.rotation.y = Math.cos(now) * Math.PI * 0.4;
+				// Update orbital controls
+				controls.update();
 
 
 				// update the animation
 				// update the animation
 				skeletonMesh.update(delta);
 				skeletonMesh.update(delta);
@@ -116,8 +120,8 @@
 			}
 			}
 
 
 			function resize() {
 			function resize() {
-				var w = window.innerWidth;
-				var h = window.innerHeight;
+				let w = window.innerWidth;
+				let h = window.innerHeight;
 				if (canvas.width != w || canvas.height != h) {
 				if (canvas.width != w || canvas.height != h) {
 					canvas.width = w;
 					canvas.width = w;
 					canvas.height = h;
 					canvas.height = h;

+ 2 - 1
spine-ts/spine-threejs/tsconfig.json

@@ -11,7 +11,8 @@
 		}
 		}
 	},
 	},
 	"include": [
 	"include": [
-		"**/*.ts"
+		"**/*.ts",
+		"src/OrbitControls.js"
 	],
 	],
 	"exclude": [
 	"exclude": [
 		"dist/**/*.d.ts"
 		"dist/**/*.d.ts"