You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
WebCAD/sample/OrbitControls.ts

773 lines
24 KiB

8 years ago
import * as THREE from 'three';
const STATE = {
NONE: - 1,
ROTATE: 0,
DOLLY: 1,
PAN: 2,
TOUCH_ROTATE: 3,
TOUCH_DOLLY: 4,
TOUCH_PAN: 5
};
const CHANGE_EVENT = { type: 'change' };
const START_EVENT = { type: 'start' };
const END_EVENT = { type: 'end' };
const EPS = 0.000001;
/**
* @author qiao / https://github.com/qiao
* @author mrdoob / http://mrdoob.com
* @author alteredq / http://alteredqualia.com/
* @author WestLangley / http://github.com/WestLangley
* @author erich666 / http://erichaines.com
* @author nicolaspanel / http://github.com/nicolaspanel
*
* 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 arrow keys / touch: three finger swipe
*/
export class OrbitControls extends THREE.EventDispatcher
{
public m_Camera: THREE.Camera;
8 years ago
domElement: HTMLElement | HTMLDocument;
window: Window;
// API
enabled: boolean;
target: THREE.Vector3;
enableZoom: boolean;
zoomSpeed: number;
minDistance: number;
maxDistance: number;
enableRotate: boolean;
rotateSpeed: number;
enablePan: boolean;
keyPanSpeed: number;
autoRotate: boolean;
autoRotateSpeed: number;
minZoom: number;
maxZoom: number;
minPolarAngle: number;
maxPolarAngle: number;
minAzimuthAngle: number;
maxAzimuthAngle: number;
enableKeys: boolean;
keys: { LEFT: number; UP: number; RIGHT: number; BOTTOM: number; CTRL: number };
8 years ago
mouseButtons: { ORBIT: THREE.MOUSE; ZOOM: THREE.MOUSE; PAN: THREE.MOUSE; };
enableDamping: boolean;
dampingFactor: number;
//Ctrl 按下
m_bCtrlIsDown: boolean;
8 years ago
private spherical: THREE.Spherical;
private sphericalDelta: THREE.Spherical;
private scale: number;
private target0: THREE.Vector3;
private position0: THREE.Vector3;
private zoom0: any;
private state: number;
private panOffset: THREE.Vector3;
private zoomChanged: boolean;
private rotateStart: THREE.Vector2;
private rotateEnd: THREE.Vector2;
private rotateDelta: THREE.Vector2
private panStart: THREE.Vector2;
private panEnd: THREE.Vector2;
private panDelta: THREE.Vector2;
private dollyStart: THREE.Vector2;
private dollyEnd: THREE.Vector2;
private dollyDelta: THREE.Vector2;
private updateLastPosition: THREE.Vector3;
private updateOffset: THREE.Vector3;
private updateQuat: THREE.Quaternion;
private updateLastQuaternion: THREE.Quaternion;
private updateQuatInverse: THREE.Quaternion;
private panLeftV: THREE.Vector3;
private panUpV: THREE.Vector3;
private panInternalOffset: THREE.Vector3;
private onContextMenu: EventListener;
private onMouseUp: EventListener;
private onMouseDown: EventListener;
private onMouseMove: EventListener;
private onMouseWheel: EventListener;
private onTouchStart: EventListener;
private onTouchEnd: EventListener;
private onTouchMove: EventListener;
private onKeyDown: EventListener;
constructor(camera: THREE.Camera, domElement?: HTMLElement, domWindow?: Window)
{
8 years ago
super();
this.m_Camera = camera;
8 years ago
this.domElement = (domElement !== undefined) ? domElement : document;
this.window = (domWindow !== undefined) ? domWindow : window;
// Set to false to disable this control
this.enabled = true;
// "target" sets the location of focus, where the object orbits around
this.target = new THREE.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 0
8 years ago
this.maxPolarAngle = Math.PI; // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.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.25;
// 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.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 round when fps is 60
// Set to false to disable use of the keys
this.enableKeys = true;
// The four arrow keys
this.keys = { CTRL: 17, LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
8 years ago
// Mouse buttons
this.mouseButtons = { ORBIT: THREE.MOUSE.MIDDLE, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.MIDDLE };
8 years ago
// for reset
this.target0 = this.target.clone();
this.position0 = this.m_Camera.position.clone();
this.zoom0 = (this.m_Camera as any).zoom;
8 years ago
// for update speedup
this.updateOffset = new THREE.Vector3();
// so camera.up is the orbit axis
this.updateQuat = new THREE.Quaternion().setFromUnitVectors(camera.up, new THREE.Vector3(0, 1, 0));
8 years ago
this.updateQuatInverse = this.updateQuat.clone().inverse();
this.updateLastPosition = new THREE.Vector3();
this.updateLastQuaternion = new THREE.Quaternion();
this.state = STATE.NONE;
this.scale = 1;
// current position in spherical coordinates
this.spherical = new THREE.Spherical();
this.sphericalDelta = new THREE.Spherical();
this.panOffset = new THREE.Vector3();
this.zoomChanged = false;
this.rotateStart = new THREE.Vector2();
this.rotateEnd = new THREE.Vector2();
this.rotateDelta = new THREE.Vector2();
this.panStart = new THREE.Vector2();
this.panEnd = new THREE.Vector2();
this.panDelta = new THREE.Vector2();
this.dollyStart = new THREE.Vector2();
this.dollyEnd = new THREE.Vector2();
this.dollyDelta = new THREE.Vector2();
this.panLeftV = new THREE.Vector3();
this.panUpV = new THREE.Vector3();
this.panInternalOffset = new THREE.Vector3();
// event handlers - FSM: listen for events and reset state
// http://www.itwendao.com/article/detail/288536.html
this.onMouseDown = (event: ThreeEvent) =>
{
8 years ago
if (this.enabled === false) return;
event.preventDefault();
if ((event as any).button === this.mouseButtons.ORBIT)
{
//旋转
if (this.m_bCtrlIsDown)
{
if (this.enableRotate === false) return;
this.rotateStart.set(event.clientX, event.clientY);
this.state = STATE.ROTATE;
}
else
{
if (this.enablePan === false) return;
this.panStart.set(event.clientX, event.clientY);
this.state = STATE.PAN;
}
8 years ago
}
// //旋转
// if ((event as any).button === this.mouseButtons.ORBIT)
// {
// if (this.enableRotate === false) return;
// this.rotateStart.set(event.clientX, event.clientY);
// this.state = STATE.ROTATE;
// }
// //缩放
// else if (event.button === this.mouseButtons.ZOOM)
// {
// if (this.enableZoom === false) return;
// this.dollyStart.set(event.clientX, event.clientY);
// this.state = STATE.DOLLY;
// }
// //平移
// else if (event.button === this.mouseButtons.PAN)
// {
// if (this.enablePan === false) return;
// this.panStart.set(event.clientX, event.clientY);
// this.state = STATE.PAN;
// }
if (this.state !== STATE.NONE)
{
8 years ago
document.addEventListener('mousemove', this.onMouseMove, false);
document.addEventListener('mouseup', this.onMouseUp, false);
this.dispatchEvent(START_EVENT);
}
};
this.onMouseMove = (event: ThreeEvent) =>
{
8 years ago
if (this.enabled === false) return;
event.preventDefault();
if (this.state === STATE.ROTATE)
{
8 years ago
if (this.enableRotate === false) return;
this.rotateEnd.set(event.clientX, event.clientY);
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
const element = this.domElement === document ? this.domElement.body : this.domElement;
// rotating across whole screen goes 360 degrees around
this.rotateLeft(2 * Math.PI * this.rotateDelta.x / (element as any).clientWidth * this.rotateSpeed);
// rotating up and down along whole screen attempts to go 360, but limited to 180
this.rotateUp(2 * Math.PI * this.rotateDelta.y / (element as any).clientHeight * this.rotateSpeed);
this.rotateStart.copy(this.rotateEnd);
this.update();
} else if (this.state === STATE.DOLLY)
{
8 years ago
if (this.enableZoom === false) return;
this.dollyEnd.set(event.clientX, event.clientY);
this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart);
if (this.dollyDelta.y > 0)
{
8 years ago
this.dollyIn(this.getZoomScale());
} else if (this.dollyDelta.y < 0)
{
8 years ago
this.dollyOut(this.getZoomScale());
}
this.dollyStart.copy(this.dollyEnd);
this.update();
} else if (this.state === STATE.PAN)
{
8 years ago
if (this.enablePan === false) return;
this.panEnd.set(event.clientX, event.clientY);
this.panDelta.subVectors(this.panEnd, this.panStart);
this.pan(this.panDelta.x, this.panDelta.y);
this.panStart.copy(this.panEnd);
this.update();
}
}
this.onMouseUp = (event: ThreeEvent) =>
{
8 years ago
if (this.enabled === false) return;
document.removeEventListener('mousemove', this.onMouseMove, false);
document.removeEventListener('mouseup', this.onMouseUp, false);
this.dispatchEvent(END_EVENT);
this.state = STATE.NONE;
};
this.onMouseWheel = (event: ThreeEvent) =>
{
8 years ago
if (this.enabled === false || this.enableZoom === false || (this.state !== STATE.NONE && this.state !== STATE.ROTATE)) return;
event.preventDefault();
event.stopPropagation();
if (event.deltaY < 0)
{
8 years ago
this.dollyOut(this.getZoomScale());
} else if (event.deltaY > 0)
{
8 years ago
this.dollyIn(this.getZoomScale());
}
this.update();
this.dispatchEvent(START_EVENT); // not sure why these are here...
this.dispatchEvent(END_EVENT);
};
this.onKeyDown = (event: ThreeEvent) =>
{
8 years ago
if (this.enabled === false || this.enableKeys === false || this.enablePan === false) return;
switch (event.keyCode)
{
8 years ago
case this.keys.UP: {
this.pan(0, this.keyPanSpeed);
this.update();
} break;
case this.keys.BOTTOM: {
this.pan(0, - this.keyPanSpeed);
this.update();
} break;
case this.keys.LEFT: {
this.pan(this.keyPanSpeed, 0);
this.update();
} break;
case this.keys.RIGHT: {
this.pan(- this.keyPanSpeed, 0);
this.update();
} break;
case this.keys.CTRL: {
this.m_bCtrlIsDown = true;
}
8 years ago
}
};
this.onTouchStart = (event: ThreeEvent) =>
{
8 years ago
if (this.enabled === false) return;
switch (event.touches.length)
{
8 years ago
// one-fingered touch: rotate
case 1: {
if (this.enableRotate === false) return;
this.rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
this.state = STATE.TOUCH_ROTATE;
} break;
// two-fingered touch: dolly
case 2: {
if (this.enableZoom === false) return;
var dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY;
var distance = Math.sqrt(dx * dx + dy * dy);
this.dollyStart.set(0, distance);
this.state = STATE.TOUCH_DOLLY;
} break;
// three-fingered touch: pan
case 3: {
if (this.enablePan === false) return;
this.panStart.set(event.touches[0].pageX, event.touches[0].pageY);
this.state = STATE.TOUCH_PAN;
} break;
default: {
this.state = STATE.NONE;
}
}
if (this.state !== STATE.NONE)
{
8 years ago
this.dispatchEvent(START_EVENT);
}
};
this.onTouchMove = (event: ThreeEvent) =>
{
8 years ago
if (this.enabled === false) return;
event.preventDefault();
event.stopPropagation();
switch (event.touches.length)
{
8 years ago
// one-fingered touch: rotate
case 1: {
if (this.enableRotate === false) return;
if (this.state !== STATE.TOUCH_ROTATE) return; // is this needed?...
this.rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
var element = this.domElement === document ? this.domElement.body : this.domElement;
// rotating across whole screen goes 360 degrees around
this.rotateLeft(2 * Math.PI * this.rotateDelta.x / (element as any).clientWidth * this.rotateSpeed);
// rotating up and down along whole screen attempts to go 360, but limited to 180
this.rotateUp(2 * Math.PI * this.rotateDelta.y / (element as any).clientHeight * this.rotateSpeed);
this.rotateStart.copy(this.rotateEnd);
this.update();
} break;
// two-fingered touch: dolly
case 2: {
if (this.enableZoom === false) return;
if (this.state !== STATE.TOUCH_DOLLY) return; // is this needed?...
//console.log( 'handleTouchMoveDolly' );
var dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY;
var distance = Math.sqrt(dx * dx + dy * dy);
this.dollyEnd.set(0, distance);
this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart);
if (this.dollyDelta.y > 0)
{
8 years ago
this.dollyOut(this.getZoomScale());
} else if (this.dollyDelta.y < 0)
{
8 years ago
this.dollyIn(this.getZoomScale());
}
this.dollyStart.copy(this.dollyEnd);
this.update();
} break;
// three-fingered touch: pan
case 3: {
if (this.enablePan === false) return;
if (this.state !== STATE.TOUCH_PAN) return; // is this needed?...
this.panEnd.set(event.touches[0].pageX, event.touches[0].pageY);
this.panDelta.subVectors(this.panEnd, this.panStart);
this.pan(this.panDelta.x, this.panDelta.y);
this.panStart.copy(this.panEnd);
this.update();
} break;
default: {
this.state = STATE.NONE;
}
}
};
this.onTouchEnd = (event: Event) =>
{
8 years ago
if (this.enabled === false) return;
this.dispatchEvent(END_EVENT);
this.state = STATE.NONE;
}
this.onContextMenu = (event) =>
{
8 years ago
event.preventDefault();
};
this.domElement.addEventListener('contextmenu', this.onContextMenu, false);
this.domElement.addEventListener('mousedown', this.onMouseDown, false);
this.domElement.addEventListener('wheel', this.onMouseWheel, false);
this.domElement.addEventListener('touchstart', this.onTouchStart, false);
this.domElement.addEventListener('touchend', this.onTouchEnd, false);
this.domElement.addEventListener('touchmove', this.onTouchMove, false);
this.window.addEventListener('keydown', this.onKeyDown, false);
this.window.addEventListener('keyup', this.onKeyUp, false);
8 years ago
// force an update at start
this.update();
}
onKeyUp = (event: ThreeEvent) =>
{
switch (event.keyCode)
{
case this.keys.CTRL: {
this.m_bCtrlIsDown = false;
}
}
}
update()
{
const position = this.m_Camera.position;
8 years ago
this.updateOffset.copy(position).sub(this.target);
// rotate offset to "y-axis-is-up" space
this.updateOffset.applyQuaternion(this.updateQuat);
// angle from z-axis around y-axis
this.spherical.setFromVector3(this.updateOffset);
if (this.autoRotate && this.state === STATE.NONE)
{
8 years ago
this.rotateLeft(this.getAutoRotationAngle());
}
(this.spherical as any).theta += (this.sphericalDelta as any).theta;
(this.spherical as any).phi += (this.sphericalDelta as any).phi;
console.log(this.spherical.phi);
8 years ago
// restrict theta to be between desired limits
// (this.spherical as (any) as any).theta = Math.max(this.minAzimuthAngle, Math.min(this.maxAzimuthAngle, (this.spherical as any).theta));
8 years ago
// restrict phi to be between desired limits
// (this.spherical as any).phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, (this.spherical as any).phi));
8 years ago
this.spherical.makeSafe();
(this.spherical as any).radius *= this.scale;
// restrict radius to be between desired limits
(this.spherical as any).radius = Math.max(this.minDistance, Math.min(this.maxDistance, (this.spherical as any).radius));
// move target to panned location
this.target.add(this.panOffset);
this.updateOffset.setFromSpherical(this.spherical);
// rotate offset back to "camera-up-vector-is-up" space
this.updateOffset.applyQuaternion(this.updateQuatInverse);
position.copy(this.target).add(this.updateOffset);
this.m_Camera.lookAt(this.target);
8 years ago
if (this.enableDamping === true)
{
8 years ago
(this.sphericalDelta as any).theta *= (1 - this.dampingFactor);
(this.sphericalDelta as any).phi *= (1 - this.dampingFactor);
} else
{
8 years ago
this.sphericalDelta.set(0, 0, 0);
}
this.scale = 1;
this.panOffset.set(0, 0, 0);
// update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
if (this.zoomChanged ||
this.updateLastPosition.distanceToSquared(this.m_Camera.position) > EPS ||
8 * (1 - this.updateLastQuaternion.dot(this.m_Camera.quaternion)) > EPS)
{
8 years ago
this.dispatchEvent(CHANGE_EVENT);
this.updateLastPosition.copy(this.m_Camera.position);
this.updateLastQuaternion.copy(this.m_Camera.quaternion);
8 years ago
this.zoomChanged = false;
return true;
}
return false;
}
panLeft(distance: number, objectMatrix)
{
8 years ago
this.panLeftV.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
this.panLeftV.multiplyScalar(- distance);
this.panOffset.add(this.panLeftV);
}
panUp(distance: number, objectMatrix)
{
8 years ago
this.panUpV.setFromMatrixColumn(objectMatrix, 1); // get Y column of objectMatrix
this.panUpV.multiplyScalar(distance);
this.panOffset.add(this.panUpV);
}
// deltaX and deltaY are in pixels; right and down are positive
pan(deltaX: number, deltaY: number)
{
8 years ago
const element = this.domElement === document ? this.domElement.body : this.domElement;
if (this.m_Camera instanceof THREE.PerspectiveCamera)
{
8 years ago
// perspective
const position = this.m_Camera.position;
8 years ago
this.panInternalOffset.copy(position).sub(this.target);
var targetDistance = this.panInternalOffset.length();
// half of the fov is center to top of screen
targetDistance *= Math.tan((this.m_Camera.fov / 2) * Math.PI / 180.0);
8 years ago
// we actually don't use screenWidth, since perspective camera is fixed to screen height
this.panLeft(2 * deltaX * targetDistance / (element as any).clientHeight, this.m_Camera.matrix);
this.panUp(2 * deltaY * targetDistance / (element as any).clientHeight, this.m_Camera.matrix);
} else if (this.m_Camera instanceof THREE.OrthographicCamera)
{
8 years ago
// orthographic
this.panLeft(deltaX * (this.m_Camera.right - this.m_Camera.left) / this.m_Camera.zoom / (element as any).clientWidth, this.m_Camera.matrix);
this.panUp(deltaY * (this.m_Camera.top - this.m_Camera.bottom) / this.m_Camera.zoom / (element as any).clientHeight, this.m_Camera.matrix);
} else
{
8 years ago
// camera neither orthographic nor perspective
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.');
this.enablePan = false;
}
}
dollyIn(dollyScale)
{
if (this.m_Camera instanceof THREE.PerspectiveCamera)
{
8 years ago
this.scale /= dollyScale;
} else if (this.m_Camera instanceof THREE.OrthographicCamera)
{
this.m_Camera.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.m_Camera.zoom * dollyScale));
this.m_Camera.updateProjectionMatrix();
8 years ago
this.zoomChanged = true;
} else
{
8 years ago
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.');
this.enableZoom = false;
}
}
dollyOut(dollyScale)
{
if (this.m_Camera instanceof THREE.PerspectiveCamera)
{
8 years ago
this.scale *= dollyScale;
} else if (this.m_Camera instanceof THREE.OrthographicCamera)
{
this.m_Camera.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.m_Camera.zoom / dollyScale));
this.m_Camera.updateProjectionMatrix();
8 years ago
this.zoomChanged = true;
} else
{
8 years ago
console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.');
this.enableZoom = false;
}
}
getAutoRotationAngle()
{
8 years ago
return 2 * Math.PI / 60 / 60 * this.autoRotateSpeed;
}
getZoomScale()
{
8 years ago
return Math.pow(0.95, this.zoomSpeed);
}
rotateLeft(angle: number)
{
8 years ago
(this.sphericalDelta as any).theta -= angle;
}
rotateUp(angle: number)
{
8 years ago
(this.sphericalDelta as any).phi -= angle;
}
getPolarAngle(): number
{
8 years ago
return (this.spherical as any).phi;
}
getAzimuthalAngle(): number
{
8 years ago
return (this.spherical as any).theta;
}
dispose(): void
{
8 years ago
this.domElement.removeEventListener('contextmenu', this.onContextMenu, false);
this.domElement.removeEventListener('mousedown', this.onMouseDown, false);
this.domElement.removeEventListener('wheel', this.onMouseWheel, false);
this.domElement.removeEventListener('touchstart', this.onTouchStart, false);
this.domElement.removeEventListener('touchend', this.onTouchEnd, false);
this.domElement.removeEventListener('touchmove', this.onTouchMove, false);
document.removeEventListener('mousemove', this.onMouseMove, false);
document.removeEventListener('mouseup', this.onMouseUp, false);
this.window.removeEventListener('keydown', this.onKeyDown, false);
//this.dispatchEvent( { type: 'dispose' } ); // should this be added here?
}
reset(): void
{
8 years ago
this.target.copy(this.target0);
this.m_Camera.position.copy(this.position0);
(this.m_Camera as any).zoom = this.zoom0;
8 years ago
(this.m_Camera as any).updateProjectionMatrix();
8 years ago
this.dispatchEvent(CHANGE_EVENT);
this.update();
this.state = STATE.NONE;
}
// backward compatibility
get center(): THREE.Vector3
{
8 years ago
console.warn('THREE.OrbitControls: .center has been renamed to .target');
return this.target;
}
get noZoom(): boolean
{
8 years ago
console.warn('THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.');
return !this.enableZoom;
}
set noZoom(value: boolean)
{
8 years ago
console.warn('THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.');
this.enableZoom = !value;
}
}
interface ThreeEvent extends Event
{
8 years ago
clientX: number;
clientY: number;
deltaY: number;
button: THREE.MOUSE;
touches: Array<any>;
keyCode: number;
}