├── .gitignore
├── Gulpfile.js
├── types
├── tslint.json
├── index.d.ts
├── tsconfig.json
├── test.ts
└── three-map-controls.d.ts
├── test
├── stub_dom.js
└── test.js
├── index.html
├── package.json
├── gulp
└── webpack.js
├── dist
├── vendor-manifest.json
├── three-map-controls.js
├── demo.js
└── three-map-controls.js.map
├── LICENSE
├── README.md
└── src
├── demo.js
└── three-map-controls.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | .idea
--------------------------------------------------------------------------------
/Gulpfile.js:
--------------------------------------------------------------------------------
1 | require('./gulp/webpack');
2 |
3 |
--------------------------------------------------------------------------------
/types/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "dtslint/dtslint.json",
3 | "rules": {
4 | "eofline": false
5 | }
6 | }
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | // TypeScript Version: 3.5
2 |
3 | import MapControls from './three-map-controls';
4 | export default MapControls;
5 |
--------------------------------------------------------------------------------
/test/stub_dom.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | console: {
3 | log: function () {}
4 | },
5 | document: {
6 | body: {
7 | clientWidth: 1920,
8 | clientHeight: 1080,
9 | addEventListener: function () {},
10 | removeEventListener: function () {}
11 | }
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "lib": ["es6", "dom"],
5 | "noImplicitAny": true,
6 | "noImplicitThis": true,
7 | "strictNullChecks": true,
8 | "strictFunctionTypes": true,
9 | "noEmit": true,
10 | "baseUrl": ".",
11 | "paths": { "three-map-controls": ["."] }
12 | }
13 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
31 |
32 |
--------------------------------------------------------------------------------
/types/test.ts:
--------------------------------------------------------------------------------
1 | import MapControls from 'three-map-controls';
2 | import { PerspectiveCamera, Sphere, Plane, Vector3, Box2, MOUSE, Box3 } from "three";
3 |
4 | const camera = new PerspectiveCamera();
5 | const element = new Element();
6 |
7 | let controls: MapControls = new MapControls(camera, element, { mode: "foo" }); // $ExpectError
8 | controls = new MapControls(camera, element, { foo: "bar" }); // $ExpectError
9 |
10 | controls = new MapControls(camera, element, { target: new Box3()}); // $ExpectError
11 | controls = new MapControls(camera, element, { target: new Plane(new Vector3(0, 0, 0), 0)}); // $ExpectError
12 |
13 | // $ExpectType MapControls
14 | controls = new MapControls(camera, element, { mode: "plane", target: new Sphere(new Vector3(0, 0, 0), 5) });
15 |
16 | // $ExpectType MapControls
17 | controls = new MapControls(camera, element, {
18 | mode: "plane",
19 | target: new Sphere(new Vector3(0, 0, 0), 5),
20 | maxDistance: 5
21 | });
22 |
23 | controls.getZoomAlpha(); // $ExpectType number
24 | controls.targetAreaVisible(); // $ExpectType Box2
25 |
26 | controls.mouseButtons = {PAN: MOUSE.RIGHT};
27 |
28 | controls.mouseButtons = {FOO: "BAR"}; // $ExpectError
29 | controls.mouseButtons = {FOO: MOUSE.LEFT}; // $ExpectError
30 |
31 | controls.keys = {PAN_UP: "ArrowUp"};
32 | controls.keys = {FOO: 1}; // $ExpectError
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@windfish-studio/three-map-controls",
3 | "version": "1.1.10",
4 | "types": "types",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/sikanrong/three-map-controls.git"
8 | },
9 | "scripts": {
10 | "test": "ava --tap test/test.js",
11 | "dtslint": "dtslint types"
12 | },
13 | "main": "src/three-map-controls.js",
14 | "author": {
15 | "name": "Alex Pilafian",
16 | "email": "sikanrong@gmail.com",
17 | "url": "http://github.com/sikanrong"
18 | },
19 | "dependencies": {
20 | "three": "^0.113.2"
21 | },
22 | "devDependencies": {
23 | "ava": "^2.0.0",
24 | "webpack": "4.6.0",
25 | "@babel/core": "^7.4.3",
26 | "@babel/register": "^7.4.0",
27 | "@babel/preset-env": "^7.4.3",
28 | "babel-loader": "^8.0.6",
29 | "webpack-bundle-analyzer": "^2.11.1",
30 | "chalk-log": "1.2.0",
31 | "chalk": "^2.4.1",
32 | "dom-stub": "1.0.1",
33 | "gulp-live-server": "0.0.30",
34 | "browserify-row-flow": "0.1.0",
35 | "watchify": "3.7.0",
36 | "babelify": "7.3.0",
37 | "gulp": "^4.0.2",
38 | "gulp-util": "3.0.7",
39 | "del": "2.2.2",
40 | "path": "0.12.7",
41 | "resolve-file": "0.2.1",
42 | "dtslint": "^0.8.0",
43 | "typescript": "^3.5.3"
44 | },
45 | "ava": {
46 | "require": [
47 | "@babel/register"
48 | ],
49 | "babel": {
50 | "testOptions": {
51 | "presets": [
52 | "@babel/preset-env"
53 | ]
54 | }
55 | }
56 | },
57 | "babel": {
58 | "presets": [
59 | [
60 | "@babel/preset-env",
61 | {
62 | "targets": {
63 | "node": "current"
64 | }
65 | }
66 | ]
67 | ]
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/types/three-map-controls.d.ts:
--------------------------------------------------------------------------------
1 | // TypeScript Version: 3.5
2 |
3 | import {
4 | Box2,
5 | EventDispatcher,
6 | MOUSE,
7 | Object3D,
8 | PerspectiveCamera,
9 | Plane,
10 | Sphere,
11 | Vector2,
12 | Vector3
13 | } from "three";
14 |
15 | export enum KeyboardActions {
16 | PAN_UP,
17 | PAN_DOWN,
18 | PAN_LEFT,
19 | PAN_RIGHT,
20 | ZOOM_OUT,
21 | ZOOM_IN
22 | }
23 |
24 | export enum MouseActions {
25 | ZOOM,
26 | PAN
27 | }
28 |
29 | export enum MapMode {
30 | plane,
31 | sphere
32 | }
33 |
34 | export interface MapControlsRequiredOpts {
35 | target: MapControls['target'];
36 | mode: MapControls['mode'];
37 | }
38 |
39 | export default class MapControls extends EventDispatcher {
40 | constructor(
41 | camera: PerspectiveCamera,
42 | domElement: Element,
43 | options: MapControlsRequiredOpts & { [key in keyof MapControls]?: MapControls[key] }
44 | );
45 |
46 | camera: PerspectiveCamera;
47 | domElement: Element;
48 | enabled: boolean;
49 | target: Plane | Sphere;
50 | mode: keyof typeof MapMode;
51 |
52 | minDistance: number;
53 | maxDistance: number;
54 | enableZoom: boolean;
55 | zoomSpeed: number;
56 | zoomDampingAlpha: number;
57 | initialZoom: number;
58 |
59 | enablePan: boolean;
60 | panDampingAlpha: number;
61 |
62 | keyPanSpeed: number;
63 | keyZoomSpeed: number;
64 | enableKeys: boolean;
65 |
66 | keys: {[key in keyof typeof KeyboardActions]?: string};
67 | mouseButtons: {[key in keyof typeof MouseActions]?: MOUSE};
68 |
69 | getZoomAlpha(): number;
70 | reset(): void;
71 | update(): void;
72 | dispose(): void;
73 |
74 | zoomToFit(
75 | mesh: Object3D,
76 | center: Vector3,
77 | dims: Vector2
78 | ): void;
79 |
80 | targetAreaVisible(): Box2;
81 | targetAreaVisibleDeg(): Box2;
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/gulp/webpack.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path = require('path');
3 | var gulp = require('gulp');
4 |
5 | var dist_path = path.resolve(__dirname, '../dist');
6 | var app_entrypoint = path.resolve(__dirname, '../src/three-map-controls.js');
7 | var demo_entrypoint = path.resolve(__dirname, '../src/demo.js');
8 |
9 | var vendor_deps = Object.keys(require('../package.json').dependencies);
10 | var extra_plugins = [];
11 | var appConf;
12 |
13 | var doPack = async function(conf, watch){
14 | await new Promise((resolve, reject) => {
15 | const reportStats = function (err, stats) {
16 | console.log(stats.toString({
17 | colors: true
18 | }));
19 |
20 | if (err) {
21 | reject();
22 | return;
23 | }
24 |
25 | resolve(stats);
26 | };
27 |
28 | var compiler = webpack(conf);
29 |
30 | if(watch){
31 | compiler.watch({
32 | aggregateTimeout: 300,
33 | ignored: [/[\\/]node_modules[\\/]/, /[\\/]assets[\\/]generated[\\/]/]
34 | }, reportStats);
35 | }else{
36 | compiler.run(reportStats);
37 | }
38 | });
39 | };
40 |
41 | gulp.task('webpack-vendor', async function webpackVendor () {
42 | return doPack({
43 | entry: {
44 | vendor: vendor_deps
45 | },
46 |
47 | output: {
48 | path: dist_path,
49 | filename: '[name].js',
50 | library: '[name]',
51 | publicPath: '/'
52 | },
53 |
54 | node: {
55 | fs: "empty"
56 | },
57 |
58 | plugins: [
59 | new webpack.DllPlugin({
60 | name: '[name]',
61 | path: path.join( dist_path, '[name]-manifest.json' ),
62 | })
63 | ]
64 | }, false);
65 | });
66 |
67 |
68 | gulp.task('webpack', gulp.series('webpack-vendor', async function webpackWatch () {
69 | appConf = {
70 | entry: {
71 | "three-map-controls": app_entrypoint,
72 | "demo": demo_entrypoint
73 | },
74 | output: {
75 | path: dist_path,
76 | filename: '[name].js',
77 | publicPath: '/'
78 | },
79 | module: {
80 | rules: [
81 | {
82 | test: /\.js$/,
83 | exclude: /node_modules/,
84 | use: {
85 | loader: "babel-loader"
86 | }
87 | }
88 | ]
89 | },
90 |
91 | plugins: [
92 | new webpack.DllReferencePlugin({
93 | manifest: require(path.join(dist_path, 'vendor-manifest.json')),
94 | }),
95 |
96 | new webpack.SourceMapDevToolPlugin({
97 | exclude: ['vendor'],
98 | filename: '[name].js.map'
99 | })
100 | ].concat(extra_plugins)
101 | };
102 |
103 | return doPack(appConf, true);
104 | }));
--------------------------------------------------------------------------------
/dist/vendor-manifest.json:
--------------------------------------------------------------------------------
1 | {"name":"vendor","content":{"./node_modules/three/build/three.module.js":{"id":0,"buildMeta":{"exportsType":"namespace","providedExports":["ACESFilmicToneMapping","AddEquation","AddOperation","AdditiveBlending","AlphaFormat","AlwaysDepth","AmbientLight","AmbientLightProbe","AnimationClip","AnimationLoader","AnimationMixer","AnimationObjectGroup","AnimationUtils","ArcCurve","ArrayCamera","ArrowHelper","Audio","AudioAnalyser","AudioContext","AudioListener","AudioLoader","AxesHelper","AxisHelper","BackSide","BasicDepthPacking","BasicShadowMap","BinaryTextureLoader","Bone","BooleanKeyframeTrack","BoundingBoxHelper","Box2","Box3","Box3Helper","BoxBufferGeometry","BoxGeometry","BoxHelper","BufferAttribute","BufferGeometry","BufferGeometryLoader","ByteType","Cache","Camera","CameraHelper","CanvasRenderer","CanvasTexture","CatmullRomCurve3","CineonToneMapping","CircleBufferGeometry","CircleGeometry","ClampToEdgeWrapping","Clock","ClosedSplineCurve3","Color","ColorKeyframeTrack","CompressedTexture","CompressedTextureLoader","ConeBufferGeometry","ConeGeometry","CubeCamera","CubeGeometry","CubeReflectionMapping","CubeRefractionMapping","CubeTexture","CubeTextureLoader","CubeUVReflectionMapping","CubeUVRefractionMapping","CubicBezierCurve","CubicBezierCurve3","CubicInterpolant","CullFaceBack","CullFaceFront","CullFaceFrontBack","CullFaceNone","Curve","CurvePath","CustomBlending","CylinderBufferGeometry","CylinderGeometry","Cylindrical","DataTexture","DataTexture2DArray","DataTexture3D","DataTextureLoader","DefaultLoadingManager","DepthFormat","DepthStencilFormat","DepthTexture","DirectionalLight","DirectionalLightHelper","DirectionalLightShadow","DiscreteInterpolant","DodecahedronBufferGeometry","DodecahedronGeometry","DoubleSide","DstAlphaFactor","DstColorFactor","DynamicBufferAttribute","EdgesGeometry","EdgesHelper","EllipseCurve","EqualDepth","EquirectangularReflectionMapping","EquirectangularRefractionMapping","Euler","EventDispatcher","ExtrudeBufferGeometry","ExtrudeGeometry","Face3","Face4","FaceColors","FaceNormalsHelper","FileLoader","FlatShading","Float32Attribute","Float32BufferAttribute","Float64Attribute","Float64BufferAttribute","FloatType","Fog","FogExp2","Font","FontLoader","FrontFaceDirectionCCW","FrontFaceDirectionCW","FrontSide","Frustum","GammaEncoding","Geometry","GeometryUtils","GreaterDepth","GreaterEqualDepth","GridHelper","Group","HalfFloatType","HemisphereLight","HemisphereLightHelper","HemisphereLightProbe","IcosahedronBufferGeometry","IcosahedronGeometry","ImageBitmapLoader","ImageLoader","ImageUtils","ImmediateRenderObject","InstancedBufferAttribute","InstancedBufferGeometry","InstancedInterleavedBuffer","Int16Attribute","Int16BufferAttribute","Int32Attribute","Int32BufferAttribute","Int8Attribute","Int8BufferAttribute","IntType","InterleavedBuffer","InterleavedBufferAttribute","Interpolant","InterpolateDiscrete","InterpolateLinear","InterpolateSmooth","JSONLoader","KeyframeTrack","LOD","LatheBufferGeometry","LatheGeometry","Layers","LensFlare","LessDepth","LessEqualDepth","Light","LightProbe","LightProbeHelper","LightShadow","Line","Line3","LineBasicMaterial","LineCurve","LineCurve3","LineDashedMaterial","LineLoop","LinePieces","LineSegments","LineStrip","LinearEncoding","LinearFilter","LinearInterpolant","LinearMipMapLinearFilter","LinearMipMapNearestFilter","LinearToneMapping","Loader","LoaderUtils","LoadingManager","LogLuvEncoding","LoopOnce","LoopPingPong","LoopRepeat","LuminanceAlphaFormat","LuminanceFormat","MOUSE","Material","MaterialLoader","Math","Matrix3","Matrix4","MaxEquation","Mesh","MeshBasicMaterial","MeshDepthMaterial","MeshDistanceMaterial","MeshFaceMaterial","MeshLambertMaterial","MeshMatcapMaterial","MeshNormalMaterial","MeshPhongMaterial","MeshPhysicalMaterial","MeshStandardMaterial","MeshToonMaterial","MinEquation","MirroredRepeatWrapping","MixOperation","MultiMaterial","MultiplyBlending","MultiplyOperation","NearestFilter","NearestMipMapLinearFilter","NearestMipMapNearestFilter","NeverDepth","NoBlending","NoColors","NoToneMapping","NormalBlending","NotEqualDepth","NumberKeyframeTrack","Object3D","ObjectLoader","ObjectSpaceNormalMap","OctahedronBufferGeometry","OctahedronGeometry","OneFactor","OneMinusDstAlphaFactor","OneMinusDstColorFactor","OneMinusSrcAlphaFactor","OneMinusSrcColorFactor","OrthographicCamera","PCFShadowMap","PCFSoftShadowMap","ParametricBufferGeometry","ParametricGeometry","Particle","ParticleBasicMaterial","ParticleSystem","ParticleSystemMaterial","Path","PerspectiveCamera","Plane","PlaneBufferGeometry","PlaneGeometry","PlaneHelper","PointCloud","PointCloudMaterial","PointLight","PointLightHelper","Points","PointsMaterial","PolarGridHelper","PolyhedronBufferGeometry","PolyhedronGeometry","PositionalAudio","PositionalAudioHelper","PropertyBinding","PropertyMixer","QuadraticBezierCurve","QuadraticBezierCurve3","Quaternion","QuaternionKeyframeTrack","QuaternionLinearInterpolant","REVISION","RGBADepthPacking","RGBAFormat","RGBA_ASTC_10x10_Format","RGBA_ASTC_10x5_Format","RGBA_ASTC_10x6_Format","RGBA_ASTC_10x8_Format","RGBA_ASTC_12x10_Format","RGBA_ASTC_12x12_Format","RGBA_ASTC_4x4_Format","RGBA_ASTC_5x4_Format","RGBA_ASTC_5x5_Format","RGBA_ASTC_6x5_Format","RGBA_ASTC_6x6_Format","RGBA_ASTC_8x5_Format","RGBA_ASTC_8x6_Format","RGBA_ASTC_8x8_Format","RGBA_PVRTC_2BPPV1_Format","RGBA_PVRTC_4BPPV1_Format","RGBA_S3TC_DXT1_Format","RGBA_S3TC_DXT3_Format","RGBA_S3TC_DXT5_Format","RGBDEncoding","RGBEEncoding","RGBEFormat","RGBFormat","RGBM16Encoding","RGBM7Encoding","RGB_ETC1_Format","RGB_PVRTC_2BPPV1_Format","RGB_PVRTC_4BPPV1_Format","RGB_S3TC_DXT1_Format","RawShaderMaterial","Ray","Raycaster","RectAreaLight","RectAreaLightHelper","RedFormat","ReinhardToneMapping","RepeatWrapping","ReverseSubtractEquation","RingBufferGeometry","RingGeometry","Scene","SceneUtils","ShaderChunk","ShaderLib","ShaderMaterial","ShadowMaterial","Shape","ShapeBufferGeometry","ShapeGeometry","ShapePath","ShapeUtils","ShortType","Skeleton","SkeletonHelper","SkinnedMesh","SmoothShading","Sphere","SphereBufferGeometry","SphereGeometry","Spherical","SphericalHarmonics3","SphericalReflectionMapping","Spline","SplineCurve","SplineCurve3","SpotLight","SpotLightHelper","SpotLightShadow","Sprite","SpriteMaterial","SrcAlphaFactor","SrcAlphaSaturateFactor","SrcColorFactor","StereoCamera","StringKeyframeTrack","SubtractEquation","SubtractiveBlending","TangentSpaceNormalMap","TetrahedronBufferGeometry","TetrahedronGeometry","TextBufferGeometry","TextGeometry","Texture","TextureLoader","TorusBufferGeometry","TorusGeometry","TorusKnotBufferGeometry","TorusKnotGeometry","Triangle","TriangleFanDrawMode","TriangleStripDrawMode","TrianglesDrawMode","TubeBufferGeometry","TubeGeometry","UVMapping","Uint16Attribute","Uint16BufferAttribute","Uint32Attribute","Uint32BufferAttribute","Uint8Attribute","Uint8BufferAttribute","Uint8ClampedAttribute","Uint8ClampedBufferAttribute","Uncharted2ToneMapping","Uniform","UniformsLib","UniformsUtils","UnsignedByteType","UnsignedInt248Type","UnsignedIntType","UnsignedShort4444Type","UnsignedShort5551Type","UnsignedShort565Type","UnsignedShortType","Vector2","Vector3","Vector4","VectorKeyframeTrack","Vertex","VertexColors","VertexNormalsHelper","VideoTexture","WebGLMultisampleRenderTarget","WebGLRenderTarget","WebGLRenderTargetCube","WebGLRenderer","WebGLUtils","WireframeGeometry","WireframeHelper","WrapAroundEnding","XHRLoader","ZeroCurvatureEnding","ZeroFactor","ZeroSlopeEnding","sRGBEncoding"]}}}}
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # three-map-controls
2 | Map Controls class for ThreeJS; pan and zoom with respect to a ThreeJS [_Plane_](https://threejs.org/docs/#api/en/math/Plane) or [_Sphere_](https://threejs.org/docs/#api/en/math/Sphere).
3 |
4 | The aim of this library is to provide a ThreeJS-compatible interface by which a user can interact with a map, either two-dimensonal (a plane) or three-dimensonal (a sphere). The controls provided are meant to behave in the most _natural_ way possible for cartographic navigation; e.g. panning the map by dragging the mouse should keep the same point under the cursor as the map moves.
5 | ## Usage
6 |
7 | ```javascript
8 | import MapControls from '@windfish-studio/three-map-controls'
9 |
10 | const radius = 6.0;
11 | new MapControls( camera, renderer.domElement, {
12 | mode: 'sphere',
13 | target: new Sphere(new THREE.Vector3(0,0,0), radius),
14 | minDistance: 2.0,
15 | maxDistance: 20
16 | });
17 | ```
18 |
19 | Here's a [JSFiddle demo](https://jsfiddle.net/sikanrong/m8c250o2/).
20 |
21 |
22 | ## Change Log
23 |
24 | #### v1.1.3 - Jun 07 2019
25 |
26 | Lots of big changes in the latest version, namely supporting a spherical target mode (e.g. a globe instead of a 2D map).
27 |
28 | As well, v1.1.3 introduces a _targetAreaVisible()_ function which returns the currently-visible portion of the map, in world coordinates.
29 |
30 | In spherical mode, _targetAreaVisible()_ returns a bounding box in spherical coordinates (theta and phi, in radians). Translating these coordinates to degrees will yield a latitude-longitude bounding box.
31 |
32 | These changes are reflected in the tests, which now use the Ava testing framework. As well, the [jsfiddle demo](https://jsfiddle.net/sikanrong/m8c250o2/) has been updated to show off the new functionality.
33 |
34 | #### v1.0.1 - May 19 2018
35 |
36 | Update the project to use ES6-style classes and function scoping features. Removes previous ES6 compatability hacks. Switches out browserify for webpack. Packages demo and test bundles with webpack, moving test
37 | suite to the client.
38 |
39 | Finally adding a universal zoomToFit(mesh) function which optimally fills the screen with a given geometry by dollying the camera towards or away from it.
40 |
41 | Adjust the relationship of pan/dolly Vector math within update().
42 |
43 | ## API
44 |
45 | ### Member Variables
46 |
47 |
48 | #### target: Plane | Sphere
49 | Must be set to instance of threejs Plane or Sphere. *required*
50 | ```javascript
51 | mapControls.target = new Sphere(new Vector3(0,0,0), 5);
52 | ```
53 |
54 | #### mode: string
55 | Must either be set to 'sphere' or 'plane'. *required*
56 | ```javascript
57 | mapControls.mode = 'sphere';
58 | ```
59 |
60 | #### enabled: boolean
61 | Set to false to disable all input events.
62 | ```javascript
63 | mapControls.enabled = true;
64 | ```
65 |
66 | #### min/maxDistance: number
67 | How far you can dolly the camera in and out from the target geometry.
68 | ```javascript
69 | mapControls.minDistance = 1; //probably should never be 0
70 | mapControls.maxDistance = 100;
71 | ```
72 |
73 | #### enableZoom: boolean
74 | Set to false to disable all camera-dolly events.
75 | ```javascript
76 | mapControls.enableZoom = true;
77 | ```
78 |
79 | #### zoomSpeed: number
80 | Set speed of camera dolly; how fast the camera will move towards the target geometry on mousewheel events
81 | ```javascript
82 | mapControls.zoomSpeed = 3.0;
83 | ```
84 |
85 | #### zoomDampingAlpha: number
86 | Set the damping of the dolly movement; makes the camera dolly movement feel smoother.
87 | ```javascript
88 | mapControls.zoomDampingAlpha = 0.1;
89 | ```
90 |
91 | #### enablePan: boolean
92 | Set to false to disable camera pan inputs. In 'sphere' mode, this disables rotation of the camera about the sphere.
93 | ```javascript
94 | mapControls.enablePan = true;
95 | ```
96 |
97 | #### panDampingAlpha: number
98 | Sets the damping of the pan movement; makes camera pan movement feel smoother.
99 | ```javascript
100 | mapControls.panDampingAlpha = 0.2;
101 | ```
102 |
103 | #### enableKeys: boolean
104 | Enable/disable keyboard input
105 | ```javascript
106 | mapControls.enableKeys = true;
107 | ```
108 |
109 | #### keyPanSpeed: number
110 | Define how fast the camera should pan for each keypress. Everything on the screen should move this many pixels per kepress.
111 | ```javascript
112 | mapControls.keyPanSpeed = 12.0;
113 | ```
114 |
115 | #### keys: object
116 | Define the keyboard char-codes which map to each pan movement.
117 | ```javascript
118 | mapControls.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
119 | ```
120 | #### mouseButtons: object
121 | Define the mouse buttons and what action each one is mapped to. (Note: all values are from the threejs MOUSE enumeration)
122 | ```javascript
123 | mapControls.mouseButtons = { ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.LEFT };
124 | ```
125 |
126 | #### Member Functions
127 |
128 | #### getZoomAlpha(void): number
129 | returns current zoom value as a range between 0 and 1; zero represents the camera at mapControls.maxDistance from the target geometry (plane or sphere), and 1 is the camera at mapControls.maxDistance.
130 | ```javascript
131 | mapControls.getZoomAlpha();
132 | ```
133 |
134 | #### update(void): void
135 | Called on each animation frame, updates all of the internal calculations and the camera position/lookAt vectors.
136 | ```javascript
137 | mapControls.update();
138 | ```
139 |
140 | #### targetAreaVisible(void): [Box2](https://threejs.org/docs/#api/en/math/Box2)
141 | Returns the bounding box which defines the currently-visible area of the map, in world coordinates.
142 |
143 | In spherical mode, returns a bounding box in spherical coordinates (Θ and φ; in radians). Translating these coordinates to degrees will yield a latitude-longitude bounding box.
144 | ```javascript
145 | mapControls.update();
146 | ```
147 |
148 | #### zoomToFit(mesh: [Object3D](https://threejs.org/docs/#api/en/core/Object3D), center?: [Vector3](https://threejs.org/docs/#api/en/core/Vector3), dims?: [Vector2](https://threejs.org/docs/#api/en/core/Vector2)): void
149 | Will make the camera reposition itself to best fit **mesh** to the visible screen area.
150 |
151 | The **center** and **dims** parameters refer to the center and dimensions of **mesh** _relative to the projection_. By default the function will attempt to infer these values from the boundingSphere of the mesh. However, this will result in a less exact fit of the object to the visible screen area; e.g. the algorithm will leave bigger margins between the edge of the screen and the mesh.
152 |
153 | ## TODO
154 |
155 | - Add typescript type definitions.
156 | - Add JSDoc documentation
157 |
158 | ## Testing
159 | ```bash
160 | npm run test
161 | ```
162 | ```
163 | TAP version 13
164 | # shouldn't allow initialization if camera intersects plane
165 | ok 1 - shouldn't allow initialization if camera intersects plane
166 | # should correctly determine the camera orientation to the target plane
167 | ok 2 - should correctly determine the camera orientation to the target plane
168 | # should initialize with cam at controls.maxDistance by default
169 | ok 3 - should initialize with cam at controls.maxDistance by default
170 | # shouldn't move from initial position if no input received
171 | ok 4 - shouldn't move from initial position if no input received
172 | # should automatically orient camera towards plane based on starting position
173 | ok 5 - should automatically orient camera towards plane based on starting position
174 | # should lerp camera towards target plane on mousewheel
175 | ok 6 - should lerp camera towards target plane on mousewheel
176 | # should stop zooming at minDistance from target plane
177 | ok 7 - should stop zooming at minDistance from target plane
178 | # reset should revert camera to correct initial position
179 | ok 8 - reset should revert camera to correct initial position
180 | # should zoom into mouse pointer
181 | ok 9 - should zoom into mouse pointer
182 | # mouse should keep same world coordinates under it during camera pan (pan calibration)
183 | ok 10 - mouse should keep same world coordinates under it during camera pan (pan calibration)
184 | # initialZoom parameter should set the default cam position correctly
185 | ok 11 - initialZoom parameter should set the default cam position correctly
186 | # pan calibration should hold true when zoomed in
187 | ok 12 - pan calibration should hold true when zoomed in
188 | # sphere camera should return correct targetVisibleArea
189 | ok 13 - sphere camera should return correct targetVisibleArea
190 | # sphere camera should return correct targetVisibleArea on zoom
191 | ok 14 - sphere camera should return correct targetVisibleArea on zoom
192 | # sphere camera should maintain distance from sphere as it rotates around
193 | ok 15 - sphere camera should maintain distance from sphere as it rotates around
194 | # sphere test rotation calibration; when rotated the point on the sphere should stay under the cursor
195 | ok 16 - sphere test rotation calibration; when rotated the point on the sphere should stay under the cursor
196 | # sphere test zoom out stops at correct distance from sphere
197 | ok 17 - sphere test zoom out stops at correct distance from sphere
198 |
199 | 1..17
200 | # tests 17
201 | # pass 17
202 | # fail 0
203 | ```
204 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import test from 'ava';
3 | import {
4 | Plane,
5 | Sphere,
6 | Raycaster,
7 | Vector3,
8 | Vector2,
9 | PerspectiveCamera
10 | } from 'three';
11 | import MapControls from '../src/three-map-controls.js';
12 |
13 | //test stubs
14 | if(typeof window == 'undefined'){
15 | global.window = require('./stub_dom');
16 | }
17 |
18 | const aspect = window.document.body.clientWidth / window.document.body.clientHeight;
19 | const camera = new PerspectiveCamera(45, aspect, 1, 1000);
20 |
21 | global.inputEvents = {};
22 |
23 | const addEventListenerStub = (key, listener) => {
24 | inputEvents[key] = listener;
25 | };
26 |
27 | window.document.body.addEventListener = addEventListenerStub;
28 |
29 | const defaultOpts = {
30 | target: new Plane(new Vector3(0,0,1), 0),
31 | mode: 'plane',
32 | minDistance: 2.0,
33 | maxDistance: 20
34 | };
35 |
36 | let controls;
37 |
38 | function advanceFrames(frames){
39 | (Array.apply(null, Array(frames))).forEach(function(){
40 | controls.update();
41 | controls.camera.updateMatrixWorld();
42 | });
43 | };
44 |
45 | function currentDistance(){
46 | return Math.abs(controls.target.distanceToPoint(controls.camera.position));
47 | };
48 |
49 | const stub = function(){};
50 |
51 | function EventStub(data){
52 |
53 | this.preventDefault = stub;
54 | this.stopPropagation = stub;
55 |
56 | return Object.assign(this, data);
57 | };
58 |
59 | const sigfigs = 3;
60 | const fastRound = (_n) => {
61 | const p = Math.pow(10, sigfigs);
62 | return (Math.round(_n*p)/p);
63 | };
64 |
65 | const screenCenter = new Vector2(
66 | window.document.body.clientWidth / 2,
67 | window.document.body.clientHeight / 2
68 | );
69 |
70 | var initial_cam_pos = new Vector3(3,2,-20); //what it should be, used for comparisons
71 |
72 | test("shouldn't allow initialization if camera intersects plane", (t) => {
73 | try{
74 | controls = new MapControls( camera, window.document.body, defaultOpts );
75 | t.fail('controls created where camera intersects target plane');
76 | }catch(e){
77 | t.pass('camera cannot intersect target plane on init');
78 | }
79 |
80 | camera.position.copy(initial_cam_pos.clone());
81 | camera.position.z = 1;
82 |
83 | try{
84 | controls = new MapControls( camera, window.document.body, defaultOpts );
85 | t.pass('controls created correctly');
86 | }catch(e){
87 | console.log(e);
88 | t.fail('controls not created successfully');
89 | }
90 |
91 |
92 | });
93 |
94 | test('should correctly determine the camera orientation to the target plane', (t) => {
95 | t.deepEqual(controls._camOrientation.toArray(), [0,0,-1]);
96 | camera.position.z = -1;
97 | controls = new MapControls( camera, window.document.body, defaultOpts );
98 | t.deepEqual(controls._camOrientation.toArray(), [0,0,1]);
99 |
100 | });
101 |
102 | test('should initialize with cam at controls.maxDistance by default', function(t){
103 | var distance = currentDistance();
104 | t.is(distance, controls.maxDistance);
105 | t.is(controls.getZoomAlpha(), controls.initialZoom);
106 |
107 | });
108 |
109 | test("shouldn't move from initial position if no input received", function(t){
110 | advanceFrames(10);
111 | var distance = currentDistance();
112 | t.is(distance, controls.maxDistance);
113 | t.truthy(initial_cam_pos.equals(controls.camera.position));
114 | });
115 |
116 | test("should automatically orient camera towards plane based on starting position", function(t){
117 | var cam_vec = new Vector3();
118 | camera.getWorldDirection(cam_vec);
119 | t.truthy(cam_vec.equals(controls.target.normal));
120 |
121 | });
122 |
123 | test('should lerp camera towards target plane on mousewheel', (t) => {
124 | var lastDistance = currentDistance();
125 | inputEvents.mousewheel(new EventStub({wheelDelta: 1}));
126 | advanceFrames(1000);
127 | var distance = currentDistance();
128 | var expected = lastDistance * Math.pow(0.95, controls.zoomSpeed);
129 | t.is(fastRound(distance), fastRound(expected)); //round both to 3rd decimal place for comparison
130 |
131 | });
132 |
133 | test('should stop zooming at minDistance from target plane', (t) => {
134 | controls.reset();
135 | (Array.apply(null, Array(20))).forEach(function(){
136 | inputEvents.mousewheel(new EventStub({
137 | wheelDelta: 1,
138 | offsetX: window.document.body.clientWidth / 2,
139 | offsetY: window.document.body.clientHeight / 2
140 | }));
141 | });
142 |
143 | advanceFrames(1000);
144 | var distance = currentDistance();
145 | t.is(controls.minDistance, distance);
146 | t.is(controls.getZoomAlpha(), 1);
147 |
148 |
149 | });
150 |
151 | test('reset should revert camera to correct initial position', function(t){
152 | controls.reset();
153 | t.truthy(initial_cam_pos.equals(controls.camera.position));
154 |
155 | });
156 |
157 | test('should zoom into mouse pointer', function(t){ //e.g. should act like maps controls.
158 | (Array.apply(null, Array(30))).forEach(function(){
159 | inputEvents.mousewheel(new EventStub({
160 | wheelDelta: 1,
161 | offsetX: 400,
162 | offsetY: 300
163 | }));
164 | });
165 |
166 | advanceFrames(1000);
167 | var tolerance = Math.pow(10, -sigfigs);
168 |
169 | const desired = new Vector3(
170 | 10.812787997105476,
171 | 5.34833686125601,
172 | -1.8118972640060278
173 | );
174 |
175 | var delta = Math.abs(new Vector3().subVectors(desired, controls.camera.position).length());
176 |
177 | t.truthy( delta <= tolerance );
178 |
179 | });
180 |
181 |
182 | var testPanCalibration = function(t, move, tol){
183 |
184 | move = move || new Vector2(10, 10);
185 |
186 | const intersectMouse = function(x, y){
187 | var mouse_pos = new Vector2(
188 | ( x / window.document.body.clientWidth ) * 2 - 1,
189 | - ( y / window.document.body.clientHeight ) * 2 + 1); //NDC
190 |
191 | const raycaster = new Raycaster();
192 | raycaster.setFromCamera(mouse_pos, controls.camera);
193 |
194 | const intersection = new Vector3();
195 |
196 | switch(controls.mode){
197 | case 'plane':
198 | raycaster.ray.intersectPlane(controls.target, intersection);
199 | break;
200 | case 'sphere':
201 | raycaster.ray.intersectSphere(controls.target, intersection);
202 | break;
203 | }
204 |
205 | return intersection;
206 | };
207 |
208 |
209 | //push mouse button down..
210 | inputEvents.mousedown(new EventStub({
211 | offsetX: screenCenter.x,
212 | offsetY: screenCenter.y,
213 | button: controls.mouseButtons.PAN
214 | }));
215 |
216 | var first_campos = controls.camera.position.clone();
217 | var first_intersect = intersectMouse(screenCenter.x, screenCenter.y);
218 |
219 | const newMouse = screenCenter.clone().add(move);
220 |
221 | inputEvents.mousemove(new EventStub({
222 | offsetX: newMouse.x,
223 | offsetY: newMouse.y
224 | }));
225 |
226 | advanceFrames(1000);
227 |
228 | var second_campos = controls.camera.position.clone();
229 | var second_intersect = intersectMouse(newMouse.x, newMouse.y);
230 |
231 | //second_intersect should be the same as first_intersect; e.g. the point in world-space under the mouse should not
232 | //have changed during pan operation
233 | const delta = Math.abs(new Vector3().subVectors(second_intersect, first_intersect).length());
234 | t.truthy(delta <= (tol || 0.0001));
235 | t.notDeepEqual(first_campos.toArray(), second_campos.toArray());
236 |
237 | inputEvents.mouseup();
238 | };
239 |
240 | test('mouse should keep same world coordinates under it during camera pan (pan calibration)', function(t){
241 | controls.reset();
242 | testPanCalibration(t);
243 | });
244 |
245 |
246 | test('initialZoom parameter should set the default cam position correctly', function(t){
247 | controls.initialZoom = 0.5;
248 | controls.reset();
249 |
250 | var correct_z = initial_cam_pos.z + ((controls.maxDistance - controls.minDistance) / 2);
251 | t.is(controls.camera.position.z, correct_z);
252 |
253 | //try max zoom
254 | controls.initialZoom = 1;
255 | controls.reset();
256 |
257 | var correct_z = -controls.minDistance;
258 | t.is(controls.camera.position.z, correct_z);
259 |
260 | });
261 |
262 | test('pan calibration should hold true when zoomed in', function(t){
263 | controls.reset();
264 | controls.camera.updateWorldMatrix();
265 | testPanCalibration(t);
266 | });
267 |
268 | test('sphere camera should return correct targetVisibleArea', (t) => {
269 | controls.dispose();
270 | controls = undefined;
271 |
272 | camera.position.set(0,0,100);
273 | camera.lookAt(new Vector3(0,0,0));
274 | camera.updateWorldMatrix();
275 | controls = new MapControls( camera, window.document.body, {
276 | target: new Sphere(new Vector3(0,0,0), 10),
277 | mode: 'sphere',
278 | minDistance: 2,
279 | maxDistance: 100
280 | });
281 |
282 | const bbox = controls.targetAreaVisible();
283 | const bbox_ar = Array.prototype.concat.apply([], [bbox.min, bbox.max].map(_v => {return _v.toArray()}));
284 | t.deepEqual(
285 | bbox_ar,
286 | [-Math.PI/2, -Math.PI/2, Math.PI/2, Math.PI/2]
287 | );
288 | });
289 |
290 | test('sphere camera should return correct targetVisibleArea on zoom', (t) => {
291 |
292 | (Array.apply(null, Array(20))).forEach(function(){
293 | inputEvents.mousewheel(new EventStub({
294 | wheelDelta: 1,
295 | offsetX: window.document.body.clientWidth / 2,
296 | offsetY: window.document.body.clientHeight / 2
297 | }));
298 | });
299 |
300 | advanceFrames(1000);
301 |
302 | const bbox = controls.targetAreaVisible();
303 | const bbox_ar = Array.prototype.concat.apply([], [bbox.min, bbox.max].map(_v => {return _v.toArray()}));
304 | t.deepEqual(
305 | bbox_ar,
306 | [
307 | -0.14727593328821142,
308 | -0.08284271247461894,
309 | 0.14727593328821142,
310 | 0.08284271247461894
311 | ]
312 | );
313 | });
314 |
315 | test('sphere camera should maintain distance from sphere as it rotates around', (t) => {
316 |
317 | t.is(currentDistance(), controls.minDistance);
318 | const first_campos = controls.camera.position.clone();
319 |
320 | inputEvents.mousedown(new EventStub({
321 | offsetX: screenCenter.x,
322 | offsetY: screenCenter.y,
323 | button: controls.mouseButtons.PAN
324 | }));
325 |
326 | inputEvents.mousemove(new EventStub({
327 | offsetX: screenCenter.x + 10,
328 | offsetY: screenCenter.y + 10
329 | }));
330 |
331 | inputEvents.mouseup();
332 |
333 | advanceFrames(1000);
334 |
335 | t.is(fastRound(currentDistance()), fastRound(controls.minDistance));
336 | t.notDeepEqual(first_campos.toArray(), controls.camera.position.toArray());
337 |
338 | });
339 |
340 | test('sphere test rotation calibration; when rotated the point on the sphere should stay under the cursor', (t) => {
341 | controls.camera.updateWorldMatrix();
342 | testPanCalibration(t, new Vector2(10, 10), 0.001);
343 | });
344 |
345 | test('sphere test zoom out stops at correct distance from sphere', (t) => {
346 | (Array.apply(null, Array(20))).forEach(function(){
347 | inputEvents.mousewheel(new EventStub({
348 | wheelDelta: -1,
349 | offsetX: window.document.body.clientWidth / 2,
350 | offsetY: window.document.body.clientHeight / 2
351 | }));
352 | });
353 |
354 | advanceFrames(1000);
355 |
356 | t.is(fastRound(controls.maxDistance), fastRound(currentDistance()));
357 | });
358 |
--------------------------------------------------------------------------------
/dist/three-map-controls.js:
--------------------------------------------------------------------------------
1 | !function(t){var e={};function s(i){if(e[i])return e[i].exports;var a=e[i]={i:i,l:!1,exports:{}};return t[i].call(a.exports,a,a.exports,s),a.l=!0,a.exports}s.m=t,s.c=e,s.d=function(t,e,i){s.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:i})},s.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},s.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return s.d(e,"a",e),e},s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.p="/",s(s.s=1)}([function(t,e,s){t.exports=s(3)(0)},function(t,e,s){"use strict";s.r(e);var i=s(0);if("undefined"==typeof window){s(2)}e.default=class extends i.EventDispatcher{constructor(t,e,s){super(),this.camera=t,this.domElement=void 0!==e?e:window.document.body,this.enabled=!0,this.target,this.minDistance=1,this.maxDistance=100,this.enableZoom=!0,this.zoomSpeed=6,this.zoomDampingAlpha=.1,this.initialZoom=0,this.enablePan=!0,this.keyPanSpeed=50,this.keyZoomSpeed=this.zoomSpeed,this.panDampingAlpha=.1,this.enableKeys=!0,this.keys={PAN_LEFT:"ArrowLeft",PAN_UP:"ArrowUp",PAN_RIGHT:"ArrowRight",PAN_BOTTOM:"ArrowDown",ZOOM_IN:"]",ZOOM_OUT:"["},this.mouseButtons={ZOOM:i.MOUSE.MIDDLE,PAN:i.MOUSE.LEFT},Object.assign(this,s);let a=!1;if(void 0===this.mode)throw new Error("'mode' option must be set to either 'plane' or 'sphere'");switch(this.mode){case"plane":a=void 0!==this.target.normal&&void 0!==this.target.constant;break;case"sphere":a=void 0!==this.target.center&&void 0!==this.target.radius}if(!a)throw new Error("'target' option must be an instance of type THREE.Plane or THREE.Sphere");this._eventListeners={contextmenu:this._onContextMenu.bind(this),mousedown:this._onMouseDown.bind(this),mousewheel:this._onMouseWheel.bind(this),MozMousePixelScroll:this._onMouseWheel.bind(this),touchstart:this._onTouchStart.bind(this),touchend:this._onTouchEnd.bind(this),touchmove:this._onTouchMove.bind(this),keydown:this._onKeyDown.bind(this),mouseover:this._onMouseOver.bind(this),mouseout:this._onMouseOut.bind(this),mousemove:this._onMouseMove.bind(this),mouseup:this._onMouseUp.bind(this)},this._init()}_init(){if(this.target0=this.target.clone(),this.position0=this.camera.position.clone(),this.zoom0=this.camera.zoom,this._changeEvent={type:"change"},this._startEvent={type:"start"},this._endEvent={type:"end"},this._STATES={NONE:-1,DOLLY:1,PAN:2,TOUCH_DOLLY:4,TOUCH_PAN:5},0==this.target.distanceToPoint(this.camera.position))throw new Error("ORIENTATION_UNKNOWABLE: initial Camera position cannot intersect target plane.");this._state=this._STATES.NONE,this._mouse=new i.Vector2,this._finalTargetDistance=0,this._currentTargetDistance=0,this._panTarget=new i.Vector3(0,0,0),this._panCurrent=new i.Vector3(0,0,0),this._minZoomPosition=new i.Vector3,this._maxZoomPosition=new i.Vector3,this._panStart=new i.Vector2,this._panEnd=new i.Vector2,this._panDelta=new i.Vector2,this._dollyStart=new i.Vector2,this._dollyEnd=new i.Vector2,this._dollyDelta=new i.Vector2,this._camOrientation=new i.Vector2,this._zoomAlpha,this._screenWorldXform=Math.tan(this.camera.fov/2*Math.PI/180),this._straightDollyTrack(),this.camera.position.lerpVectors(this._minZoomPosition,this._maxZoomPosition,this.initialZoom),this._finalTargetDistance=this._currentTargetDistance=Math.abs(this.target.distanceToPoint(this.camera.position));const t=this._intersectCameraTarget();this.camera.lookAt(t.intersection),this._camOrientation=t.ray.direction.clone().normalize(),this._updateZoomAlpha(),["contextmenu","mousedown","mousewheel","MozMousePixelScroll","touchstart","touchend","touchmove","mouseover","mouseout","keydown"].forEach(t=>{this.domElement.addEventListener(t,this._eventListeners[t],!1)}),"CANVAS"!=this.domElement.tagName||this.domElement.getAttribute("tabindex")||this.domElement.setAttribute("tabindex","1"),this.update()}_intersectCameraTarget(){let t,e=new i.Vector3;switch(this.mode){case"plane":const s=new i.Vector3;this.target.projectPoint(this.camera.position,s),(t=new i.Ray(this.camera.position,(new i.Vector3).subVectors(s,this.camera.position).normalize())).intersectPlane(this.target,e);break;case"sphere":(t=new i.Ray(this.camera.position,(new i.Vector3).subVectors(this.target.center,this.camera.position).normalize())).intersectSphere(this.target,e)}return{intersection:e,ray:t}}_straightDollyTrack(){this._updateDollyTrack(this._intersectCameraTarget().ray)}getZoomAlpha(){return this._zoomAlpha}reset(){this.target.copy(this.target0),this.camera.position.copy(this.position0),this.camera.zoom=this.zoom0,this.camera.updateProjectionMatrix(),this._init(),this.dispatchEvent(this._changeEvent),this.update(),this._state=this._STATES.NONE}update(){const t=new i.Vector3,e=new i.Vector3,s=this.camera.position;switch(e.copy(this._panCurrent),this._panCurrent.lerp(this._panTarget,this.panDampingAlpha),t.subVectors(this._panCurrent,e),this.mode){case"plane":this._maxZoomPosition.add(t),this._minZoomPosition.add(t);break;case"sphere":const s=new i.Vector3,a=new i.Quaternion;a.setFromAxisAngle(s.setFromMatrixColumn(this.camera.matrix,1),t.x),this._maxZoomPosition.applyQuaternion(a),this._minZoomPosition.applyQuaternion(a),a.setFromAxisAngle(s.setFromMatrixColumn(this.camera.matrix,0),t.y),this._maxZoomPosition.applyQuaternion(a),this._minZoomPosition.applyQuaternion(a),a.setFromAxisAngle(new i.Vector3(0,1,0),t.z),this._maxZoomPosition.applyQuaternion(a),this._minZoomPosition.applyQuaternion(a)}s.lerpVectors(this._minZoomPosition,this._maxZoomPosition,this._updateZoomAlpha()),"sphere"==this.mode&&this.camera.lookAt(this.target.center)}dispose(){Object.keys(this._eventListeners).forEach(t=>{this.domElement.removeEventListener(t,this._eventListeners[t],!1)})}zoomToFit(t,e,s){if(void 0===e&&(e=t.geometry.boundingSphere.center.clone()),e=t.localToWorld(e.clone()),void 0===s){const e=2*t.geometry.boundingSphere.radius;s=new i.Vector2(e,e)}switch(this.mode){case"plane":this._panTarget.copy(e),this._panCurrent.copy(this._intersectCameraTarget().intersection);break;case"sphere":const t=this._sphericalCoordinatesFrom(e),s=this._sphericalCoordinatesFrom(this.camera.position),a=(new i.Vector2).subVectors(t,s);Math.abs(a.x)>Math.PI&&(a.x=-Math.abs(a.x)/a.x*(2*Math.PI-Math.abs(a.x))),this._panTarget.add(new i.Vector3(0,-a.y,a.x))}this._straightDollyTrack();const a=this.camera.fov*(Math.PI/180),o=2*Math.atan(Math.tan(a/2)*this.camera.aspect),n=s.x/s.y;this._finalTargetDistance=(n>this.camera.aspect?s.x:s.y)/2/Math.tan((n>this.camera.aspect?o:a)/2)}targetAreaVisible(){let t,e,s,a;switch(this.mode){case"plane":var o=new i.Ray(this.camera.position,this._camOrientation).distanceToPlane(this.target);a=this.camera.position.clone(),s=(e=this._screenWorldXform*o)*this.camera.aspect,t=new i.Box2(new i.Vector2(a.x-s,a.y-e),new i.Vector2(a.x+s,a.y+e));break;case"sphere":const n=(new i.Vector3).subVectors(this.target.center,this.camera.position);a=this._sphericalCoordinatesFrom(this.camera.position);const h=Math.PI/2,r=n.length();e=this._screenWorldXform*(r/this.target.radius-1),e=Math.min(e,h);const c=this.target.radius*Math.cos(a.y-h);s=e*this.camera.aspect*(this.target.radius/c),s=Math.min(s,h),t=new i.Box2(new i.Vector2(a.x-s-h,a.y-e-h),new i.Vector2(a.x+s-h,a.y+e-h)),["min","max"].forEach(e=>{t[e].x=t[e].x>Math.PI?-2*Math.PI+t[e].x:t[e].x})}return t}targetAreaVisibleDeg(){let t=this.targetAreaVisible();return"sphere"==this.mode&&(t.min.x=t.min.x*(180/Math.PI),t.min.y=t.min.y*(180/Math.PI),t.max.x=t.max.x*(180/Math.PI),t.max.y=t.max.y*(180/Math.PI)),t}_sphericalCoordinatesFrom(t){const e=(new i.Vector3).subVectors(this.target.center,t),s=new i.Vector3(e.x,0,e.z),a=(new i.Vector3,new i.Vector2(s.angleTo(new i.Vector3(1,0,0)),e.angleTo(new i.Vector3(0,1,0))));return a.x=e.z>0?2*Math.PI-a.x:a.x,a}_updateZoomAlpha(){this._finalTargetDistance=Math.max(this.minDistance,Math.min(this.maxDistance,this._finalTargetDistance));var t=this._currentTargetDistance-this._finalTargetDistance,e=this.zoomDampingAlpha;return this._currentTargetDistance-=t*e,this._zoomAlpha=Math.abs(Math.round(1e5*(1-(this._currentTargetDistance-this.minDistance)/(this.maxDistance-this.minDistance)))/1e5),this._zoomAlpha}_updateDollyTrack(t){let e=new i.Vector3;switch(this.mode){case"plane":t.intersectPlane(this.target,e);break;case"sphere":t.intersectSphere(this.target,e)}e&&(this._maxZoomPosition.addVectors(e,(new i.Vector3).subVectors(this.camera.position,e).normalize().multiplyScalar(this.minDistance)),this._minZoomPosition.copy(this._calculateMinZoom(this.camera.position,e)),this._finalTargetDistance=this._currentTargetDistance=e.clone().sub(this.camera.position).length())}_getZoomScale(t){return t=t||this.zoomSpeed,Math.pow(.95,t)}_panLeft(t,e){var s=new i.Vector3;switch(this.mode){case"sphere":s.set(-t,0,0);break;case"plane":s.setFromMatrixColumn(e,0),s.multiplyScalar(-t)}this._panTarget.add(s)}_panUp(t,e){var s=new i.Vector3;switch(this.mode){case"sphere":s.set(0,-t,0);break;case"plane":s.setFromMatrixColumn(e,1),s.multiplyScalar(t)}this._panTarget.add(s)}_pan(t,e){var s,a=this.domElement,o=new i.Ray(this.camera.position,this._camOrientation);switch(this.mode){case"plane":s=this._screenWorldXform*o.distanceToPlane(this.target);break;case"sphere":const t=(new i.Vector3).subVectors(this.camera.position,this.target.center);s=this._screenWorldXform*(t.length()/this.target.radius-1)}this._panLeft(2*t*s/a.clientHeight,this.camera.matrix),this._panUp(2*e*s/a.clientHeight,this.camera.matrix)}_dollyIn(t){this._cameraOfKnownType()?this._finalTargetDistance/=t:(console.warn("WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled."),this.enableZoom=!1)}_dollyOut(t){this._cameraOfKnownType()?this._finalTargetDistance*=t:(console.warn("WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled."),this.enableZoom=!1)}_cameraOfKnownType(){return"PerspectiveCamera"===this.camera.type}_handleUpdateDollyTrackMouse(t){var e=this._mouse.clone();if(this._mouse.set(t.offsetX/this.domElement.clientWidth*2-1,-t.offsetY/this.domElement.clientHeight*2+1),!e.equals(this._mouse)){var s=new i.Raycaster;s.setFromCamera(this._mouse,this.camera),this._updateDollyTrack(s.ray)}}_handleMouseDownDolly(t){this._handleUpdateDollyTrackMouse(t),this._dollyStart.set(t.offsetX,t.offsetY)}_handleMouseDownPan(t){this._panStart.set(t.offsetX,t.offsetY)}_handleMouseMoveDolly(t){this._handleUpdateDollyTrackMouse(t),this._dollyEnd.set(t.offsetX,t.offsetY),this._dollyDelta.subVectors(this._dollyEnd,this._dollyStart),this._dollyDelta.y>0?this._dollyIn(this._getZoomScale()):this._dollyDelta.y<0&&this._dollyOut(this._getZoomScale()),this._dollyStart.copy(this._dollyEnd),this.update()}_handleMouseMovePan(t){this._panEnd.set(t.offsetX,t.offsetY),this._panDelta.subVectors(this._panEnd,this._panStart),this._pan(this._panDelta.x,this._panDelta.y),this._panStart.copy(this._panEnd),this.update()}_handleMouseUp(t){}_calculateMinZoom(t,e){return e.clone().add(t.clone().sub(e).normalize().multiplyScalar(this.maxDistance))}_handleMouseWheel(t){this._handleUpdateDollyTrackMouse(t);var e=0;void 0!==t.wheelDelta?e=t.wheelDelta:void 0!==t.detail&&(e=-t.detail),e>0?this._dollyOut(this._getZoomScale()):e<0&&this._dollyIn(this._getZoomScale()),this.update()}_handleKeyDown(t){switch(t.key){case this.keys.PAN_UP:this._pan(0,this.keyPanSpeed),this.update();break;case this.keys.PAN_BOTTOM:this._pan(0,-this.keyPanSpeed),this.update();break;case this.keys.PAN_LEFT:this._pan(this.keyPanSpeed,0),this.update();break;case this.keys.PAN_RIGHT:this._pan(-this.keyPanSpeed,0),this.update();break;case this.keys.ZOOM_IN:this._dollyIn(this._getZoomScale(this.keyZoomSpeed)),this.update();break;case this.keys.ZOOM_OUT:this._dollyOut(this._getZoomScale(this.keyZoomSpeed)),this.update()}}_handleUpdateDollyTrackTouch(t){var e=new i.Vector2,s=t.touches[0].pageX-t.touches[1].pageX,a=t.touches[0].pageY-t.touches[1].pageY;e.x=t.touches[0].pageX+s/2,e.y=t.touches[0].pageY+a/2;var o=new i.Vector2;o.x=e.x/domElement.clientWidth*2-1,o.y=-e.y/domElement.clientHeight*2+1,this._updateDollyTrack(o)}_handleTouchStartDolly(t){this._handleUpdateDollyTrackTouch(t);var e=t.touches[0].pageX-t.touches[1].pageX,s=t.touches[0].pageY-t.touches[1].pageY,i=Math.sqrt(e*e+s*s);this._dollyStart.set(0,i)}_handleTouchStartPan(t){this._panStart.set(t.touches[0].pageX,t.touches[0].pageY)}_handleTouchMoveDolly(t){this._handleUpdateDollyTrackTouch(t);var e=t.touches[0].pageX-t.touches[1].pageX,s=t.touches[0].pageY-t.touches[1].pageY,i=Math.sqrt(e*e+s*s);this._dollyEnd.set(0,i),this._dollyDelta.subVectors(this._dollyEnd,this._dollyStart),this._dollyDelta.y>0?this._dollyOut(this._getZoomScale()):this._dollyDelta.y<0&&this._dollyIn(this._getZoomScale()),this._dollyStart.copy(this._dollyEnd),this.update()}_handleTouchMovePan(t){this._panEnd.set(t.touches[0].pageX,t.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()}_handleTouchEnd(t){}_onMouseDown(t){if(!1!==this.enabled){if(t.preventDefault(),t.button===this.mouseButtons.ZOOM){if(!1===this.enableZoom)return;this._handleMouseDownDolly(t),this._state=this._STATES.DOLLY}else if(t.button===this.mouseButtons.PAN){if(!1===this.enablePan)return;this._handleMouseDownPan(t),this._state=this._STATES.PAN}this._state!==this._STATES.NONE&&(this.domElement.addEventListener("mousemove",this._eventListeners.mousemove,!1),this.domElement.addEventListener("mouseup",this._eventListeners.mouseup,!1),this.dispatchEvent(this._startEvent))}}_onMouseMove(t){if(!1!==this.enabled)if(t.preventDefault(),this._state===this._STATES.DOLLY){if(!1===this.enableZoom)return;this._handleMouseMoveDolly(t)}else if(this._state===this._STATES.PAN){if(!1===this.enablePan)return;this._handleMouseMovePan(t)}}_onMouseUp(t){!1!==this.enabled&&(this._handleMouseUp(t),this.domElement.removeEventListener("mousemove",this._eventListeners.mousemove,!1),this.domElement.removeEventListener("mouseup",this._eventListeners.mouseup,!1),this.dispatchEvent(this._endEvent),this._state=this._STATES.NONE)}_onMouseWheel(t){!1!==this.enabled&&!1!==this.enableZoom&&this._state===this._STATES.NONE&&(t.preventDefault(),t.stopPropagation(),this._handleMouseWheel(t),this.dispatchEvent(this._startEvent),this.dispatchEvent(this._endEvent))}_onKeyDown(t){!1!==this.enabled&&!1!==this.enableKeys&&!1!==this.enablePan&&this._handleKeyDown(t)}_onTouchStart(t){if(!1!==this.enabled){switch(t.touches.length){case 1:if(!1===this.enablePan)return;this._handleTouchStartPan(t),this._state=this._STATES.TOUCH_PAN;break;case 2:if(!1===this.enableZoom)return;this._handleTouchStartDolly(t),this._state=this._STATES.TOUCH_DOLLY;break;default:this._state=this._STATES.NONE}this._state!==this._STATES.NONE&&this.dispatchEvent(this._startEvent)}}_onTouchMove(t){if(!1!==this.enabled)switch(t.preventDefault(),t.stopPropagation(),t.touches.length){case 1:if(!1===this.enablePan)return;if(this._state!==this._STATES.TOUCH_PAN)return;this._handleTouchMovePan(t);break;case 2:if(!1===this.enableZoom)return;if(this._state!==this._STATES.TOUCH_DOLLY)return;this._handleTouchMoveDolly(t);break;default:this._state=this._STATES.NONE}}_onTouchEnd(t){!1!==this.enabled&&(this._handleTouchEnd(t),this.dispatchEvent(this._endEvent),this._state=this._STATES.NONE)}_onContextMenu(t){t.preventDefault()}_onMouseOver(t){return this.domElement.focus(),!1}_onMouseOut(t){return this.domElement.blur(),!1}}},function(t,e){t.exports={console:{log:function(){}},document:{body:{clientWidth:1920,clientHeight:1080,addEventListener:function(){},removeEventListener:function(){}}}}},function(t,e){t.exports=vendor}]);
2 | //# sourceMappingURL=three-map-controls.js.map
--------------------------------------------------------------------------------
/src/demo.js:
--------------------------------------------------------------------------------
1 | import {
2 | Scene,
3 | Vector3,
4 | Vector2,
5 | Quaternion,
6 | Box2,
7 | Box3,
8 | PerspectiveCamera,
9 | SphereBufferGeometry,
10 | PlaneBufferGeometry,
11 | BufferGeometry,
12 | CubeGeometry,
13 | PointsMaterial,
14 | MeshBasicMaterial,
15 | MeshNormalMaterial,
16 | Color,
17 | DoubleSide,
18 | VertexColors,
19 | Mesh,
20 | Points,
21 | Raycaster,
22 | Float32BufferAttribute,
23 | Plane,
24 | Sphere,
25 | WebGLRenderer
26 | } from 'three';
27 | import MapControls from './three-map-controls.js';
28 |
29 | const SPHERE_RADIUS = 10;
30 |
31 | class MapControlsDemo {
32 | constructor (mode) {
33 | this.container = document.body;
34 | this.scene = new Scene();
35 | this.renderer = null;
36 | this.meshes = [];
37 | this.dims = 10;
38 | this.selectedObjectTween = 0;
39 | this.selectedObject = null;
40 | this.controls;
41 | this.mode;
42 |
43 | this.debugCamViewInterval;
44 |
45 | this.camViewMesh;
46 | this.camViewLines;
47 |
48 | this.init();
49 | this.setMode(mode);
50 | this.animate();
51 | }
52 |
53 | setMode(mode) {
54 | this.deselect();
55 | this.mode = mode;
56 | const links = {
57 | sphere: document.getElementById('sphere-link'),
58 | plane: document.getElementById('plane-link')
59 | };
60 |
61 | links[this.mode].style.display = 'none';
62 | links[(this.mode == 'plane')? 'sphere' : 'plane'].style.display = 'inline-block';
63 |
64 | this.meshes.concat([this.camViewLines, this.camViewMesh]).forEach((_m) => {
65 | if(_m === undefined){
66 | return;
67 | }
68 |
69 | this.scene.remove(_m);
70 | _m.geometry.dispose();
71 | });
72 |
73 | this.camViewLines = this.camViewMesh = undefined;
74 |
75 | switch(this.mode){
76 | case 'sphere':
77 | this.initSphere();
78 | break;
79 | case 'plane':
80 | this.initPlane();
81 | break;
82 | }
83 | }
84 |
85 | initSphere(){
86 |
87 | var camera = new PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
88 | camera.position.z = 40;
89 | this.controls = new MapControls( camera, this.renderer.domElement, {
90 | target: new Sphere(new Vector3(0,0,0), SPHERE_RADIUS),
91 | mode: 'sphere',
92 | minDistance: 1,
93 | maxDistance: camera.position.z
94 | });
95 |
96 | const colors = [];
97 |
98 | const geometry = new SphereBufferGeometry(SPHERE_RADIUS, this.dims, this.dims);
99 | geometry.computeBoundingSphere();
100 |
101 | const vertices = geometry.getAttribute('position').array;
102 | for(var i = 0; i < vertices.length; i += 3){
103 | var color = new Color();
104 | var vert = new Vector3(vertices[i], vertices[i+1], vertices[i+2]);
105 |
106 | color.setRGB(
107 | ( vert.x / SPHERE_RADIUS ) + 0.5,
108 | ( vert.y / SPHERE_RADIUS ) + 0.5,
109 | ( vert.z / SPHERE_RADIUS ) + 0.5
110 | );
111 |
112 | colors.push( color.r, color.g, color.b );
113 | }
114 |
115 | geometry.addAttribute( 'color', new Float32BufferAttribute( Float32Array.from(colors), 3 ) );
116 |
117 | const points = new Points(
118 | geometry,
119 | new PointsMaterial( { size: 1, vertexColors: VertexColors } )
120 | );
121 |
122 | this.scene.add( points );
123 | this.meshes.push( points );
124 |
125 | const polys = new Mesh(
126 | geometry,
127 | new MeshBasicMaterial({
128 | vertexColors: VertexColors,
129 | transparent: true,
130 | opacity: 0.2
131 |
132 | })
133 | );
134 |
135 | polys.userData.selectable = true;
136 |
137 | this.meshes.push( polys );
138 | this.scene.add( polys );
139 |
140 | const lines = new Mesh(
141 | geometry,
142 | new MeshBasicMaterial({
143 | vertexColors: VertexColors,
144 | wireframe: true
145 | })
146 | );
147 |
148 | this.meshes.push( lines );
149 | this.scene.add( lines );
150 |
151 | }
152 |
153 | toggleDebugCamView(e){
154 | if(!e.target.checked){
155 | clearInterval(this.debugCamViewInterval);
156 | this.scene.remove( this.camViewMesh );
157 | this.scene.remove( this.camViewLines );
158 | this.camViewMesh.geometry.dispose();
159 | this.camViewLines.geometry.dispose();
160 | this.camViewLines = this.camViewMesh = undefined;
161 | return true;
162 | }
163 |
164 | this.debugCamViewInterval = setInterval(() => {
165 | const bbox = this.controls.targetAreaVisible();
166 | console.log(`${bbox.min.x}, ${bbox.min.y}, ${bbox.max.x}, ${bbox.max.y}`);
167 |
168 | let geometry, position;
169 | position = new Vector3(0,0,0);
170 |
171 | switch (this.mode) {
172 | case 'sphere':
173 | let phidelta = Math.abs(bbox.max.x - bbox.min.x);
174 | if(phidelta > Math.PI){
175 | phidelta = Math.abs((bbox.max.x + Math.PI*2) - bbox.min.x);
176 | }
177 | geometry = new SphereBufferGeometry(SPHERE_RADIUS, this.dims, this.dims,
178 | bbox.min.x + Math.PI/2, //phistart
179 | phidelta, //philength
180 | -bbox.max.y + Math.PI/2, //thetastart
181 | Math.abs(bbox.max.y - bbox.min.y) //thetalength
182 | );
183 | break;
184 | case 'plane':
185 |
186 | geometry = new PlaneBufferGeometry(
187 | (bbox.max.x - bbox.min.x),
188 | (bbox.max.y - bbox.min.y),
189 | this.dims, this.dims
190 | );
191 |
192 | position.copy(this.controls.camera.position);
193 | position.z = 0;
194 |
195 | break;
196 | }
197 |
198 | if(this.camViewMesh == undefined){
199 | this.camViewMesh = new Mesh(
200 | geometry,
201 | new MeshBasicMaterial({
202 | color: new Color(1, 0, 0),
203 | side: DoubleSide,
204 | transparent: true,
205 | opacity: 0.5
206 | })
207 | );
208 |
209 | this.camViewLines = new Mesh(
210 | geometry,
211 | new MeshBasicMaterial({
212 | color: new Color(1, 0, 0),
213 | wireframe: true
214 | })
215 | );
216 |
217 | this.scene.add( this.camViewMesh );
218 | this.scene.add( this.camViewLines );
219 | }else{
220 |
221 | this.camViewMesh.geometry.copy(geometry);
222 | this.camViewLines.geometry.copy(geometry);
223 |
224 | geometry.dispose();
225 | }
226 |
227 | this.camViewMesh.geometry.computeBoundingSphere();
228 | this.camViewMesh.position.copy(position);
229 | this.camViewLines.position.copy(position);
230 |
231 | }, 1000);
232 | }
233 |
234 | initPlane(){
235 |
236 | var camera = new PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
237 | camera.position.z = 20;
238 | this.controls = new MapControls( camera, this.renderer.domElement, {
239 | target: new Plane(new Vector3(0,0,1), 0),
240 | mode: 'plane',
241 | minDistance: 2.0,
242 | maxDistance: 20
243 | });
244 |
245 | var offset = 3;
246 |
247 | for(var x = 0; x < this.dims; x++){
248 | for(var y = 0; y < this.dims; y++){
249 | var geometry = new CubeGeometry(1, 1, 1);
250 | var material = new MeshNormalMaterial();
251 |
252 | var mesh = new Mesh( geometry, material );
253 | mesh.position.x += ((-0.5 * this.dims * offset) + (x * offset));
254 | mesh.position.y += ((-0.5 * this.dims * offset) + (y * offset));
255 | mesh.userData.selectable = true;
256 | this.meshes.push( mesh );
257 | this.scene.add( mesh );
258 |
259 | mesh.geometry.computeBoundingSphere();
260 | }
261 | }
262 | }
263 |
264 | init () {
265 | this.renderer = new WebGLRenderer();
266 | this.renderer.setPixelRatio( window.devicePixelRatio );
267 | this.renderer.setSize( window.innerWidth, window.innerHeight );
268 |
269 | this.container.appendChild( this.renderer.domElement );
270 |
271 | window.addEventListener( 'resize', ()=>{
272 | this.onWindowResize();
273 | }, false );
274 |
275 | this.renderer.domElement.addEventListener( 'mousedown', (_e) => {this.pick(_e)} );
276 | this.renderer.domElement.addEventListener( 'dblclick', (_e) => {this.zoomTo(_e)} );
277 |
278 | const cb = document.getElementById('toggleCamDebug');
279 | cb.addEventListener('click', this.toggleDebugCamView.bind(this));
280 | }
281 |
282 | zoomTo(){
283 | if(!this.selectedObject)
284 | return;
285 |
286 | switch(this.mode){
287 | case 'sphere':
288 | this.controls.zoomToFit(
289 | this.selectedObject,
290 | this.selectedObject.userData.zoom.center,
291 | this.selectedObject.userData.zoom.dims
292 | );
293 | break;
294 | case 'plane':
295 | this.controls.zoomToFit( this.selectedObject );
296 | break;
297 | }
298 | }
299 |
300 | deselect() {
301 | if(this.selectedObject){
302 | switch (this.mode) {
303 | case 'sphere':
304 | this.selectedObject.parent.remove(this.selectedObject);
305 | this.selectedObject.geometry.dispose();
306 | this.selectedObject.material.dispose();
307 | break;
308 | case 'plane':
309 | this.selectedObject.material.dispose();
310 | this.selectedObject.material = new MeshNormalMaterial();
311 | break;
312 | }
313 | }
314 |
315 | this.selectedObject = null;
316 | }
317 |
318 | pick(event){
319 | this.deselect();
320 |
321 | const mouse = new Vector2();
322 |
323 | mouse.x = ( event.clientX / this.renderer.domElement.clientWidth ) * 2 - 1;
324 | mouse.y = - ( event.clientY / this.renderer.domElement.clientHeight ) * 2 + 1;
325 |
326 | const raycaster = new Raycaster();
327 |
328 | raycaster.setFromCamera(mouse, this.controls.camera);
329 |
330 | // calculate objects intersecting the picking ray
331 | const intersect = (raycaster.intersectObjects( this.scene.children, true )).filter(_int => {
332 | return _int.object.userData.selectable;
333 | })[0];
334 |
335 | if(intersect !== undefined && intersect.face){
336 | switch (this.mode) {
337 | case 'sphere':
338 | //create a new selectedObject from the triangle that was raycasted.
339 | const geo = new BufferGeometry();
340 | const orig_geo = intersect.object.geometry;
341 | const orig_vtx_ar = orig_geo.getAttribute('position').array;
342 | const f = intersect.face;
343 | const new_vtx_ar = [f.a, f.b, f.c].map(_idx => {
344 | const vtx_ar = [];
345 | for(let i = 0; i < 3; i++){
346 | vtx_ar.push(orig_vtx_ar[(_idx * 3) + i]);
347 | }
348 | return vtx_ar;
349 | }).flat();
350 |
351 | geo.setIndex([0,1,2]);
352 | geo.addAttribute('position', new Float32BufferAttribute(Float32Array.from(new_vtx_ar), 3));
353 |
354 | geo.computeBoundingSphere();
355 | geo.computeVertexNormals();
356 | geo.computeBoundingBox();
357 |
358 | this.selectedObject = new Mesh(geo, new MeshBasicMaterial({
359 | color: new Color(1,0,0),
360 | transparent: true,
361 | opacity: 0.5
362 | }));
363 |
364 | const projected = this.projectTriangleToPlane(new_vtx_ar);
365 |
366 | Object.assign(this.selectedObject.userData, {
367 | zoom: {
368 | center: projected.center,
369 | dims: projected.projection_size
370 | }
371 | });
372 |
373 | this.scene.add(this.selectedObject);
374 | break;
375 | case 'plane':
376 | this.selectedObject = intersect.object;
377 | this.selectedObject.material.dispose();
378 | this.selectedObject.material = new MeshBasicMaterial({
379 | color: new Color(1,0,0),
380 | wireframe: true
381 | });
382 | break;
383 | }
384 |
385 | }else{
386 | this.deselect();
387 | }
388 |
389 | }
390 |
391 | projectTriangleToPlane(verts_ar){
392 | const centroid = this.findTriangleCentroid(verts_ar);
393 |
394 | const vecs = [0,1,2].map(_t => {
395 | return new Vector3().fromArray([0,1,2].map(_v => {
396 | return verts_ar[(_t*3)+_v];
397 | })).sub(centroid);
398 | });
399 |
400 | const norm = new Vector3().crossVectors(vecs[1], vecs[0]).normalize();
401 | const right = new Vector3().crossVectors(new Vector3(0,1,0), norm).normalize();
402 | const up = new Vector3().crossVectors(norm, right).normalize();
403 | const prj_vecs = vecs.map(_v => {
404 | return new Vector2(
405 | _v.dot(right),
406 | _v.dot(up)
407 | );
408 | });
409 |
410 | const prj_bbox = new Box2().setFromPoints(prj_vecs);
411 | const prj_dims = new Vector2();
412 | prj_bbox.getSize(prj_dims);
413 |
414 | const prj_center = new Vector2(
415 | prj_bbox.min.x + (prj_dims.x / 2),
416 | prj_bbox.min.y + (prj_dims.y / 2)
417 | );
418 |
419 | const prj_delta = new Vector2().subVectors(prj_center, prj_vecs[0]);
420 |
421 | //translate the 3d centroid to the position of the 2d projected center via prj_delta, up, and right.
422 | const center = vecs[0].clone().add(centroid);
423 | center.add(up.clone().multiplyScalar(prj_delta.y));
424 | center.add(right.clone().multiplyScalar(prj_delta.x));
425 |
426 | return {
427 | center: center,
428 | projection: prj_vecs,
429 | projection_bbox: prj_bbox,
430 | projection_size: prj_dims
431 | };
432 |
433 | }
434 |
435 | findTriangleCentroid (verts_ar) {
436 | let center = [0,0,0];
437 |
438 | [0,1,2].forEach(_v => {
439 | [0,1,2].forEach(_d => {
440 | center[_d] += verts_ar[(_v*3) + _d];
441 | });
442 | });
443 |
444 | center = center.map(_d => {return _d / 3;});
445 |
446 | return (new Vector3()).fromArray(center);
447 | }
448 |
449 | onWindowResize(){
450 | this.renderer.setSize( window.innerWidth, window.innerHeight );
451 | this.controls.camera.aspect = this.renderer.domElement.clientWidth / this.renderer.domElement.clientHeight;
452 | this.controls.camera.updateProjectionMatrix();
453 | this.renderer.setSize( this.renderer.domElement.clientWidth, this.renderer.domElement.clientHeight );
454 | }
455 |
456 | animate(){
457 | requestAnimationFrame( () => {
458 | this.animate();
459 | } );
460 |
461 | if(this.selectedObject){
462 | const currentColor = this.selectedObject.material.color;
463 | this.selectedObjectTween += 0.025;
464 | currentColor.g = Math.abs(1 - (this.selectedObjectTween % 2));
465 | }
466 |
467 | if(this.mode == 'plane'){
468 | this.meshes.forEach(( mesh ) => {
469 | mesh.rotation.x += 0.005;
470 | mesh.rotation.y += 0.01;
471 | });
472 | }
473 |
474 | this.controls.update();
475 | this.renderer.render( this.scene, this.controls.camera );
476 | }
477 | };
478 |
479 | window.addEventListener('load', () => {
480 | window.demo = new MapControlsDemo('sphere');
481 | });
--------------------------------------------------------------------------------
/dist/demo.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};function s(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,s),o.l=!0,o.exports}s.m=e,s.c=t,s.d=function(e,t,i){s.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:i})},s.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="/",s(s.s=4)}([function(e,t,s){e.exports=s(3)(0)},function(e,t,s){"use strict";s.r(t);var i=s(0);if("undefined"==typeof window){s(2)}t.default=class extends i.EventDispatcher{constructor(e,t,s){super(),this.camera=e,this.domElement=void 0!==t?t:window.document.body,this.enabled=!0,this.target,this.minDistance=1,this.maxDistance=100,this.enableZoom=!0,this.zoomSpeed=6,this.zoomDampingAlpha=.1,this.initialZoom=0,this.enablePan=!0,this.keyPanSpeed=50,this.keyZoomSpeed=this.zoomSpeed,this.panDampingAlpha=.1,this.enableKeys=!0,this.keys={PAN_LEFT:"ArrowLeft",PAN_UP:"ArrowUp",PAN_RIGHT:"ArrowRight",PAN_BOTTOM:"ArrowDown",ZOOM_IN:"]",ZOOM_OUT:"["},this.mouseButtons={ZOOM:i.MOUSE.MIDDLE,PAN:i.MOUSE.LEFT},Object.assign(this,s);let o=!1;if(void 0===this.mode)throw new Error("'mode' option must be set to either 'plane' or 'sphere'");switch(this.mode){case"plane":o=void 0!==this.target.normal&&void 0!==this.target.constant;break;case"sphere":o=void 0!==this.target.center&&void 0!==this.target.radius}if(!o)throw new Error("'target' option must be an instance of type THREE.Plane or THREE.Sphere");this._eventListeners={contextmenu:this._onContextMenu.bind(this),mousedown:this._onMouseDown.bind(this),mousewheel:this._onMouseWheel.bind(this),MozMousePixelScroll:this._onMouseWheel.bind(this),touchstart:this._onTouchStart.bind(this),touchend:this._onTouchEnd.bind(this),touchmove:this._onTouchMove.bind(this),keydown:this._onKeyDown.bind(this),mouseover:this._onMouseOver.bind(this),mouseout:this._onMouseOut.bind(this),mousemove:this._onMouseMove.bind(this),mouseup:this._onMouseUp.bind(this)},this._init()}_init(){if(this.target0=this.target.clone(),this.position0=this.camera.position.clone(),this.zoom0=this.camera.zoom,this._changeEvent={type:"change"},this._startEvent={type:"start"},this._endEvent={type:"end"},this._STATES={NONE:-1,DOLLY:1,PAN:2,TOUCH_DOLLY:4,TOUCH_PAN:5},0==this.target.distanceToPoint(this.camera.position))throw new Error("ORIENTATION_UNKNOWABLE: initial Camera position cannot intersect target plane.");this._state=this._STATES.NONE,this._mouse=new i.Vector2,this._finalTargetDistance=0,this._currentTargetDistance=0,this._panTarget=new i.Vector3(0,0,0),this._panCurrent=new i.Vector3(0,0,0),this._minZoomPosition=new i.Vector3,this._maxZoomPosition=new i.Vector3,this._panStart=new i.Vector2,this._panEnd=new i.Vector2,this._panDelta=new i.Vector2,this._dollyStart=new i.Vector2,this._dollyEnd=new i.Vector2,this._dollyDelta=new i.Vector2,this._camOrientation=new i.Vector2,this._zoomAlpha,this._screenWorldXform=Math.tan(this.camera.fov/2*Math.PI/180),this._straightDollyTrack(),this.camera.position.lerpVectors(this._minZoomPosition,this._maxZoomPosition,this.initialZoom),this._finalTargetDistance=this._currentTargetDistance=Math.abs(this.target.distanceToPoint(this.camera.position));const e=this._intersectCameraTarget();this.camera.lookAt(e.intersection),this._camOrientation=e.ray.direction.clone().normalize(),this._updateZoomAlpha(),["contextmenu","mousedown","mousewheel","MozMousePixelScroll","touchstart","touchend","touchmove","mouseover","mouseout","keydown"].forEach(e=>{this.domElement.addEventListener(e,this._eventListeners[e],!1)}),"CANVAS"!=this.domElement.tagName||this.domElement.getAttribute("tabindex")||this.domElement.setAttribute("tabindex","1"),this.update()}_intersectCameraTarget(){let e,t=new i.Vector3;switch(this.mode){case"plane":const s=new i.Vector3;this.target.projectPoint(this.camera.position,s),(e=new i.Ray(this.camera.position,(new i.Vector3).subVectors(s,this.camera.position).normalize())).intersectPlane(this.target,t);break;case"sphere":(e=new i.Ray(this.camera.position,(new i.Vector3).subVectors(this.target.center,this.camera.position).normalize())).intersectSphere(this.target,t)}return{intersection:t,ray:e}}_straightDollyTrack(){this._updateDollyTrack(this._intersectCameraTarget().ray)}getZoomAlpha(){return this._zoomAlpha}reset(){this.target.copy(this.target0),this.camera.position.copy(this.position0),this.camera.zoom=this.zoom0,this.camera.updateProjectionMatrix(),this._init(),this.dispatchEvent(this._changeEvent),this.update(),this._state=this._STATES.NONE}update(){const e=new i.Vector3,t=new i.Vector3,s=this.camera.position;switch(t.copy(this._panCurrent),this._panCurrent.lerp(this._panTarget,this.panDampingAlpha),e.subVectors(this._panCurrent,t),this.mode){case"plane":this._maxZoomPosition.add(e),this._minZoomPosition.add(e);break;case"sphere":const s=new i.Vector3,o=new i.Quaternion;o.setFromAxisAngle(s.setFromMatrixColumn(this.camera.matrix,1),e.x),this._maxZoomPosition.applyQuaternion(o),this._minZoomPosition.applyQuaternion(o),o.setFromAxisAngle(s.setFromMatrixColumn(this.camera.matrix,0),e.y),this._maxZoomPosition.applyQuaternion(o),this._minZoomPosition.applyQuaternion(o),o.setFromAxisAngle(new i.Vector3(0,1,0),e.z),this._maxZoomPosition.applyQuaternion(o),this._minZoomPosition.applyQuaternion(o)}s.lerpVectors(this._minZoomPosition,this._maxZoomPosition,this._updateZoomAlpha()),"sphere"==this.mode&&this.camera.lookAt(this.target.center)}dispose(){Object.keys(this._eventListeners).forEach(e=>{this.domElement.removeEventListener(e,this._eventListeners[e],!1)})}zoomToFit(e,t,s){if(void 0===t&&(t=e.geometry.boundingSphere.center.clone()),t=e.localToWorld(t.clone()),void 0===s){const t=2*e.geometry.boundingSphere.radius;s=new i.Vector2(t,t)}switch(this.mode){case"plane":this._panTarget.copy(t),this._panCurrent.copy(this._intersectCameraTarget().intersection);break;case"sphere":const e=this._sphericalCoordinatesFrom(t),s=this._sphericalCoordinatesFrom(this.camera.position),o=(new i.Vector2).subVectors(e,s);Math.abs(o.x)>Math.PI&&(o.x=-Math.abs(o.x)/o.x*(2*Math.PI-Math.abs(o.x))),this._panTarget.add(new i.Vector3(0,-o.y,o.x))}this._straightDollyTrack();const o=this.camera.fov*(Math.PI/180),n=2*Math.atan(Math.tan(o/2)*this.camera.aspect),a=s.x/s.y;this._finalTargetDistance=(a>this.camera.aspect?s.x:s.y)/2/Math.tan((a>this.camera.aspect?n:o)/2)}targetAreaVisible(){let e,t,s,o;switch(this.mode){case"plane":var n=new i.Ray(this.camera.position,this._camOrientation).distanceToPlane(this.target);o=this.camera.position.clone(),s=(t=this._screenWorldXform*n)*this.camera.aspect,e=new i.Box2(new i.Vector2(o.x-s,o.y-t),new i.Vector2(o.x+s,o.y+t));break;case"sphere":const a=(new i.Vector3).subVectors(this.target.center,this.camera.position);o=this._sphericalCoordinatesFrom(this.camera.position);const r=Math.PI/2,h=a.length();t=this._screenWorldXform*(h/this.target.radius-1),t=Math.min(t,r);const c=this.target.radius*Math.cos(o.y-r);s=t*this.camera.aspect*(this.target.radius/c),s=Math.min(s,r),e=new i.Box2(new i.Vector2(o.x-s-r,o.y-t-r),new i.Vector2(o.x+s-r,o.y+t-r)),["min","max"].forEach(t=>{e[t].x=e[t].x>Math.PI?-2*Math.PI+e[t].x:e[t].x})}return e}targetAreaVisibleDeg(){let e=this.targetAreaVisible();return"sphere"==this.mode&&(e.min.x=e.min.x*(180/Math.PI),e.min.y=e.min.y*(180/Math.PI),e.max.x=e.max.x*(180/Math.PI),e.max.y=e.max.y*(180/Math.PI)),e}_sphericalCoordinatesFrom(e){const t=(new i.Vector3).subVectors(this.target.center,e),s=new i.Vector3(t.x,0,t.z),o=(new i.Vector3,new i.Vector2(s.angleTo(new i.Vector3(1,0,0)),t.angleTo(new i.Vector3(0,1,0))));return o.x=t.z>0?2*Math.PI-o.x:o.x,o}_updateZoomAlpha(){this._finalTargetDistance=Math.max(this.minDistance,Math.min(this.maxDistance,this._finalTargetDistance));var e=this._currentTargetDistance-this._finalTargetDistance,t=this.zoomDampingAlpha;return this._currentTargetDistance-=e*t,this._zoomAlpha=Math.abs(Math.round(1e5*(1-(this._currentTargetDistance-this.minDistance)/(this.maxDistance-this.minDistance)))/1e5),this._zoomAlpha}_updateDollyTrack(e){let t=new i.Vector3;switch(this.mode){case"plane":e.intersectPlane(this.target,t);break;case"sphere":e.intersectSphere(this.target,t)}t&&(this._maxZoomPosition.addVectors(t,(new i.Vector3).subVectors(this.camera.position,t).normalize().multiplyScalar(this.minDistance)),this._minZoomPosition.copy(this._calculateMinZoom(this.camera.position,t)),this._finalTargetDistance=this._currentTargetDistance=t.clone().sub(this.camera.position).length())}_getZoomScale(e){return e=e||this.zoomSpeed,Math.pow(.95,e)}_panLeft(e,t){var s=new i.Vector3;switch(this.mode){case"sphere":s.set(-e,0,0);break;case"plane":s.setFromMatrixColumn(t,0),s.multiplyScalar(-e)}this._panTarget.add(s)}_panUp(e,t){var s=new i.Vector3;switch(this.mode){case"sphere":s.set(0,-e,0);break;case"plane":s.setFromMatrixColumn(t,1),s.multiplyScalar(e)}this._panTarget.add(s)}_pan(e,t){var s,o=this.domElement,n=new i.Ray(this.camera.position,this._camOrientation);switch(this.mode){case"plane":s=this._screenWorldXform*n.distanceToPlane(this.target);break;case"sphere":const e=(new i.Vector3).subVectors(this.camera.position,this.target.center);s=this._screenWorldXform*(e.length()/this.target.radius-1)}this._panLeft(2*e*s/o.clientHeight,this.camera.matrix),this._panUp(2*t*s/o.clientHeight,this.camera.matrix)}_dollyIn(e){this._cameraOfKnownType()?this._finalTargetDistance/=e:(console.warn("WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled."),this.enableZoom=!1)}_dollyOut(e){this._cameraOfKnownType()?this._finalTargetDistance*=e:(console.warn("WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled."),this.enableZoom=!1)}_cameraOfKnownType(){return"PerspectiveCamera"===this.camera.type}_handleUpdateDollyTrackMouse(e){var t=this._mouse.clone();if(this._mouse.set(e.offsetX/this.domElement.clientWidth*2-1,-e.offsetY/this.domElement.clientHeight*2+1),!t.equals(this._mouse)){var s=new i.Raycaster;s.setFromCamera(this._mouse,this.camera),this._updateDollyTrack(s.ray)}}_handleMouseDownDolly(e){this._handleUpdateDollyTrackMouse(e),this._dollyStart.set(e.offsetX,e.offsetY)}_handleMouseDownPan(e){this._panStart.set(e.offsetX,e.offsetY)}_handleMouseMoveDolly(e){this._handleUpdateDollyTrackMouse(e),this._dollyEnd.set(e.offsetX,e.offsetY),this._dollyDelta.subVectors(this._dollyEnd,this._dollyStart),this._dollyDelta.y>0?this._dollyIn(this._getZoomScale()):this._dollyDelta.y<0&&this._dollyOut(this._getZoomScale()),this._dollyStart.copy(this._dollyEnd),this.update()}_handleMouseMovePan(e){this._panEnd.set(e.offsetX,e.offsetY),this._panDelta.subVectors(this._panEnd,this._panStart),this._pan(this._panDelta.x,this._panDelta.y),this._panStart.copy(this._panEnd),this.update()}_handleMouseUp(e){}_calculateMinZoom(e,t){return t.clone().add(e.clone().sub(t).normalize().multiplyScalar(this.maxDistance))}_handleMouseWheel(e){this._handleUpdateDollyTrackMouse(e);var t=0;void 0!==e.wheelDelta?t=e.wheelDelta:void 0!==e.detail&&(t=-e.detail),t>0?this._dollyOut(this._getZoomScale()):t<0&&this._dollyIn(this._getZoomScale()),this.update()}_handleKeyDown(e){switch(e.key){case this.keys.PAN_UP:this._pan(0,this.keyPanSpeed),this.update();break;case this.keys.PAN_BOTTOM:this._pan(0,-this.keyPanSpeed),this.update();break;case this.keys.PAN_LEFT:this._pan(this.keyPanSpeed,0),this.update();break;case this.keys.PAN_RIGHT:this._pan(-this.keyPanSpeed,0),this.update();break;case this.keys.ZOOM_IN:this._dollyIn(this._getZoomScale(this.keyZoomSpeed)),this.update();break;case this.keys.ZOOM_OUT:this._dollyOut(this._getZoomScale(this.keyZoomSpeed)),this.update()}}_handleUpdateDollyTrackTouch(e){var t=new i.Vector2,s=e.touches[0].pageX-e.touches[1].pageX,o=e.touches[0].pageY-e.touches[1].pageY;t.x=e.touches[0].pageX+s/2,t.y=e.touches[0].pageY+o/2;var n=new i.Vector2;n.x=t.x/domElement.clientWidth*2-1,n.y=-t.y/domElement.clientHeight*2+1,this._updateDollyTrack(n)}_handleTouchStartDolly(e){this._handleUpdateDollyTrackTouch(e);var t=e.touches[0].pageX-e.touches[1].pageX,s=e.touches[0].pageY-e.touches[1].pageY,i=Math.sqrt(t*t+s*s);this._dollyStart.set(0,i)}_handleTouchStartPan(e){this._panStart.set(e.touches[0].pageX,e.touches[0].pageY)}_handleTouchMoveDolly(e){this._handleUpdateDollyTrackTouch(e);var t=e.touches[0].pageX-e.touches[1].pageX,s=e.touches[0].pageY-e.touches[1].pageY,i=Math.sqrt(t*t+s*s);this._dollyEnd.set(0,i),this._dollyDelta.subVectors(this._dollyEnd,this._dollyStart),this._dollyDelta.y>0?this._dollyOut(this._getZoomScale()):this._dollyDelta.y<0&&this._dollyIn(this._getZoomScale()),this._dollyStart.copy(this._dollyEnd),this.update()}_handleTouchMovePan(e){this._panEnd.set(e.touches[0].pageX,e.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()}_handleTouchEnd(e){}_onMouseDown(e){if(!1!==this.enabled){if(e.preventDefault(),e.button===this.mouseButtons.ZOOM){if(!1===this.enableZoom)return;this._handleMouseDownDolly(e),this._state=this._STATES.DOLLY}else if(e.button===this.mouseButtons.PAN){if(!1===this.enablePan)return;this._handleMouseDownPan(e),this._state=this._STATES.PAN}this._state!==this._STATES.NONE&&(this.domElement.addEventListener("mousemove",this._eventListeners.mousemove,!1),this.domElement.addEventListener("mouseup",this._eventListeners.mouseup,!1),this.dispatchEvent(this._startEvent))}}_onMouseMove(e){if(!1!==this.enabled)if(e.preventDefault(),this._state===this._STATES.DOLLY){if(!1===this.enableZoom)return;this._handleMouseMoveDolly(e)}else if(this._state===this._STATES.PAN){if(!1===this.enablePan)return;this._handleMouseMovePan(e)}}_onMouseUp(e){!1!==this.enabled&&(this._handleMouseUp(e),this.domElement.removeEventListener("mousemove",this._eventListeners.mousemove,!1),this.domElement.removeEventListener("mouseup",this._eventListeners.mouseup,!1),this.dispatchEvent(this._endEvent),this._state=this._STATES.NONE)}_onMouseWheel(e){!1!==this.enabled&&!1!==this.enableZoom&&this._state===this._STATES.NONE&&(e.preventDefault(),e.stopPropagation(),this._handleMouseWheel(e),this.dispatchEvent(this._startEvent),this.dispatchEvent(this._endEvent))}_onKeyDown(e){!1!==this.enabled&&!1!==this.enableKeys&&!1!==this.enablePan&&this._handleKeyDown(e)}_onTouchStart(e){if(!1!==this.enabled){switch(e.touches.length){case 1:if(!1===this.enablePan)return;this._handleTouchStartPan(e),this._state=this._STATES.TOUCH_PAN;break;case 2:if(!1===this.enableZoom)return;this._handleTouchStartDolly(e),this._state=this._STATES.TOUCH_DOLLY;break;default:this._state=this._STATES.NONE}this._state!==this._STATES.NONE&&this.dispatchEvent(this._startEvent)}}_onTouchMove(e){if(!1!==this.enabled)switch(e.preventDefault(),e.stopPropagation(),e.touches.length){case 1:if(!1===this.enablePan)return;if(this._state!==this._STATES.TOUCH_PAN)return;this._handleTouchMovePan(e);break;case 2:if(!1===this.enableZoom)return;if(this._state!==this._STATES.TOUCH_DOLLY)return;this._handleTouchMoveDolly(e);break;default:this._state=this._STATES.NONE}}_onTouchEnd(e){!1!==this.enabled&&(this._handleTouchEnd(e),this.dispatchEvent(this._endEvent),this._state=this._STATES.NONE)}_onContextMenu(e){e.preventDefault()}_onMouseOver(e){return this.domElement.focus(),!1}_onMouseOut(e){return this.domElement.blur(),!1}}},function(e,t){e.exports={console:{log:function(){}},document:{body:{clientWidth:1920,clientHeight:1080,addEventListener:function(){},removeEventListener:function(){}}}}},function(e,t){e.exports=vendor},function(e,t,s){"use strict";s.r(t);var i=s(0),o=s(1);const n=10;window.addEventListener("load",()=>{window.demo=new class{constructor(e){this.container=document.body,this.scene=new i.Scene,this.renderer=null,this.meshes=[],this.dims=10,this.selectedObjectTween=0,this.selectedObject=null,this.controls,this.mode,this.debugCamViewInterval,this.camViewMesh,this.camViewLines,this.init(),this.setMode(e),this.animate()}setMode(e){this.deselect(),this.mode=e;const t={sphere:document.getElementById("sphere-link"),plane:document.getElementById("plane-link")};switch(t[this.mode].style.display="none",t["plane"==this.mode?"sphere":"plane"].style.display="inline-block",this.meshes.concat([this.camViewLines,this.camViewMesh]).forEach(e=>{void 0!==e&&(this.scene.remove(e),e.geometry.dispose())}),this.camViewLines=this.camViewMesh=void 0,this.mode){case"sphere":this.initSphere();break;case"plane":this.initPlane()}}initSphere(){var e=new i.PerspectiveCamera(45,window.innerWidth/window.innerHeight,1,1e3);e.position.z=40,this.controls=new o.default(e,this.renderer.domElement,{target:new i.Sphere(new i.Vector3(0,0,0),n),mode:"sphere",minDistance:1,maxDistance:e.position.z});const t=[],s=new i.SphereBufferGeometry(n,this.dims,this.dims);s.computeBoundingSphere();const a=s.getAttribute("position").array;for(var r=0;r{const e=this.controls.targetAreaVisible();let t,s;switch(console.log(`${e.min.x}, ${e.min.y}, ${e.max.x}, ${e.max.y}`),s=new i.Vector3(0,0,0),this.mode){case"sphere":let o=Math.abs(e.max.x-e.min.x);o>Math.PI&&(o=Math.abs(e.max.x+2*Math.PI-e.min.x)),t=new i.SphereBufferGeometry(n,this.dims,this.dims,e.min.x+Math.PI/2,o,-e.max.y+Math.PI/2,Math.abs(e.max.y-e.min.y));break;case"plane":t=new i.PlaneBufferGeometry(e.max.x-e.min.x,e.max.y-e.min.y,this.dims,this.dims),s.copy(this.controls.camera.position),s.z=0}void 0==this.camViewMesh?(this.camViewMesh=new i.Mesh(t,new i.MeshBasicMaterial({color:new i.Color(1,0,0),side:i.DoubleSide,transparent:!0,opacity:.5})),this.camViewLines=new i.Mesh(t,new i.MeshBasicMaterial({color:new i.Color(1,0,0),wireframe:!0})),this.scene.add(this.camViewMesh),this.scene.add(this.camViewLines)):(this.camViewMesh.geometry.copy(t),this.camViewLines.geometry.copy(t),t.dispose()),this.camViewMesh.geometry.computeBoundingSphere(),this.camViewMesh.position.copy(s),this.camViewLines.position.copy(s)},1e3)}initPlane(){var e=new i.PerspectiveCamera(70,window.innerWidth/window.innerHeight,1,1e3);e.position.z=20,this.controls=new o.default(e,this.renderer.domElement,{target:new i.Plane(new i.Vector3(0,0,1),0),mode:"plane",minDistance:2,maxDistance:20});for(var t=0;t{this.onWindowResize()},!1),this.renderer.domElement.addEventListener("mousedown",e=>{this.pick(e)}),this.renderer.domElement.addEventListener("dblclick",e=>{this.zoomTo(e)}),document.getElementById("toggleCamDebug").addEventListener("click",this.toggleDebugCamView.bind(this))}zoomTo(){if(this.selectedObject)switch(this.mode){case"sphere":this.controls.zoomToFit(this.selectedObject,this.selectedObject.userData.zoom.center,this.selectedObject.userData.zoom.dims);break;case"plane":this.controls.zoomToFit(this.selectedObject)}}deselect(){if(this.selectedObject)switch(this.mode){case"sphere":this.selectedObject.parent.remove(this.selectedObject),this.selectedObject.geometry.dispose(),this.selectedObject.material.dispose();break;case"plane":this.selectedObject.material.dispose(),this.selectedObject.material=new i.MeshNormalMaterial}this.selectedObject=null}pick(e){this.deselect();const t=new i.Vector2;t.x=e.clientX/this.renderer.domElement.clientWidth*2-1,t.y=-e.clientY/this.renderer.domElement.clientHeight*2+1;const s=new i.Raycaster;s.setFromCamera(t,this.controls.camera);const o=s.intersectObjects(this.scene.children,!0).filter(e=>e.object.userData.selectable)[0];if(void 0!==o&&o.face)switch(this.mode){case"sphere":const e=new i.BufferGeometry,t=o.object.geometry.getAttribute("position").array,s=o.face,n=[s.a,s.b,s.c].map(e=>{const s=[];for(let i=0;i<3;i++)s.push(t[3*e+i]);return s}).flat();e.setIndex([0,1,2]),e.addAttribute("position",new i.Float32BufferAttribute(Float32Array.from(n),3)),e.computeBoundingSphere(),e.computeVertexNormals(),e.computeBoundingBox(),this.selectedObject=new i.Mesh(e,new i.MeshBasicMaterial({color:new i.Color(1,0,0),transparent:!0,opacity:.5}));const a=this.projectTriangleToPlane(n);Object.assign(this.selectedObject.userData,{zoom:{center:a.center,dims:a.projection_size}}),this.scene.add(this.selectedObject);break;case"plane":this.selectedObject=o.object,this.selectedObject.material.dispose(),this.selectedObject.material=new i.MeshBasicMaterial({color:new i.Color(1,0,0),wireframe:!0})}else this.deselect()}projectTriangleToPlane(e){const t=this.findTriangleCentroid(e),s=[0,1,2].map(s=>(new i.Vector3).fromArray([0,1,2].map(t=>e[3*s+t])).sub(t)),o=(new i.Vector3).crossVectors(s[1],s[0]).normalize(),n=(new i.Vector3).crossVectors(new i.Vector3(0,1,0),o).normalize(),a=(new i.Vector3).crossVectors(o,n).normalize(),r=s.map(e=>new i.Vector2(e.dot(n),e.dot(a))),h=(new i.Box2).setFromPoints(r),c=new i.Vector2;h.getSize(c);const l=new i.Vector2(h.min.x+c.x/2,h.min.y+c.y/2),d=(new i.Vector2).subVectors(l,r[0]),m=s[0].clone().add(t);return m.add(a.clone().multiplyScalar(d.y)),m.add(n.clone().multiplyScalar(d.x)),{center:m,projection:r,projection_bbox:h,projection_size:c}}findTriangleCentroid(e){let t=[0,0,0];return[0,1,2].forEach(s=>{[0,1,2].forEach(i=>{t[i]+=e[3*s+i]})}),t=t.map(e=>e/3),(new i.Vector3).fromArray(t)}onWindowResize(){this.renderer.setSize(window.innerWidth,window.innerHeight),this.controls.camera.aspect=this.renderer.domElement.clientWidth/this.renderer.domElement.clientHeight,this.controls.camera.updateProjectionMatrix(),this.renderer.setSize(this.renderer.domElement.clientWidth,this.renderer.domElement.clientHeight)}animate(){if(requestAnimationFrame(()=>{this.animate()}),this.selectedObject){const e=this.selectedObject.material.color;this.selectedObjectTween+=.025,e.g=Math.abs(1-this.selectedObjectTween%2)}"plane"==this.mode&&this.meshes.forEach(e=>{e.rotation.x+=.005,e.rotation.y+=.01}),this.controls.update(),this.renderer.render(this.scene,this.controls.camera)}}("sphere")})}]);
2 | //# sourceMappingURL=demo.js.map
--------------------------------------------------------------------------------
/src/three-map-controls.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | //Alex Pilafian 2016-2019 - sikanrong@gmail.com
4 |
5 | import {
6 | Box2,
7 | Quaternion,
8 | EventDispatcher,
9 | Vector2,
10 | Vector3,
11 | Raycaster,
12 | Ray,
13 | MOUSE
14 | } from 'three'
15 |
16 | //test stubs
17 | if(typeof window == 'undefined'){
18 | let window = require('../test/stub_dom');
19 | }
20 |
21 | class MapControls extends EventDispatcher{
22 |
23 | constructor(camera, domElement, options){
24 | super();
25 |
26 | this.camera = camera;
27 |
28 | //Object to use for listening for keyboard/mouse events
29 | this.domElement = ( domElement !== undefined ) ? domElement : window.document.body;
30 |
31 | // Set to false to disable this control (Disables all input events)
32 | this.enabled = true;
33 |
34 | // Must be set to instance of Plane or Sphere
35 | this.target;
36 |
37 | // How far you can dolly in and out
38 | this.minDistance = 1; //probably should never be 0
39 | this.maxDistance = 100;
40 |
41 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
42 | // Set to false to disable zooming
43 | this.enableZoom = true;
44 | this.zoomSpeed = 6.0;
45 | this.zoomDampingAlpha = 0.1;
46 | this.initialZoom = 0; //start zoomed all the way out unless set in options.
47 |
48 | // Set to false to disable panning
49 | this.enablePan = true;
50 | this.keyPanSpeed = 50.0; // pixels moved per arrow key push
51 | this.keyZoomSpeed = this.zoomSpeed; // keyboard zoom speed, defaults to mouse-wheel zoom speed
52 | this.panDampingAlpha = 0.1;
53 |
54 | // Set to false to disable use of the keys
55 | this.enableKeys = true;
56 |
57 | // The four arrow keys, and two zoom keys
58 | this.keys = {
59 | PAN_LEFT: "ArrowLeft",
60 | PAN_UP: "ArrowUp",
61 | PAN_RIGHT: "ArrowRight",
62 | PAN_BOTTOM: "ArrowDown",
63 | ZOOM_IN: "]",
64 | ZOOM_OUT: "["
65 | };
66 |
67 | // Mouse buttons
68 | this.mouseButtons = { ZOOM: MOUSE.MIDDLE, PAN: MOUSE.LEFT };
69 |
70 | //Copy options from parameters
71 | Object.assign(this, options);
72 | let isTargetValid = false;
73 |
74 | if(this.mode === undefined){
75 | throw new Error('\'mode\' option must be set to either \'plane\' or \'sphere\'');
76 | }
77 |
78 | switch(this.mode){
79 | case 'plane':
80 | isTargetValid = (this.target.normal !== undefined && this.target.constant !== undefined);
81 | break;
82 | case 'sphere':
83 | isTargetValid = (this.target.center !== undefined && this.target.radius !== undefined);
84 | break;
85 | }
86 |
87 | if(!isTargetValid){
88 | throw new Error('\'target\' option must be an instance of type THREE.Plane or THREE.Sphere');
89 | }
90 |
91 | this._eventListeners = {
92 | 'contextmenu': this._onContextMenu.bind(this),
93 | 'mousedown': this._onMouseDown.bind(this),
94 | 'mousewheel': this._onMouseWheel.bind(this),
95 | 'MozMousePixelScroll': this._onMouseWheel.bind(this),
96 | 'touchstart': this._onTouchStart.bind(this),
97 | 'touchend': this._onTouchEnd.bind(this),
98 | 'touchmove': this._onTouchMove.bind(this),
99 | 'keydown': this._onKeyDown.bind(this),
100 | 'mouseover': this._onMouseOver.bind(this),
101 | 'mouseout': this._onMouseOut.bind(this),
102 | 'mousemove': this._onMouseMove.bind(this),
103 | 'mouseup': this._onMouseUp.bind(this)
104 | };
105 |
106 | this._init();
107 | }
108 |
109 | _init (){
110 |
111 | this.target0 = this.target.clone();
112 | this.position0 = this.camera.position.clone();
113 | this.zoom0 = this.camera.zoom;
114 | this._changeEvent = { type: 'change' };
115 | this._startEvent = { type: 'start' };
116 | this._endEvent = { type: 'end' };
117 |
118 | this._STATES = { NONE : - 1, DOLLY : 1, PAN : 2, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
119 |
120 | if(this.target.distanceToPoint(this.camera.position) == 0){
121 | throw new Error("ORIENTATION_UNKNOWABLE: initial Camera position cannot intersect target plane.");
122 | }
123 |
124 | this._state = this._STATES.NONE;
125 |
126 | this._mouse = new Vector2();
127 |
128 | this._finalTargetDistance = 0;
129 | this._currentTargetDistance = 0;
130 |
131 | this._panTarget = new Vector3(0,0,0);
132 | this._panCurrent = new Vector3(0,0,0);
133 |
134 | this._minZoomPosition = new Vector3();
135 | this._maxZoomPosition = new Vector3();
136 |
137 | this._panStart = new Vector2();
138 | this._panEnd = new Vector2();
139 | this._panDelta = new Vector2();
140 |
141 | this._dollyStart = new Vector2();
142 | this._dollyEnd = new Vector2();
143 | this._dollyDelta = new Vector2();
144 |
145 | this._camOrientation = new Vector2();
146 |
147 | this._zoomAlpha;
148 |
149 | this._screenWorldXform = Math.tan( ( this.camera.fov / 2 ) * Math.PI / 180.0 );
150 |
151 | //establish initial camera orientation based on position w.r.t. _this.target plane
152 | this._straightDollyTrack();
153 |
154 | this.camera.position.lerpVectors(this._minZoomPosition, this._maxZoomPosition, this.initialZoom);
155 | this._finalTargetDistance = this._currentTargetDistance = Math.abs(this.target.distanceToPoint(this.camera.position));
156 |
157 | const res = this._intersectCameraTarget();
158 | this.camera.lookAt(res.intersection); //set the orientation of the camera towards the map.
159 | this._camOrientation = res.ray.direction.clone().normalize();
160 |
161 | this._updateZoomAlpha();
162 |
163 | //Assign event listeners
164 |
165 | [
166 | 'contextmenu',
167 | 'mousedown',
168 | 'mousewheel',
169 | 'MozMousePixelScroll',
170 | 'touchstart',
171 | 'touchend',
172 | 'touchmove',
173 | 'mouseover',
174 | 'mouseout',
175 | 'keydown'
176 | ].forEach(_e => {
177 | this.domElement.addEventListener(_e, this._eventListeners[_e], false);
178 | });
179 |
180 | if(this.domElement.tagName == 'CANVAS' &&
181 | !this.domElement.getAttribute('tabindex')){
182 | //if we're dealing with a canvas element which has no tabindex,
183 | //give it one so that it may recieve keyboard focus
184 | this.domElement.setAttribute('tabindex', '1');
185 | }
186 |
187 | this.update();
188 | }
189 |
190 | _intersectCameraTarget(){
191 | let intersection = new Vector3();
192 | let ray;
193 |
194 | switch(this.mode){
195 | case 'plane':
196 | const coplanar = new Vector3();
197 | this.target.projectPoint(this.camera.position, coplanar);
198 | ray = new Ray(this.camera.position, new Vector3().subVectors(coplanar, this.camera.position).normalize());
199 | ray.intersectPlane(this.target, intersection);
200 | break;
201 | case 'sphere':
202 | ray = new Ray(this.camera.position, (new Vector3()).subVectors(this.target.center, this.camera.position).normalize());
203 | ray.intersectSphere(this.target, intersection);
204 | break;
205 | }
206 |
207 | return {
208 | intersection: intersection,
209 | ray: ray
210 | }
211 | }
212 |
213 | _straightDollyTrack(){
214 | this._updateDollyTrack(this._intersectCameraTarget().ray);
215 | }
216 |
217 | getZoomAlpha () {
218 | return this._zoomAlpha;
219 | }
220 |
221 | reset () {
222 |
223 | this.target.copy( this.target0 );
224 | this.camera.position.copy( this.position0 );
225 | this.camera.zoom = this.zoom0;
226 |
227 | this.camera.updateProjectionMatrix();
228 |
229 | this._init(); //reinit
230 |
231 | this.dispatchEvent( this._changeEvent );
232 |
233 | this.update();
234 |
235 | this._state = this._STATES.NONE;
236 |
237 | };
238 |
239 | // this method is exposed, but perhaps it would be better if we can make it private...
240 | update () {
241 | const panDelta = new Vector3();
242 | const oldPanCurrent = new Vector3();
243 | const position = this.camera.position;
244 |
245 | // move target to panned location
246 | oldPanCurrent.copy(this._panCurrent);
247 | this._panCurrent.lerp( this._panTarget, this.panDampingAlpha );
248 | panDelta.subVectors(this._panCurrent, oldPanCurrent);
249 |
250 | switch (this.mode) {
251 | case 'plane':
252 | this._maxZoomPosition.add(panDelta);
253 | this._minZoomPosition.add(panDelta);
254 | break;
255 | case 'sphere':
256 | const v = new Vector3();
257 | const quat = new Quaternion();
258 |
259 | quat.setFromAxisAngle(v.setFromMatrixColumn( this.camera.matrix, 1 ), panDelta.x);
260 |
261 | this._maxZoomPosition.applyQuaternion(quat);
262 | this._minZoomPosition.applyQuaternion(quat);
263 |
264 | quat.setFromAxisAngle(v.setFromMatrixColumn( this.camera.matrix, 0 ), panDelta.y);
265 |
266 | this._maxZoomPosition.applyQuaternion(quat);
267 | this._minZoomPosition.applyQuaternion(quat);
268 |
269 | //panDelta.z is only used for zoomToFit
270 | //all pan operations rotate around the camera's MatrixColumn axes, while zoomToFit needs to
271 | //rotate about the world Y-axis
272 | quat.setFromAxisAngle(new Vector3(0,1,0), panDelta.z);
273 | this._maxZoomPosition.applyQuaternion(quat);
274 | this._minZoomPosition.applyQuaternion(quat);
275 |
276 | break;
277 | }
278 |
279 | position.lerpVectors(this._minZoomPosition, this._maxZoomPosition, this._updateZoomAlpha());
280 |
281 | if(this.mode == 'sphere'){
282 | this.camera.lookAt(this.target.center);
283 | }
284 | }
285 |
286 | dispose () {
287 | Object.keys(this._eventListeners).forEach(_e =>{
288 | this.domElement.removeEventListener(_e, this._eventListeners[_e], false);
289 | });
290 | };
291 |
292 | zoomToFit (mesh, center, dims){
293 |
294 | if(center === undefined){
295 | center = mesh.geometry.boundingSphere.center.clone();
296 | }
297 |
298 | center = mesh.localToWorld(center.clone());
299 |
300 | if(dims === undefined){
301 | const diameter = (mesh.geometry.boundingSphere.radius * 2);
302 | dims = new Vector2(
303 | diameter,
304 | diameter
305 | );
306 | }
307 |
308 | switch(this.mode){
309 | case 'plane':
310 | this._panTarget.copy(center);
311 | this._panCurrent.copy(this._intersectCameraTarget().intersection);
312 | break;
313 | case 'sphere':
314 | const targetCoord = this._sphericalCoordinatesFrom(center);
315 | const camCoord = this._sphericalCoordinatesFrom(this.camera.position);
316 | const delta = new Vector2().subVectors(targetCoord, camCoord);
317 |
318 | //Handle wrapping around the antimeridian; the line of 2π (or 0) radians
319 | if(Math.abs(delta.x) > Math.PI){
320 | delta.x = (-Math.abs(delta.x) / delta.x) * ((Math.PI * 2) - Math.abs(delta.x));
321 | }
322 |
323 | this._panTarget.add(new Vector3(0, -delta.y, delta.x));
324 | break;
325 | }
326 |
327 | this._straightDollyTrack();
328 |
329 | const vFOV = this.camera.fov * (Math.PI / 180);
330 | const hFOV = 2 * Math.atan( Math.tan( vFOV / 2 ) * this.camera.aspect );
331 | const obj_aspect = dims.x / dims.y;
332 |
333 | this._finalTargetDistance = ((((obj_aspect > this.camera.aspect)? dims.x : dims.y) / 2) / Math.tan(((obj_aspect > this.camera.aspect)? hFOV : vFOV) / 2));
334 |
335 |
336 | };
337 |
338 | //returns a bounding box denoting the visible target area
339 | targetAreaVisible(){
340 |
341 | let bbox, vOffset, hOffset, center;
342 |
343 | switch(this.mode){
344 | case 'plane':
345 | var ray = new Ray(this.camera.position, this._camOrientation);
346 | var depth = ray.distanceToPlane(this.target);
347 |
348 | center = this.camera.position.clone();
349 |
350 | vOffset = this._screenWorldXform * depth;
351 | hOffset = vOffset * this.camera.aspect;
352 |
353 | bbox = new Box2(
354 | new Vector2(center.x - hOffset, center.y - vOffset),
355 | new Vector2(center.x + hOffset, center.y + vOffset)
356 | );
357 |
358 | break;
359 | case 'sphere':
360 | const cam_pos = (new Vector3()).subVectors(this.target.center, this.camera.position);
361 | center = this._sphericalCoordinatesFrom(this.camera.position);
362 |
363 | const halfPi = Math.PI / 2;
364 |
365 | const d = cam_pos.length();
366 |
367 | //Derived from solving the Haversine formula for Phi_2 when all other variables
368 | //(d, r, Theta_1, Theta_2, Phi_1) are given
369 | vOffset = this._screenWorldXform * ((d / this.target.radius) - 1);
370 | vOffset = Math.min(vOffset, halfPi);
371 |
372 | //Account for the aspect ratio of the screen, and the deformation of the sphere
373 | const r = this.target.radius * Math.cos(center.y - halfPi);
374 | hOffset = vOffset * this.camera.aspect * ( this.target.radius / r);
375 | hOffset = Math.min(hOffset, halfPi);
376 |
377 | bbox = new Box2(
378 | new Vector2(center.x - hOffset - halfPi, center.y - vOffset - halfPi),
379 | new Vector2(center.x + hOffset - halfPi, center.y + vOffset - halfPi)
380 | );
381 |
382 | ['min', 'max'].forEach(_mm => {
383 | bbox[_mm].x = (bbox[_mm].x > Math.PI)? (-2*Math.PI + bbox[_mm].x): bbox[_mm].x;
384 | });
385 |
386 | break;
387 | };
388 |
389 | return bbox;
390 | }
391 |
392 | targetAreaVisibleDeg() {
393 | let bbox = this.targetAreaVisible();
394 | if(this.mode == 'sphere'){
395 | bbox['min'].x = bbox['min'].x * (180/Math.PI);
396 | bbox['min'].y = bbox['min'].y * (180/Math.PI);
397 | bbox['max'].x = bbox['max'].x * (180/Math.PI);
398 | bbox['max'].y = bbox['max'].y * (180/Math.PI);
399 | }
400 | return bbox;
401 | }
402 |
403 | _sphericalCoordinatesFrom (cartesian_vec) {
404 | const rel_pos = ((new Vector3()).subVectors(this.target.center, cartesian_vec));
405 | const rel_xzcomponent = new Vector3(rel_pos.x, 0, rel_pos.z);
406 |
407 | const v = new Vector3();
408 | const sphCoord = new Vector2(
409 | rel_xzcomponent.angleTo(new Vector3(1,0,0)),
410 | rel_pos.angleTo(new Vector3(0,1,0))
411 | );
412 | sphCoord.x = (rel_pos.z > 0)? (2*Math.PI - sphCoord.x) : sphCoord.x;
413 | return sphCoord;
414 | }
415 |
416 | _updateZoomAlpha(){
417 | this._finalTargetDistance = Math.max( this.minDistance, Math.min( this.maxDistance, this._finalTargetDistance ) );
418 | var diff = this._currentTargetDistance - this._finalTargetDistance;
419 | var damping_alpha = this.zoomDampingAlpha;
420 | this._currentTargetDistance -= diff * damping_alpha;
421 | var rounding_places = 100000;
422 | this._zoomAlpha = Math.abs(Math.round((1 - ((this._currentTargetDistance - this.minDistance) / (this.maxDistance - this.minDistance))) * rounding_places ) / rounding_places);
423 |
424 | return this._zoomAlpha;
425 | }
426 |
427 | _updateDollyTrack(ray){
428 | let intersect = new Vector3();
429 |
430 | switch(this.mode){
431 | case 'plane':
432 | ray.intersectPlane(this.target, intersect);
433 | break;
434 | case 'sphere':
435 | ray.intersectSphere(this.target, intersect);
436 | break;
437 | }
438 |
439 | if(intersect){
440 | this._maxZoomPosition.addVectors(intersect, new Vector3().subVectors(this.camera.position, intersect).normalize().multiplyScalar(this.minDistance));
441 | this._minZoomPosition.copy(this._calculateMinZoom(this.camera.position, intersect));
442 |
443 | this._finalTargetDistance = this._currentTargetDistance = intersect.clone().sub(this.camera.position).length();
444 | }
445 | }
446 |
447 | _getZoomScale(speed) {
448 | speed = speed || this.zoomSpeed;
449 | return Math.pow( 0.95, speed );
450 | }
451 |
452 | _panLeft( distance, cameraMatrix ) {
453 | var v = new Vector3();
454 |
455 | switch(this.mode){
456 | case 'sphere':
457 | v.set(- distance, 0, 0);
458 | break;
459 | case 'plane':
460 | v.setFromMatrixColumn( cameraMatrix, 0 ); // get Y column of cameraMatrix
461 | v.multiplyScalar( - distance );
462 | break;
463 | }
464 |
465 | this._panTarget.add( v );
466 | }
467 |
468 | _panUp ( distance, cameraMatrix ) {
469 | var v = new Vector3();
470 |
471 | switch(this.mode){
472 | case 'sphere':
473 | v.set(0, - distance, 0);
474 | break;
475 | case 'plane':
476 | v.setFromMatrixColumn( cameraMatrix, 1 ); // get Y column of cameraMatrix
477 | v.multiplyScalar( distance );
478 | break;
479 | }
480 |
481 | this._panTarget.add( v );
482 | }
483 |
484 | // deltaX and deltaY are in pixels; right and down are positive
485 | _pan (deltaX, deltaY) {
486 | var element = this.domElement;
487 |
488 | var r = new Ray(this.camera.position, this._camOrientation);
489 | var targetDistance;
490 |
491 | switch(this.mode){
492 | case 'plane':
493 | targetDistance = this._screenWorldXform * r.distanceToPlane(this.target);
494 | break;
495 | case 'sphere':
496 | //in spherical mode the pan coords are saved as radians and used as rotation angles
497 | const camToTarget = (new Vector3()).subVectors(this.camera.position, this.target.center);
498 | targetDistance = this._screenWorldXform * ((camToTarget.length() / this.target.radius) - 1);
499 | break;
500 | }
501 |
502 | // we actually don't use screenWidth, since perspective camera is fixed to screen height
503 | this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.camera.matrix );
504 | this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.camera.matrix );
505 |
506 | }
507 |
508 | _dollyIn( dollyScale ) {
509 | if ( this._cameraOfKnownType() ) {
510 | this._finalTargetDistance /= dollyScale;
511 | } else {
512 | console.warn( 'WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled.' );
513 | this.enableZoom = false;
514 | }
515 | }
516 |
517 | _dollyOut( dollyScale ) {
518 | if ( this._cameraOfKnownType() ) {
519 | this._finalTargetDistance *= dollyScale;
520 | } else {
521 | console.warn( 'WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled.' );
522 | this.enableZoom = false;
523 | }
524 | }
525 |
526 | _cameraOfKnownType() {
527 | return this.camera.type === 'PerspectiveCamera'
528 | }
529 |
530 | _handleUpdateDollyTrackMouse(event){
531 | var prevMouse = this._mouse.clone();
532 | this._mouse.set(( event.offsetX / this.domElement.clientWidth ) * 2 - 1, - ( event.offsetY / this.domElement.clientHeight ) * 2 + 1);
533 |
534 | if(!prevMouse.equals(this._mouse)){
535 | var rc = new Raycaster();
536 | rc.setFromCamera(this._mouse, this.camera);
537 | this._updateDollyTrack(rc.ray);
538 | }
539 | }
540 |
541 | _handleMouseDownDolly( event ) {
542 | this._handleUpdateDollyTrackMouse(event);
543 | this._dollyStart.set( event.offsetX, event.offsetY );
544 | }
545 |
546 | _handleMouseDownPan( event ) {
547 |
548 | this._panStart.set( event.offsetX, event.offsetY );
549 |
550 | }
551 |
552 | _handleMouseMoveDolly( event ) {
553 |
554 | this._handleUpdateDollyTrackMouse(event);
555 |
556 | //console.log( 'handleMouseMoveDolly' );
557 |
558 | this._dollyEnd.set( event.offsetX, event.offsetY );
559 |
560 | this._dollyDelta.subVectors(this._dollyEnd, this._dollyStart );
561 |
562 | if ( this._dollyDelta.y > 0 ) {
563 |
564 | this._dollyIn( this._getZoomScale() );
565 |
566 | } else if ( this._dollyDelta.y < 0 ) {
567 |
568 | this._dollyOut( this._getZoomScale() );
569 |
570 | }
571 |
572 | this._dollyStart.copy( this._dollyEnd );
573 |
574 | this.update();
575 |
576 | }
577 |
578 | _handleMouseMovePan( event ) {
579 |
580 | //console.log( 'handleMouseMovePan' );
581 |
582 | this._panEnd.set( event.offsetX, event.offsetY );
583 |
584 | this._panDelta.subVectors( this._panEnd, this._panStart );
585 |
586 | this._pan( this._panDelta.x, this._panDelta.y );
587 |
588 | this._panStart.copy( this._panEnd );
589 |
590 | this.update();
591 |
592 | }
593 |
594 | _handleMouseUp( event ) {
595 |
596 | //console.log( 'handleMouseUp' );
597 |
598 | }
599 |
600 | _calculateMinZoom(cam_pos, map_intersect){
601 | return map_intersect.clone().add(
602 | cam_pos.clone()
603 | .sub(map_intersect)
604 | .normalize()
605 | .multiplyScalar(this.maxDistance)
606 | );
607 | }
608 |
609 |
610 | _handleMouseWheel( event ) {
611 | this._handleUpdateDollyTrackMouse(event);
612 |
613 | var delta = 0;
614 |
615 | if ( event.wheelDelta !== undefined ) {
616 |
617 | // WebKit / Opera / Explorer 9
618 |
619 | delta = event.wheelDelta;
620 |
621 | } else if ( event.detail !== undefined ) {
622 |
623 | // Firefox
624 |
625 | delta = - event.detail;
626 |
627 | }
628 |
629 | if ( delta > 0 ) {
630 | this._dollyOut( this._getZoomScale() );
631 | } else if ( delta < 0 ) {
632 | this._dollyIn( this._getZoomScale() );
633 | }
634 |
635 | this.update();
636 | }
637 |
638 | _handleKeyDown( event ) {
639 |
640 | //console.log( 'handleKeyDown' );
641 |
642 | switch ( event.key ) {
643 |
644 | case this.keys.PAN_UP:
645 | this._pan( 0, this.keyPanSpeed );
646 | this.update();
647 | break;
648 |
649 | case this.keys.PAN_BOTTOM:
650 | this._pan( 0, - this.keyPanSpeed );
651 | this.update();
652 | break;
653 |
654 | case this.keys.PAN_LEFT:
655 | this._pan( this.keyPanSpeed, 0 );
656 | this.update();
657 | break;
658 |
659 | case this.keys.PAN_RIGHT:
660 | this._pan( - this.keyPanSpeed, 0 );
661 | this.update();
662 | break;
663 |
664 | case this.keys.ZOOM_IN:
665 | this._dollyIn( this._getZoomScale(this.keyZoomSpeed) )
666 | this.update();
667 | break;
668 |
669 | case this.keys.ZOOM_OUT:
670 | this._dollyOut( this._getZoomScale(this.keyZoomSpeed) )
671 | this.update();
672 | break;
673 |
674 | }
675 | }
676 |
677 | _handleUpdateDollyTrackTouch( event ){
678 | var centerpoint = new Vector2();
679 |
680 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
681 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
682 |
683 | centerpoint.x = event.touches[ 0 ].pageX + (dx / 2);
684 | centerpoint.y = event.touches[ 0 ].pageY + (dy / 2);
685 |
686 | var mouse = new Vector2();
687 | mouse.x = ( centerpoint.x / domElement.clientWidth ) * 2 - 1;
688 | mouse.y = - ( centerpoint.y / domElement.clientHeight ) * 2 + 1;
689 |
690 | this._updateDollyTrack(mouse);
691 | }
692 |
693 | _handleTouchStartDolly( event ) {
694 | this._handleUpdateDollyTrackTouch(event);
695 |
696 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
697 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
698 |
699 | var distance = Math.sqrt( dx * dx + dy * dy );
700 |
701 | this._dollyStart.set( 0, distance );
702 |
703 | }
704 |
705 | _handleTouchStartPan( event ) {
706 |
707 | //console.log( 'handleTouchStartPan' );
708 |
709 | this._panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
710 |
711 | }
712 |
713 |
714 | _handleTouchMoveDolly( event ) {
715 | this._handleUpdateDollyTrackTouch(event);
716 |
717 | //console.log( 'handleTouchMoveDolly' );
718 |
719 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
720 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
721 |
722 | var distance = Math.sqrt( dx * dx + dy * dy );
723 |
724 | this._dollyEnd.set( 0, distance );
725 |
726 | this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart );
727 |
728 | if ( this._dollyDelta.y > 0 ) {
729 |
730 | this._dollyOut( this._getZoomScale() );
731 |
732 | } else if ( this._dollyDelta.y < 0 ) {
733 |
734 | this._dollyIn( this._getZoomScale() );
735 |
736 | }
737 |
738 | this._dollyStart.copy( this._dollyEnd );
739 |
740 | this.update();
741 |
742 | }
743 |
744 | _handleTouchMovePan( event ) {
745 |
746 | this._panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
747 |
748 | this._panDelta.subVectors( this._panEnd, this._panStart );
749 |
750 | this._pan( this._panDelta.x, this._panDelta.y );
751 |
752 | this._panStart.copy( this._panEnd );
753 |
754 | this.update();
755 |
756 | }
757 |
758 | _handleTouchEnd( event ) {
759 | //console.log( 'handleTouchEnd' );
760 | }
761 |
762 | //
763 | // event handlers - FSM: listen for events and reset state
764 | //
765 |
766 | _onMouseDown( event ) {
767 |
768 | if ( this.enabled === false ) return;
769 |
770 | event.preventDefault();
771 |
772 | if ( event.button === this.mouseButtons.ZOOM ) {
773 |
774 | if ( this.enableZoom === false ) return;
775 |
776 | this._handleMouseDownDolly( event );
777 |
778 | this._state = this._STATES.DOLLY;
779 |
780 | } else if ( event.button === this.mouseButtons.PAN ) {
781 |
782 | if ( this.enablePan === false ) return;
783 |
784 | this._handleMouseDownPan( event );
785 |
786 | this._state = this._STATES.PAN;
787 |
788 | }
789 |
790 | if ( this._state !== this._STATES.NONE ) {
791 |
792 | this.domElement.addEventListener( 'mousemove', this._eventListeners.mousemove, false );
793 | this.domElement.addEventListener( 'mouseup', this._eventListeners.mouseup, false );
794 |
795 | this.dispatchEvent( this._startEvent );
796 |
797 | }
798 |
799 | }
800 |
801 | _onMouseMove( event ) {
802 |
803 | if ( this.enabled === false ) return;
804 |
805 | event.preventDefault();
806 |
807 | if ( this._state === this._STATES.DOLLY ) {
808 |
809 | if ( this.enableZoom === false ) return;
810 |
811 | this._handleMouseMoveDolly( event );
812 |
813 | } else if ( this._state === this._STATES.PAN ) {
814 |
815 | if ( this.enablePan === false ) return;
816 |
817 | this._handleMouseMovePan( event );
818 | }
819 | }
820 |
821 | _onMouseUp( event ) {
822 |
823 | if ( this.enabled === false ) return;
824 |
825 | this._handleMouseUp( event );
826 |
827 | this.domElement.removeEventListener( 'mousemove', this._eventListeners.mousemove, false );
828 | this.domElement.removeEventListener( 'mouseup', this._eventListeners.mouseup, false );
829 |
830 | this.dispatchEvent( this._endEvent );
831 |
832 | this._state = this._STATES.NONE;
833 |
834 | }
835 |
836 | _onMouseWheel( event ) {
837 | if ( this.enabled === false || this.enableZoom === false || ( this._state !== this._STATES.NONE ) ) return;
838 |
839 | event.preventDefault();
840 | event.stopPropagation();
841 |
842 | this._handleMouseWheel( event );
843 |
844 | this.dispatchEvent( this._startEvent ); // not sure why these are here...
845 | this.dispatchEvent( this._endEvent );
846 |
847 | }
848 |
849 | _onKeyDown( event ) {
850 | if ( this.enabled === false || this.enableKeys === false || this.enablePan === false ) return;
851 |
852 | this._handleKeyDown( event );
853 | }
854 |
855 | _onTouchStart( event ) {
856 |
857 | if ( this.enabled === false ) return;
858 |
859 | switch ( event.touches.length ) {
860 | case 1: // three-fingered touch: pan
861 |
862 | if ( this.enablePan === false ) return;
863 |
864 | this._handleTouchStartPan( event );
865 |
866 | this._state = this._STATES.TOUCH_PAN;
867 |
868 | break;
869 |
870 | case 2: // two-fingered touch: dolly
871 |
872 | if ( this.enableZoom === false ) return;
873 |
874 | this._handleTouchStartDolly( event );
875 |
876 | this._state = this._STATES.TOUCH_DOLLY;
877 |
878 | break;
879 |
880 | default:
881 |
882 | this._state = this._STATES.NONE;
883 |
884 | }
885 |
886 | if ( this._state !== this._STATES.NONE ) {
887 |
888 | this.dispatchEvent( this._startEvent );
889 |
890 | }
891 |
892 | }
893 |
894 | _onTouchMove( event ) {
895 |
896 | if ( this.enabled === false ) return;
897 |
898 | event.preventDefault();
899 | event.stopPropagation();
900 |
901 | switch ( event.touches.length ) {
902 |
903 | case 1: // one-fingered touch: pan
904 | if ( this.enablePan === false ) return;
905 | if ( this._state !== this._STATES.TOUCH_PAN ) return; // is this needed?...
906 |
907 | this._handleTouchMovePan( event );
908 |
909 | break;
910 |
911 | case 2: // two-fingered touch: dolly
912 |
913 | if ( this.enableZoom === false ) return;
914 | if ( this._state !== this._STATES.TOUCH_DOLLY ) return; // is this needed?...
915 |
916 | this._handleTouchMoveDolly( event );
917 |
918 | break;
919 |
920 | default:
921 |
922 | this._state = this._STATES.NONE;
923 |
924 | }
925 |
926 | }
927 |
928 | _onTouchEnd( event ) {
929 |
930 | if ( this.enabled === false ) return;
931 |
932 | this._handleTouchEnd( event );
933 |
934 | this.dispatchEvent( this._endEvent );
935 |
936 | this._state = this._STATES.NONE;
937 |
938 | }
939 |
940 | _onContextMenu( event ) {
941 | event.preventDefault();
942 | }
943 |
944 | _onMouseOver ( event ) {
945 | this.domElement.focus();
946 | return false;
947 | }
948 |
949 | _onMouseOut ( event ) {
950 | this.domElement.blur();
951 | return false;
952 | }
953 |
954 | };
955 |
956 | export default MapControls;
957 |
--------------------------------------------------------------------------------
/dist/three-map-controls.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///delegated ./node_modules/three/build/three.module.js from dll-reference vendor","webpack:///./src/three-map-controls.js","webpack:///./test/stub_dom.js","webpack:///external \"vendor\""],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","r","value","n","__esModule","object","property","prototype","hasOwnProperty","p","s","__webpack_exports__","three__WEBPACK_IMPORTED_MODULE_0__","window","require","MapControls","EventDispatcher","constructor","camera","domElement","options","super","this","undefined","document","body","enabled","target","minDistance","maxDistance","enableZoom","zoomSpeed","zoomDampingAlpha","initialZoom","enablePan","keyPanSpeed","keyZoomSpeed","panDampingAlpha","enableKeys","keys","PAN_LEFT","PAN_UP","PAN_RIGHT","PAN_BOTTOM","ZOOM_IN","ZOOM_OUT","mouseButtons","ZOOM","MOUSE","MIDDLE","PAN","LEFT","assign","isTargetValid","mode","Error","normal","constant","center","radius","_eventListeners","contextmenu","_onContextMenu","bind","mousedown","_onMouseDown","mousewheel","_onMouseWheel","MozMousePixelScroll","touchstart","_onTouchStart","touchend","_onTouchEnd","touchmove","_onTouchMove","keydown","_onKeyDown","mouseover","_onMouseOver","mouseout","_onMouseOut","mousemove","_onMouseMove","mouseup","_onMouseUp","_init","target0","clone","position0","position","zoom0","zoom","_changeEvent","type","_startEvent","_endEvent","_STATES","NONE","DOLLY","TOUCH_DOLLY","TOUCH_PAN","distanceToPoint","_state","_mouse","Vector2","_finalTargetDistance","_currentTargetDistance","_panTarget","Vector3","_panCurrent","_minZoomPosition","_maxZoomPosition","_panStart","_panEnd","_panDelta","_dollyStart","_dollyEnd","_dollyDelta","_camOrientation","_zoomAlpha","_screenWorldXform","Math","tan","fov","PI","_straightDollyTrack","lerpVectors","abs","res","_intersectCameraTarget","lookAt","intersection","ray","direction","normalize","_updateZoomAlpha","forEach","_e","addEventListener","tagName","getAttribute","setAttribute","update","coplanar","projectPoint","Ray","subVectors","intersectPlane","intersectSphere","_updateDollyTrack","getZoomAlpha","reset","copy","updateProjectionMatrix","dispatchEvent","panDelta","oldPanCurrent","lerp","add","v","quat","Quaternion","setFromAxisAngle","setFromMatrixColumn","matrix","x","applyQuaternion","y","z","dispose","removeEventListener","zoomToFit","mesh","dims","geometry","boundingSphere","localToWorld","diameter","targetCoord","_sphericalCoordinatesFrom","camCoord","delta","vFOV","hFOV","atan","aspect","obj_aspect","targetAreaVisible","bbox","vOffset","hOffset","depth","distanceToPlane","Box2","cam_pos","halfPi","length","min","cos","_mm","targetAreaVisibleDeg","cartesian_vec","rel_pos","rel_xzcomponent","sphCoord","angleTo","max","diff","damping_alpha","round","intersect","addVectors","multiplyScalar","_calculateMinZoom","sub","_getZoomScale","speed","pow","_panLeft","distance","cameraMatrix","set","_panUp","_pan","deltaX","deltaY","targetDistance","element","camToTarget","clientHeight","_dollyIn","dollyScale","_cameraOfKnownType","console","warn","_dollyOut","_handleUpdateDollyTrackMouse","event","prevMouse","offsetX","clientWidth","offsetY","equals","rc","Raycaster","setFromCamera","_handleMouseDownDolly","_handleMouseDownPan","_handleMouseMoveDolly","_handleMouseMovePan","_handleMouseUp","map_intersect","_handleMouseWheel","wheelDelta","detail","_handleKeyDown","key","_handleUpdateDollyTrackTouch","centerpoint","dx","touches","pageX","dy","pageY","mouse","_handleTouchStartDolly","sqrt","_handleTouchStartPan","_handleTouchMoveDolly","_handleTouchMovePan","_handleTouchEnd","preventDefault","button","stopPropagation","focus","blur","log","vendor"],"mappings":"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","file":"three-map-controls.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 1);\n","module.exports = (__webpack_require__(3))(0);","'use strict';\n\n//Alex Pilafian 2016-2019 - sikanrong@gmail.com\n\nimport {\n Box2,\n Quaternion,\n EventDispatcher,\n Vector2,\n Vector3,\n Raycaster,\n Ray,\n MOUSE\n} from 'three'\n\n//test stubs\nif(typeof window == 'undefined'){\n let window = require('../test/stub_dom');\n}\n\nclass MapControls extends EventDispatcher{\n\n constructor(camera, domElement, options){\n super();\n\n this.camera = camera;\n\n //Object to use for listening for keyboard/mouse events\n this.domElement = ( domElement !== undefined ) ? domElement : window.document.body;\n\n // Set to false to disable this control (Disables all input events)\n this.enabled = true;\n\n // Must be set to instance of Plane or Sphere\n this.target;\n\n // How far you can dolly in and out\n this.minDistance = 1; //probably should never be 0\n this.maxDistance = 100;\n\n // This option actually enables dollying in and out; left as \"zoom\" for backwards compatibility.\n // Set to false to disable zooming\n this.enableZoom = true;\n this.zoomSpeed = 6.0;\n this.zoomDampingAlpha = 0.1;\n this.initialZoom = 0; //start zoomed all the way out unless set in options.\n\n // Set to false to disable panning\n this.enablePan = true;\n this.keyPanSpeed = 50.0;\t// pixels moved per arrow key push\n this.keyZoomSpeed = this.zoomSpeed;\t// keyboard zoom speed, defaults to mouse-wheel zoom speed\n this.panDampingAlpha = 0.1;\n\n // Set to false to disable use of the keys\n this.enableKeys = true;\n\n // The four arrow keys, and two zoom keys\n this.keys = {\n PAN_LEFT: \"ArrowLeft\",\n PAN_UP: \"ArrowUp\",\n PAN_RIGHT: \"ArrowRight\",\n PAN_BOTTOM: \"ArrowDown\",\n ZOOM_IN: \"]\",\n ZOOM_OUT: \"[\"\n };\n\n // Mouse buttons\n this.mouseButtons = { ZOOM: MOUSE.MIDDLE, PAN: MOUSE.LEFT };\n \n //Copy options from parameters\n Object.assign(this, options);\n let isTargetValid = false;\n\n if(this.mode === undefined){\n throw new Error('\\'mode\\' option must be set to either \\'plane\\' or \\'sphere\\'');\n }\n\n switch(this.mode){\n case 'plane':\n isTargetValid = (this.target.normal !== undefined && this.target.constant !== undefined);\n break;\n case 'sphere':\n isTargetValid = (this.target.center !== undefined && this.target.radius !== undefined);\n break;\n }\n\n if(!isTargetValid){\n throw new Error('\\'target\\' option must be an instance of type THREE.Plane or THREE.Sphere');\n }\n\n this._eventListeners = {\n 'contextmenu': this._onContextMenu.bind(this),\n 'mousedown': this._onMouseDown.bind(this),\n 'mousewheel': this._onMouseWheel.bind(this),\n 'MozMousePixelScroll': this._onMouseWheel.bind(this),\n 'touchstart': this._onTouchStart.bind(this),\n 'touchend': this._onTouchEnd.bind(this),\n 'touchmove': this._onTouchMove.bind(this),\n 'keydown': this._onKeyDown.bind(this),\n 'mouseover': this._onMouseOver.bind(this),\n 'mouseout': this._onMouseOut.bind(this),\n 'mousemove': this._onMouseMove.bind(this),\n 'mouseup': this._onMouseUp.bind(this)\n };\n\n this._init();\n }\n\n _init (){\n\n this.target0 = this.target.clone();\n this.position0 = this.camera.position.clone();\n this.zoom0 = this.camera.zoom;\n this._changeEvent = { type: 'change' };\n this._startEvent = { type: 'start' };\n this._endEvent = { type: 'end' };\n\n this._STATES = { NONE : - 1, DOLLY : 1, PAN : 2, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };\n\n if(this.target.distanceToPoint(this.camera.position) == 0){\n throw new Error(\"ORIENTATION_UNKNOWABLE: initial Camera position cannot intersect target plane.\");\n }\n\n this._state = this._STATES.NONE;\n\n this._mouse = new Vector2();\n\n this._finalTargetDistance = 0;\n this._currentTargetDistance = 0;\n\n this._panTarget = new Vector3(0,0,0);\n this._panCurrent = new Vector3(0,0,0);\n\n this._minZoomPosition = new Vector3();\n this._maxZoomPosition = new Vector3();\n\n this._panStart = new Vector2();\n this._panEnd = new Vector2();\n this._panDelta = new Vector2();\n\n this._dollyStart = new Vector2();\n this._dollyEnd = new Vector2();\n this._dollyDelta = new Vector2();\n\n this._camOrientation = new Vector2();\n\n this._zoomAlpha;\n\n this._screenWorldXform = Math.tan( ( this.camera.fov / 2 ) * Math.PI / 180.0 );\n\n //establish initial camera orientation based on position w.r.t. _this.target plane\n this._straightDollyTrack();\n\n this.camera.position.lerpVectors(this._minZoomPosition, this._maxZoomPosition, this.initialZoom);\n this._finalTargetDistance = this._currentTargetDistance = Math.abs(this.target.distanceToPoint(this.camera.position));\n\n const res = this._intersectCameraTarget();\n this.camera.lookAt(res.intersection); //set the orientation of the camera towards the map.\n this._camOrientation = res.ray.direction.clone().normalize();\n\n this._updateZoomAlpha();\n\n //Assign event listeners\n\n [\n 'contextmenu',\n 'mousedown',\n 'mousewheel',\n 'MozMousePixelScroll',\n 'touchstart',\n 'touchend',\n 'touchmove',\n 'mouseover',\n 'mouseout',\n 'keydown'\n ].forEach(_e => {\n this.domElement.addEventListener(_e, this._eventListeners[_e], false);\n });\n\n if(this.domElement.tagName == 'CANVAS' &&\n !this.domElement.getAttribute('tabindex')){\n //if we're dealing with a canvas element which has no tabindex,\n //give it one so that it may recieve keyboard focus\n this.domElement.setAttribute('tabindex', '1');\n }\n\n this.update();\n }\n\n _intersectCameraTarget(){\n let intersection = new Vector3();\n let ray;\n\n switch(this.mode){\n case 'plane':\n const coplanar = new Vector3();\n this.target.projectPoint(this.camera.position, coplanar);\n ray = new Ray(this.camera.position, new Vector3().subVectors(coplanar, this.camera.position).normalize());\n ray.intersectPlane(this.target, intersection);\n break;\n case 'sphere':\n ray = new Ray(this.camera.position, (new Vector3()).subVectors(this.target.center, this.camera.position).normalize());\n ray.intersectSphere(this.target, intersection);\n break;\n }\n\n return {\n intersection: intersection,\n ray: ray\n }\n }\n\n _straightDollyTrack(){\n this._updateDollyTrack(this._intersectCameraTarget().ray);\n }\n\n getZoomAlpha () {\n return this._zoomAlpha;\n }\n\n reset () {\n\n this.target.copy( this.target0 );\n this.camera.position.copy( this.position0 );\n this.camera.zoom = this.zoom0;\n\n this.camera.updateProjectionMatrix();\n\n this._init(); //reinit\n\n this.dispatchEvent( this._changeEvent );\n\n this.update();\n\n this._state = this._STATES.NONE;\n\n };\n\n // this method is exposed, but perhaps it would be better if we can make it private...\n update () {\n const panDelta = new Vector3();\n const oldPanCurrent = new Vector3();\n const position = this.camera.position;\n\n // move target to panned location\n oldPanCurrent.copy(this._panCurrent);\n this._panCurrent.lerp( this._panTarget, this.panDampingAlpha );\n panDelta.subVectors(this._panCurrent, oldPanCurrent);\n\n switch (this.mode) {\n case 'plane':\n this._maxZoomPosition.add(panDelta);\n this._minZoomPosition.add(panDelta);\n break;\n case 'sphere':\n const v = new Vector3();\n const quat = new Quaternion();\n\n quat.setFromAxisAngle(v.setFromMatrixColumn( this.camera.matrix, 1 ), panDelta.x);\n\n this._maxZoomPosition.applyQuaternion(quat);\n this._minZoomPosition.applyQuaternion(quat);\n\n quat.setFromAxisAngle(v.setFromMatrixColumn( this.camera.matrix, 0 ), panDelta.y);\n\n this._maxZoomPosition.applyQuaternion(quat);\n this._minZoomPosition.applyQuaternion(quat);\n\n //panDelta.z is only used for zoomToFit\n //all pan operations rotate around the camera's MatrixColumn axes, while zoomToFit needs to\n //rotate about the world Y-axis\n quat.setFromAxisAngle(new Vector3(0,1,0), panDelta.z);\n this._maxZoomPosition.applyQuaternion(quat);\n this._minZoomPosition.applyQuaternion(quat);\n\n break;\n }\n\n position.lerpVectors(this._minZoomPosition, this._maxZoomPosition, this._updateZoomAlpha());\n\n if(this.mode == 'sphere'){\n this.camera.lookAt(this.target.center);\n }\n }\n\n dispose () {\n Object.keys(this._eventListeners).forEach(_e =>{\n this.domElement.removeEventListener(_e, this._eventListeners[_e], false);\n });\n };\n\n zoomToFit (mesh, center, dims){\n\n if(center === undefined){\n center = mesh.geometry.boundingSphere.center.clone();\n }\n\n center = mesh.localToWorld(center.clone());\n\n if(dims === undefined){\n const diameter = (mesh.geometry.boundingSphere.radius * 2);\n dims = new Vector2(\n diameter,\n diameter\n );\n }\n\n switch(this.mode){\n case 'plane':\n this._panTarget.copy(center);\n this._panCurrent.copy(this._intersectCameraTarget().intersection);\n break;\n case 'sphere':\n const targetCoord = this._sphericalCoordinatesFrom(center);\n const camCoord = this._sphericalCoordinatesFrom(this.camera.position);\n const delta = new Vector2().subVectors(targetCoord, camCoord);\n\n //Handle wrapping around the antimeridian; the line of 2π (or 0) radians\n if(Math.abs(delta.x) > Math.PI){\n delta.x = (-Math.abs(delta.x) / delta.x) * ((Math.PI * 2) - Math.abs(delta.x));\n }\n\n this._panTarget.add(new Vector3(0, -delta.y, delta.x));\n break;\n }\n\n this._straightDollyTrack();\n\n const vFOV = this.camera.fov * (Math.PI / 180);\n const hFOV = 2 * Math.atan( Math.tan( vFOV / 2 ) * this.camera.aspect );\n const obj_aspect = dims.x / dims.y;\n\n this._finalTargetDistance = ((((obj_aspect > this.camera.aspect)? dims.x : dims.y) / 2) / Math.tan(((obj_aspect > this.camera.aspect)? hFOV : vFOV) / 2));\n\n\n };\n\n //returns a bounding box denoting the visible target area\n targetAreaVisible(){\n\n let bbox, vOffset, hOffset, center;\n\n switch(this.mode){\n case 'plane':\n var ray = new Ray(this.camera.position, this._camOrientation);\n var depth = ray.distanceToPlane(this.target);\n\n center = this.camera.position.clone();\n\n vOffset = this._screenWorldXform * depth;\n hOffset = vOffset * this.camera.aspect;\n\n bbox = new Box2(\n new Vector2(center.x - hOffset, center.y - vOffset),\n new Vector2(center.x + hOffset, center.y + vOffset)\n );\n\n break;\n case 'sphere':\n const cam_pos = (new Vector3()).subVectors(this.target.center, this.camera.position);\n center = this._sphericalCoordinatesFrom(this.camera.position);\n\n const halfPi = Math.PI / 2;\n\n const d = cam_pos.length();\n\n //Derived from solving the Haversine formula for Phi_2 when all other variables\n //(d, r, Theta_1, Theta_2, Phi_1) are given\n vOffset = this._screenWorldXform * ((d / this.target.radius) - 1);\n vOffset = Math.min(vOffset, halfPi);\n\n //Account for the aspect ratio of the screen, and the deformation of the sphere\n const r = this.target.radius * Math.cos(center.y - halfPi);\n hOffset = vOffset * this.camera.aspect * ( this.target.radius / r);\n hOffset = Math.min(hOffset, halfPi);\n\n bbox = new Box2(\n new Vector2(center.x - hOffset - halfPi, center.y - vOffset - halfPi),\n new Vector2(center.x + hOffset - halfPi, center.y + vOffset - halfPi)\n );\n\n ['min', 'max'].forEach(_mm => {\n bbox[_mm].x = (bbox[_mm].x > Math.PI)? (-2*Math.PI + bbox[_mm].x): bbox[_mm].x;\n });\n\n break;\n };\n\n return bbox;\n }\n \n targetAreaVisibleDeg() {\n let bbox = this.targetAreaVisible();\n if(this.mode == 'sphere'){\n bbox['min'].x = bbox['min'].x * (180/Math.PI);\n bbox['min'].y = bbox['min'].y * (180/Math.PI);\n bbox['max'].x = bbox['max'].x * (180/Math.PI);\n bbox['max'].y = bbox['max'].y * (180/Math.PI);\n }\n return bbox;\n }\n\n _sphericalCoordinatesFrom (cartesian_vec) {\n const rel_pos = ((new Vector3()).subVectors(this.target.center, cartesian_vec));\n const rel_xzcomponent = new Vector3(rel_pos.x, 0, rel_pos.z);\n\n const v = new Vector3();\n const sphCoord = new Vector2(\n rel_xzcomponent.angleTo(new Vector3(1,0,0)),\n rel_pos.angleTo(new Vector3(0,1,0))\n );\n sphCoord.x = (rel_pos.z > 0)? (2*Math.PI - sphCoord.x) : sphCoord.x;\n return sphCoord;\n }\n\n _updateZoomAlpha(){\n this._finalTargetDistance = Math.max( this.minDistance, Math.min( this.maxDistance, this._finalTargetDistance ) );\n var diff = this._currentTargetDistance - this._finalTargetDistance;\n var damping_alpha = this.zoomDampingAlpha;\n this._currentTargetDistance -= diff * damping_alpha;\n var rounding_places = 100000;\n this._zoomAlpha = Math.abs(Math.round((1 - ((this._currentTargetDistance - this.minDistance) / (this.maxDistance - this.minDistance))) * rounding_places ) / rounding_places);\n\n return this._zoomAlpha;\n }\n\n _updateDollyTrack(ray){\n let intersect = new Vector3();\n\n switch(this.mode){\n case 'plane':\n ray.intersectPlane(this.target, intersect);\n break;\n case 'sphere':\n ray.intersectSphere(this.target, intersect);\n break;\n }\n\n if(intersect){\n this._maxZoomPosition.addVectors(intersect, new Vector3().subVectors(this.camera.position, intersect).normalize().multiplyScalar(this.minDistance));\n this._minZoomPosition.copy(this._calculateMinZoom(this.camera.position, intersect));\n\n this._finalTargetDistance = this._currentTargetDistance = intersect.clone().sub(this.camera.position).length();\n }\n }\n\n _getZoomScale(speed) {\n speed = speed || this.zoomSpeed;\n return Math.pow( 0.95, speed );\n }\n\n _panLeft( distance, cameraMatrix ) {\n var v = new Vector3();\n\n switch(this.mode){\n case 'sphere':\n v.set(- distance, 0, 0);\n break;\n case 'plane':\n v.setFromMatrixColumn( cameraMatrix, 0 ); // get Y column of cameraMatrix\n v.multiplyScalar( - distance );\n break;\n }\n\n this._panTarget.add( v );\n }\n\n _panUp ( distance, cameraMatrix ) {\n var v = new Vector3();\n\n switch(this.mode){\n case 'sphere':\n v.set(0, - distance, 0);\n break;\n case 'plane':\n v.setFromMatrixColumn( cameraMatrix, 1 ); // get Y column of cameraMatrix\n v.multiplyScalar( distance );\n break;\n }\n\n this._panTarget.add( v );\n }\n\n // deltaX and deltaY are in pixels; right and down are positive\n _pan (deltaX, deltaY) {\n var element = this.domElement;\n\n var r = new Ray(this.camera.position, this._camOrientation);\n var targetDistance;\n\n switch(this.mode){\n case 'plane':\n targetDistance = this._screenWorldXform * r.distanceToPlane(this.target);\n break;\n case 'sphere':\n //in spherical mode the pan coords are saved as radians and used as rotation angles\n const camToTarget = (new Vector3()).subVectors(this.camera.position, this.target.center);\n targetDistance = this._screenWorldXform * ((camToTarget.length() / this.target.radius) - 1);\n break;\n }\n\n // we actually don't use screenWidth, since perspective camera is fixed to screen height\n this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.camera.matrix );\n this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.camera.matrix );\n\n }\n\n _dollyIn( dollyScale ) {\n if ( this._cameraOfKnownType() ) {\n this._finalTargetDistance /= dollyScale;\n } else {\n console.warn( 'WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled.' );\n this.enableZoom = false;\n }\n }\n\n _dollyOut( dollyScale ) {\n if ( this._cameraOfKnownType() ) {\n this._finalTargetDistance *= dollyScale;\n } else {\n console.warn( 'WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled.' );\n this.enableZoom = false;\n }\n }\n\n _cameraOfKnownType() {\n return this.camera.type === 'PerspectiveCamera'\n }\n\n _handleUpdateDollyTrackMouse(event){\n var prevMouse = this._mouse.clone();\n this._mouse.set(( event.offsetX / this.domElement.clientWidth ) * 2 - 1, - ( event.offsetY / this.domElement.clientHeight ) * 2 + 1);\n\n if(!prevMouse.equals(this._mouse)){\n var rc = new Raycaster();\n rc.setFromCamera(this._mouse, this.camera);\n this._updateDollyTrack(rc.ray);\n }\n }\n\n _handleMouseDownDolly( event ) {\n this._handleUpdateDollyTrackMouse(event);\n this._dollyStart.set( event.offsetX, event.offsetY );\n }\n\n _handleMouseDownPan( event ) {\n\n this._panStart.set( event.offsetX, event.offsetY );\n\n }\n\n _handleMouseMoveDolly( event ) {\n\n this._handleUpdateDollyTrackMouse(event);\n\n //console.log( 'handleMouseMoveDolly' );\n\n this._dollyEnd.set( event.offsetX, event.offsetY );\n\n this._dollyDelta.subVectors(this._dollyEnd, this._dollyStart );\n\n if ( this._dollyDelta.y > 0 ) {\n\n this._dollyIn( this._getZoomScale() );\n\n } else if ( this._dollyDelta.y < 0 ) {\n\n this._dollyOut( this._getZoomScale() );\n\n }\n\n this._dollyStart.copy( this._dollyEnd );\n\n this.update();\n\n }\n\n _handleMouseMovePan( event ) {\n\n //console.log( 'handleMouseMovePan' );\n\n this._panEnd.set( event.offsetX, event.offsetY );\n\n this._panDelta.subVectors( this._panEnd, this._panStart );\n\n this._pan( this._panDelta.x, this._panDelta.y );\n\n this._panStart.copy( this._panEnd );\n\n this.update();\n\n }\n\n _handleMouseUp( event ) {\n\n //console.log( 'handleMouseUp' );\n\n }\n\n _calculateMinZoom(cam_pos, map_intersect){\n return map_intersect.clone().add(\n cam_pos.clone()\n .sub(map_intersect)\n .normalize()\n .multiplyScalar(this.maxDistance)\n );\n }\n\n\n _handleMouseWheel( event ) {\n this._handleUpdateDollyTrackMouse(event);\n\n var delta = 0;\n\n if ( event.wheelDelta !== undefined ) {\n\n // WebKit / Opera / Explorer 9\n\n delta = event.wheelDelta;\n\n } else if ( event.detail !== undefined ) {\n\n // Firefox\n\n delta = - event.detail;\n\n }\n\n if ( delta > 0 ) {\n this._dollyOut( this._getZoomScale() );\n } else if ( delta < 0 ) {\n this._dollyIn( this._getZoomScale() );\n }\n\n this.update();\n }\n\n _handleKeyDown( event ) {\n\n //console.log( 'handleKeyDown' );\n\n switch ( event.key ) {\n\n case this.keys.PAN_UP:\n this._pan( 0, this.keyPanSpeed );\n this.update();\n break;\n\n case this.keys.PAN_BOTTOM:\n this._pan( 0, - this.keyPanSpeed );\n this.update();\n break;\n\n case this.keys.PAN_LEFT:\n this._pan( this.keyPanSpeed, 0 );\n this.update();\n break;\n\n case this.keys.PAN_RIGHT:\n this._pan( - this.keyPanSpeed, 0 );\n this.update();\n break;\n\n case this.keys.ZOOM_IN:\n this._dollyIn( this._getZoomScale(this.keyZoomSpeed) )\n this.update();\n break;\n\n case this.keys.ZOOM_OUT:\n this._dollyOut( this._getZoomScale(this.keyZoomSpeed) )\n this.update();\n break;\n\n }\n }\n\n _handleUpdateDollyTrackTouch( event ){\n var centerpoint = new Vector2();\n\n var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;\n var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;\n\n centerpoint.x = event.touches[ 0 ].pageX + (dx / 2);\n centerpoint.y = event.touches[ 0 ].pageY + (dy / 2);\n\n var mouse = new Vector2();\n mouse.x = ( centerpoint.x / domElement.clientWidth ) * 2 - 1;\n mouse.y = - ( centerpoint.y / domElement.clientHeight ) * 2 + 1;\n\n this._updateDollyTrack(mouse);\n }\n\n _handleTouchStartDolly( event ) {\n this._handleUpdateDollyTrackTouch(event);\n\n var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;\n var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;\n\n var distance = Math.sqrt( dx * dx + dy * dy );\n\n this._dollyStart.set( 0, distance );\n\n }\n\n _handleTouchStartPan( event ) {\n\n //console.log( 'handleTouchStartPan' );\n\n this._panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );\n\n }\n\n\n _handleTouchMoveDolly( event ) {\n this._handleUpdateDollyTrackTouch(event);\n\n //console.log( 'handleTouchMoveDolly' );\n\n var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;\n var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;\n\n var distance = Math.sqrt( dx * dx + dy * dy );\n\n this._dollyEnd.set( 0, distance );\n\n this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart );\n\n if ( this._dollyDelta.y > 0 ) {\n\n this._dollyOut( this._getZoomScale() );\n\n } else if ( this._dollyDelta.y < 0 ) {\n\n this._dollyIn( this._getZoomScale() );\n\n }\n\n this._dollyStart.copy( this._dollyEnd );\n\n this.update();\n\n }\n\n _handleTouchMovePan( event ) {\n\n this._panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );\n\n this._panDelta.subVectors( this._panEnd, this._panStart );\n\n this._pan( this._panDelta.x, this._panDelta.y );\n\n this._panStart.copy( this._panEnd );\n\n this.update();\n\n }\n\n _handleTouchEnd( event ) {\n //console.log( 'handleTouchEnd' );\n }\n\n //\n // event handlers - FSM: listen for events and reset state\n //\n\n _onMouseDown( event ) {\n\n if ( this.enabled === false ) return;\n\n event.preventDefault();\n\n if ( event.button === this.mouseButtons.ZOOM ) {\n\n if ( this.enableZoom === false ) return;\n\n this._handleMouseDownDolly( event );\n\n this._state = this._STATES.DOLLY;\n\n } else if ( event.button === this.mouseButtons.PAN ) {\n\n if ( this.enablePan === false ) return;\n\n this._handleMouseDownPan( event );\n\n this._state = this._STATES.PAN;\n\n }\n\n if ( this._state !== this._STATES.NONE ) {\n\n this.domElement.addEventListener( 'mousemove', this._eventListeners.mousemove, false );\n this.domElement.addEventListener( 'mouseup', this._eventListeners.mouseup, false );\n\n this.dispatchEvent( this._startEvent );\n\n }\n\n }\n\n _onMouseMove( event ) {\n\n if ( this.enabled === false ) return;\n\n event.preventDefault();\n\n if ( this._state === this._STATES.DOLLY ) {\n\n if ( this.enableZoom === false ) return;\n\n this._handleMouseMoveDolly( event );\n\n } else if ( this._state === this._STATES.PAN ) {\n\n if ( this.enablePan === false ) return;\n\n this._handleMouseMovePan( event );\n }\n }\n\n _onMouseUp( event ) {\n\n if ( this.enabled === false ) return;\n\n this._handleMouseUp( event );\n\n this.domElement.removeEventListener( 'mousemove', this._eventListeners.mousemove, false );\n this.domElement.removeEventListener( 'mouseup', this._eventListeners.mouseup, false );\n\n this.dispatchEvent( this._endEvent );\n\n this._state = this._STATES.NONE;\n\n }\n\n _onMouseWheel( event ) {\n if ( this.enabled === false || this.enableZoom === false || ( this._state !== this._STATES.NONE ) ) return;\n\n event.preventDefault();\n event.stopPropagation();\n\n this._handleMouseWheel( event );\n\n this.dispatchEvent( this._startEvent ); // not sure why these are here...\n this.dispatchEvent( this._endEvent );\n\n }\n\n _onKeyDown( event ) {\n if ( this.enabled === false || this.enableKeys === false || this.enablePan === false ) return;\n\n this._handleKeyDown( event );\n }\n\n _onTouchStart( event ) {\n\n if ( this.enabled === false ) return;\n\n switch ( event.touches.length ) {\n case 1: // three-fingered touch: pan\n\n if ( this.enablePan === false ) return;\n\n this._handleTouchStartPan( event );\n\n this._state = this._STATES.TOUCH_PAN;\n\n break;\n\n case 2:\t// two-fingered touch: dolly\n\n if ( this.enableZoom === false ) return;\n\n this._handleTouchStartDolly( event );\n\n this._state = this._STATES.TOUCH_DOLLY;\n\n break;\n\n default:\n\n this._state = this._STATES.NONE;\n\n }\n\n if ( this._state !== this._STATES.NONE ) {\n\n this.dispatchEvent( this._startEvent );\n\n }\n\n }\n\n _onTouchMove( event ) {\n\n if ( this.enabled === false ) return;\n\n event.preventDefault();\n event.stopPropagation();\n\n switch ( event.touches.length ) {\n\n case 1: // one-fingered touch: pan\n if ( this.enablePan === false ) return;\n if ( this._state !== this._STATES.TOUCH_PAN ) return; // is this needed?...\n\n this._handleTouchMovePan( event );\n\n break;\n\n case 2: // two-fingered touch: dolly\n\n if ( this.enableZoom === false ) return;\n if ( this._state !== this._STATES.TOUCH_DOLLY ) return; // is this needed?...\n\n this._handleTouchMoveDolly( event );\n\n break;\n\n default:\n\n this._state = this._STATES.NONE;\n\n }\n\n }\n\n _onTouchEnd( event ) {\n\n if ( this.enabled === false ) return;\n\n this._handleTouchEnd( event );\n\n this.dispatchEvent( this._endEvent );\n\n this._state = this._STATES.NONE;\n\n }\n\n _onContextMenu( event ) {\n event.preventDefault();\n }\n\n _onMouseOver ( event ) {\n this.domElement.focus();\n return false;\n }\n\n _onMouseOut ( event ) {\n this.domElement.blur();\n return false;\n }\n\n};\n\nexport default MapControls;\n","module.exports = {\n console: {\n log: function () {}\n },\n document: {\n body: {\n clientWidth: 1920,\n clientHeight: 1080,\n addEventListener: function () {},\n removeEventListener: function () {}\n }\n }\n};\n","module.exports = vendor;"],"sourceRoot":""}
--------------------------------------------------------------------------------