├── .gitignore
├── README.md
├── simple-scene
├── zelda.jpg
├── snowy-night-street.jpg
├── index.html
└── js
│ └── scripts.js
├── perf-testing
├── index.html
├── snowing.html
└── js
│ ├── scripts.js
│ └── snowing.js
├── three-particles
├── index.html
├── snowing.html
└── js
│ ├── snowing.js
│ └── scripts.js
├── package.json
├── three.js.shader.html
├── three
├── DeviceOrientationControls.js
└── OrbitControls.js
└── obj_loader
├── webgl_loader_obj.html
└── js
└── OBJLoader.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | These are some simple three.js experiements I make. Nothing to see, move along...
--------------------------------------------------------------------------------
/simple-scene/zelda.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiya/three.js-samples/HEAD/simple-scene/zelda.jpg
--------------------------------------------------------------------------------
/simple-scene/snowy-night-street.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiya/three.js-samples/HEAD/simple-scene/snowy-night-street.jpg
--------------------------------------------------------------------------------
/simple-scene/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Particles
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/perf-testing/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Particles
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/three-particles/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Particles
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three.js-samples",
3 | "version": "0.0.1",
4 | "description": "A collection of three.js experiments.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/shiya/three.js-samples.git"
12 | },
13 | "author": "",
14 | "license": "MIT",
15 | "bugs": {
16 | "url": "https://github.com/shiya/three.js-samples/issues"
17 | },
18 | "homepage": "https://github.com/shiya/three.js-samples#readme"
19 | }
20 |
--------------------------------------------------------------------------------
/perf-testing/snowing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Particles
6 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/three-particles/snowing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Particles
6 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/three-particles/js/snowing.js:
--------------------------------------------------------------------------------
1 | var scene, camera, renderer, controls;
2 | var canvas = document.getElementById('canvas');
3 |
4 | // particles set up
5 | var particleCount = 1000;
6 | var particles = [];
7 |
8 | init();
9 | animate();
10 |
11 | function init() {
12 |
13 | scene = new THREE.Scene();
14 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1100 );
15 | controls = new THREE.DeviceOrientationControls( camera );
16 | renderer = new THREE.WebGLRenderer();
17 | renderer.setClearColor( 0x111727, 1);
18 | renderer.setSize( window.innerWidth, window.innerHeight );
19 |
20 | // create a random set of particles
21 | for (var i = 0; i < particleCount; i++) {
22 |
23 | particles[i] = new THREE.Mesh(
24 | new THREE.SphereGeometry(5, 32, 32),
25 | new THREE.MeshBasicMaterial({
26 | color: 0xFFFFFF,
27 | transparent: true,
28 | opacity: 0.5
29 | })
30 | );
31 |
32 | //randomize positions
33 | var px = Math.random() * window.innerWidth * 2 - window.innerWidth;
34 | var py = Math.random() * window.innerHeight * 2 - window.innerHeight;
35 | var pz = Math.random() * window.innerWidth * 2 - window.innerWidth;
36 |
37 | particles[i].position.x = px;
38 | particles[i].position.y = py;
39 | particles[i].position.z = pz;
40 |
41 | particles[i].direction = {
42 | x: Math.random(),
43 | y: Math.random()
44 | }
45 | particles[i].velocity = {
46 | y: Math.random()
47 | }
48 |
49 | scene.add(particles[i]);
50 | }
51 |
52 | canvas.appendChild( renderer.domElement );
53 |
54 | renderer.render( scene, camera );
55 | }
56 |
57 | function animate() {
58 |
59 | requestAnimationFrame( animate );
60 | controls.update();
61 |
62 | for (var i = 0; i < particleCount; i++) {
63 | particles[i].position.y -= particles[i].velocity.y * 3;
64 | if (particles[i].position.y < -window.innerHeight ||
65 | particles[i].position.y > window.innerHeight) {
66 | particles[i].position.y = window.innerHeight;
67 | }
68 | }
69 |
70 | renderer.render( scene, camera );
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/three.js.shader.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
24 |
25 |
34 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/perf-testing/js/scripts.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var scene, camera, renderer, controls;
3 | var canvas = document.getElementById('canvas');
4 |
5 | // particles set up
6 | var particleCount = 100;
7 | var particles = [];
8 |
9 | init();
10 | animate();
11 |
12 | function fillScene() {
13 |
14 | var particleGeometry = new THREE.SphereGeometry(10, 32, 32); // size, number of polys to form this circle
15 | var particleMaterial = new THREE.MeshBasicMaterial({
16 | color: 0xFFFFFF,
17 | transparent: true,
18 | opacity: 0.5
19 | });
20 |
21 | // create a random set of particles
22 | for (var i = 0; i < particleCount; i++) {
23 |
24 | particles[i] = new THREE.Mesh( particleGeometry, particleMaterial );
25 |
26 | //randomize positions
27 | particles[i].position.x = Math.random() * window.innerWidth * 2 - window.innerWidth;;
28 | particles[i].position.y = Math.random() * window.innerHeight * 2 - window.innerHeight;
29 | particles[i].position.z = Math.random() * window.innerWidth * 2 - window.innerWidth;
30 |
31 | particles[i]. direction = {
32 | x: Math.random(),
33 | y: Math.random()
34 | }
35 |
36 | scene.add(particles[i]);
37 | }
38 | }
39 |
40 | function init() {
41 | scene = new THREE.Scene();
42 |
43 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
44 | camera.position.z = 1000;
45 |
46 | renderer = new THREE.WebGLRenderer();
47 | renderer.setClearColor( 0x31AED1, 1);
48 | renderer.setSize( window.innerWidth, window.innerHeight );
49 |
50 | controls = new THREE.OrbitControls( camera, renderer.domElement );
51 | controls.enableDamping = true;
52 | controls.dampingFactor = 0.25;
53 | controls.enableZoom = false;
54 |
55 | fillScene();
56 | canvas.appendChild( renderer.domElement );
57 | renderer.render( scene, camera );
58 | }
59 |
60 | function animate() {
61 | requestAnimationFrame( animate );
62 | controls.update();
63 |
64 | for (var i = 0; i < particleCount; i++) {
65 | particles[i].position.x += particles[i].direction.x;
66 | particles[i].position.y += particles[i].direction.y;
67 |
68 | // if edge is reached, bounce back
69 | if (particles[i].position.x < -window.innerWidth ||
70 | particles[i].position.x > window.innerWidth) {
71 | particles[i].direction.x = -particles[i].direction.x;
72 | }
73 | if (particles[i].position.y < -window.innerHeight ||
74 | particles[i].position.y > window.innerHeight) {
75 | particles[i].direction.y = -particles[i].direction.y;
76 | }
77 | }
78 |
79 | renderer.render( scene, camera );
80 | }
81 | })();
82 |
--------------------------------------------------------------------------------
/three-particles/js/scripts.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var scene, camera, renderer, controls;
3 | var canvas = document.getElementById('canvas');
4 |
5 | // particles set up
6 | var particleCount = 100;
7 | var particles = [];
8 |
9 | init();
10 | animate();
11 |
12 | function fillScene() {
13 |
14 | var particleGeometry = new THREE.SphereGeometry(10, 32, 32); // size, number of polys to form this circle
15 | var particleMaterial = new THREE.MeshBasicMaterial({
16 | color: 0xFFFFFF,
17 | transparent: true,
18 | opacity: 0.5
19 | });
20 |
21 | // create a random set of particles
22 | for (var i = 0; i < particleCount; i++) {
23 |
24 | particles[i] = new THREE.Mesh( particleGeometry, particleMaterial );
25 |
26 | //randomize positions
27 | particles[i].position.x = Math.random() * window.innerWidth * 2 - window.innerWidth;;
28 | particles[i].position.y = Math.random() * window.innerHeight * 2 - window.innerHeight;
29 | particles[i].position.z = Math.random() * window.innerWidth * 2 - window.innerWidth;
30 |
31 | particles[i]. direction = {
32 | x: Math.random(),
33 | y: Math.random()
34 | }
35 |
36 | scene.add(particles[i]);
37 | }
38 | }
39 |
40 | function init() {
41 | scene = new THREE.Scene();
42 |
43 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
44 | camera.position.z = 1000;
45 |
46 | renderer = new THREE.WebGLRenderer();
47 | renderer.setClearColor( 0x31AED1, 1);
48 | renderer.setSize( window.innerWidth, window.innerHeight );
49 |
50 | controls = new THREE.OrbitControls( camera, renderer.domElement );
51 | controls.enableDamping = true;
52 | controls.dampingFactor = 0.25;
53 | controls.enableZoom = false;
54 |
55 | fillScene();
56 | canvas.appendChild( renderer.domElement );
57 | renderer.render( scene, camera );
58 | }
59 |
60 | function animate() {
61 | requestAnimationFrame( animate );
62 | controls.update();
63 |
64 | for (var i = 0; i < particleCount; i++) {
65 | particles[i].position.x += particles[i].direction.x;
66 | particles[i].position.y += particles[i].direction.y;
67 |
68 | // if edge is reached, bounce back
69 | if (particles[i].position.x < -window.innerWidth ||
70 | particles[i].position.x > window.innerWidth) {
71 | particles[i].direction.x = -particles[i].direction.x;
72 | }
73 | if (particles[i].position.y < -window.innerHeight ||
74 | particles[i].position.y > window.innerHeight) {
75 | particles[i].direction.y = -particles[i].direction.y;
76 | }
77 | }
78 |
79 | renderer.render( scene, camera );
80 | }
81 | })();
82 |
--------------------------------------------------------------------------------
/simple-scene/js/scripts.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var scene, camera, renderer, controls, diretionalLight, hemisphereLight;
3 | var canvas = document.getElementById('canvas');
4 |
5 | // particles set up
6 | var particleCount = 100;
7 | var particles = [];
8 |
9 | init();
10 | animate();
11 |
12 | function fillScene() {
13 |
14 |
15 | // Add stuff to your scene
16 |
17 | var geometry = new THREE.BoxGeometry( 100, 100, 100 );
18 | var material = new THREE.MeshPhongMaterial( {color: 0x00ff00} );
19 | var cube = new THREE.Mesh( geometry, material );
20 | scene.add( cube );
21 |
22 | }
23 |
24 | function init() {
25 | scene = new THREE.Scene();
26 |
27 | // LIGHTS
28 | hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.6 );
29 | hemiLight.color.setHSL( 0.6, 1, 0.6 );
30 | hemiLight.groundColor.setHSL( 0.095, 1, 0.75 );
31 | hemiLight.position.set( 0, 500, 0 );
32 | scene.add( hemiLight );
33 | //
34 | dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
35 | dirLight.color.setHSL( 0.1, 1, 0.95 );
36 | dirLight.position.set( -1, 1.75, 1 );
37 | dirLight.position.multiplyScalar( 50 );
38 | scene.add( dirLight );
39 |
40 | dirLight.castShadow = true;
41 |
42 | dirLight.shadow.mapSize.width = 2048;
43 | dirLight.shadow.mapSize.height = 2048;
44 |
45 | var d = 50;
46 | dirLight.shadow.camera.left = -d;
47 | dirLight.shadow.camera.right = d;
48 | dirLight.shadow.camera.top = d;
49 | dirLight.shadow.camera.bottom = -d;
50 |
51 | dirLight.shadow.camera.far = 3500;
52 | dirLight.shadow.bias = -0.0001;
53 |
54 | // ground
55 | var groundGeo = new THREE.PlaneBufferGeometry( 10000, 10000 );
56 | var groundMat = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x050505 } );
57 | groundMat.color.setHex( 0x0C73B8 );
58 | var ground = new THREE.Mesh( groundGeo, groundMat );
59 | ground.rotation.x = -Math.PI/2;
60 | ground.position.y = -33;
61 | scene.add( ground );
62 | ground.receiveShadow = true;
63 |
64 | // camera
65 |
66 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
67 | camera.position.z = 1000;
68 |
69 | renderer = new THREE.WebGLRenderer();
70 | renderer.setClearColor( 0xBFE6F3, 1);
71 | renderer.setSize( window.innerWidth, window.innerHeight );
72 |
73 | controls = new THREE.OrbitControls( camera, renderer.domElement );
74 | controls.enableDamping = true;
75 | controls.dampingFactor = 0.25;
76 | controls.enableZoom = false;
77 |
78 | fillScene();
79 | canvas.appendChild( renderer.domElement );
80 | renderer.render( scene, camera );
81 | }
82 |
83 | function animate() {
84 | requestAnimationFrame( animate );
85 | controls.update();
86 |
87 | renderer.render( scene, camera );
88 | }
89 | })();
--------------------------------------------------------------------------------
/perf-testing/js/snowing.js:
--------------------------------------------------------------------------------
1 | var scene, camera, renderer, controls;
2 | var canvas = document.getElementById('canvas');
3 |
4 | // particles set up
5 | var particleCount = 1000;
6 | var particles = [];
7 |
8 | init();
9 | animate();
10 |
11 | function init() {
12 |
13 | scene = new THREE.Scene();
14 |
15 | // camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1100 );
16 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
17 | camera.position.z = 1000;
18 |
19 | // controls = new THREE.DeviceOrientationControls( camera );
20 |
21 |
22 | renderer = new THREE.WebGLRenderer();
23 | renderer.setClearColor( 0x111727, 1 );
24 | renderer.setSize( window.innerWidth, window.innerHeight );
25 |
26 | controls = new THREE.OrbitControls( camera, renderer.domElement );
27 | controls.enableDamping = true;
28 | controls.dampingFactor = 0.25;
29 | controls.enableZoom = false;
30 |
31 | // ambient light
32 | var hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.6 );
33 | hemiLight.color.setHex( 0xffffff );
34 | hemiLight.groundColor.setHex( 0xffffff );
35 | hemiLight.position.set( 0, 500, 0 );
36 | scene.add( hemiLight );
37 |
38 | // point light (stree lamp)
39 | var pointLight = new THREE.PointLight( 0xffffff, 1, 700, 2 );
40 | pointLight.position.set(200, 200, 300);
41 | scene.add(pointLight);
42 |
43 | // ground
44 | var groundGeo = new THREE.PlaneBufferGeometry( 10000, 10000 );
45 | var groundMat = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x050505 } );
46 | groundMat.color.setHex( 0xbfd2eb )
47 | var ground = new THREE.Mesh( groundGeo, groundMat );
48 | ground.rotation.x = -Math.PI/2;
49 | ground.position.y = -33;
50 | scene.add( ground );
51 | ground.receiveShadow = true;
52 |
53 | // create a random set of particles
54 | for (var i = 0; i < particleCount; i++) {
55 |
56 | particles[i] = new THREE.Mesh( new THREE.SphereGeometry(5, 32, 32), new THREE.MeshPhongMaterial({color: 0xbfd2eb}));
57 |
58 | //randomize positions
59 | var px = Math.random() * window.innerWidth * 2 - window.innerWidth;
60 | var py = Math.random() * window.innerHeight * 2 - window.innerHeight;
61 | var pz = Math.random() * window.innerWidth * 2 - window.innerWidth;
62 |
63 | particles[i].position.x = px;
64 | particles[i].position.y = py;
65 | particles[i].position.z = pz;
66 |
67 | particles[i].direction = {
68 | x: Math.random(),
69 | y: Math.random()
70 | }
71 | particles[i].velocity = {
72 | y: Math.random()
73 | }
74 |
75 | scene.add(particles[i]);
76 | }
77 |
78 | canvas.appendChild( renderer.domElement );
79 |
80 | renderer.render( scene, camera );
81 | }
82 |
83 | function animate() {
84 |
85 | requestAnimationFrame( animate );
86 | controls.update();
87 |
88 | for (var i = 0; i < particleCount; i++) {
89 | particles[i].position.y -= particles[i].velocity.y * 3;
90 | if (particles[i].position.y < -window.innerHeight ||
91 | particles[i].position.y > window.innerHeight) {
92 | particles[i].position.y = window.innerHeight;
93 | }
94 | }
95 |
96 | renderer.render( scene, camera );
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/three/DeviceOrientationControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author richt / http://richt.me
3 | * @author WestLangley / http://github.com/WestLangley
4 | *
5 | * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html)
6 | */
7 |
8 | THREE.DeviceOrientationControls = function( object ) {
9 |
10 | var scope = this;
11 |
12 | this.object = object;
13 | this.object.rotation.reorder( "YXZ" );
14 |
15 | this.enabled = true;
16 |
17 | this.deviceOrientation = {};
18 | this.screenOrientation = 0;
19 |
20 | this.alpha = 0;
21 | this.alphaOffsetAngle = 0;
22 |
23 |
24 | var onDeviceOrientationChangeEvent = function( event ) {
25 |
26 | scope.deviceOrientation = event;
27 |
28 | };
29 |
30 | var onScreenOrientationChangeEvent = function() {
31 |
32 | scope.screenOrientation = window.orientation || 0;
33 |
34 | };
35 |
36 | // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
37 |
38 | var setObjectQuaternion = function() {
39 |
40 | var zee = new THREE.Vector3( 0, 0, 1 );
41 |
42 | var euler = new THREE.Euler();
43 |
44 | var q0 = new THREE.Quaternion();
45 |
46 | var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
47 |
48 | return function( quaternion, alpha, beta, gamma, orient ) {
49 |
50 | euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
51 |
52 | quaternion.setFromEuler( euler ); // orient the device
53 |
54 | quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
55 |
56 | quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation
57 |
58 | }
59 |
60 | }();
61 |
62 | this.connect = function() {
63 |
64 | onScreenOrientationChangeEvent(); // run once on load
65 |
66 | window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
67 | window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
68 |
69 | scope.enabled = true;
70 |
71 | };
72 |
73 | this.disconnect = function() {
74 |
75 | window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
76 | window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
77 |
78 | scope.enabled = false;
79 |
80 | };
81 |
82 | this.update = function() {
83 |
84 | if ( scope.enabled === false ) return;
85 |
86 | var alpha = scope.deviceOrientation.alpha ? THREE.Math.degToRad( scope.deviceOrientation.alpha ) + this.alphaOffsetAngle : 0; // Z
87 | var beta = scope.deviceOrientation.beta ? THREE.Math.degToRad( scope.deviceOrientation.beta ) : 0; // X'
88 | var gamma = scope.deviceOrientation.gamma ? THREE.Math.degToRad( scope.deviceOrientation.gamma ) : 0; // Y''
89 | var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O
90 |
91 | setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient );
92 | this.alpha = alpha;
93 |
94 | };
95 |
96 | this.updateAlphaOffsetAngle = function( angle ) {
97 |
98 | this.alphaOffsetAngle = angle;
99 | this.update();
100 |
101 | };
102 |
103 | this.dispose = function() {
104 |
105 | this.disconnect();
106 |
107 | };
108 |
109 | this.connect();
110 |
111 | };
112 |
--------------------------------------------------------------------------------
/obj_loader/webgl_loader_obj.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | three.js webgl - loaders - OBJ loader
6 |
7 |
8 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
174 |
175 |
176 |
177 |
--------------------------------------------------------------------------------
/obj_loader/js/OBJLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 |
5 | THREE.OBJLoader = function ( manager ) {
6 |
7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
8 |
9 | this.materials = null;
10 |
11 | this.regexp = {
12 | // v float float float
13 | vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
14 | // vn float float float
15 | normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
16 | // vt float float
17 | uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
18 | // f vertex vertex vertex
19 | face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/,
20 | // f vertex/uv vertex/uv vertex/uv
21 | face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/,
22 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal
23 | face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/,
24 | // f vertex//normal vertex//normal vertex//normal
25 | face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/,
26 | // o object_name | g group_name
27 | object_pattern : /^[og]\s*(.+)?/,
28 | // s boolean
29 | smoothing_pattern : /^s\s+(\d+|on|off)/,
30 | // mtllib file_reference
31 | material_library_pattern : /^mtllib /,
32 | // usemtl material_name
33 | material_use_pattern : /^usemtl /
34 | };
35 |
36 | };
37 |
38 | THREE.OBJLoader.prototype = {
39 |
40 | constructor: THREE.OBJLoader,
41 |
42 | load: function ( url, onLoad, onProgress, onError ) {
43 |
44 | var scope = this;
45 |
46 | var loader = new THREE.XHRLoader( scope.manager );
47 | loader.setPath( this.path );
48 | loader.load( url, function ( text ) {
49 |
50 | onLoad( scope.parse( text ) );
51 |
52 | }, onProgress, onError );
53 |
54 | },
55 |
56 | setPath: function ( value ) {
57 |
58 | this.path = value;
59 |
60 | },
61 |
62 | setMaterials: function ( materials ) {
63 |
64 | this.materials = materials;
65 |
66 | },
67 |
68 | _createParserState : function () {
69 |
70 | var state = {
71 | objects : [],
72 | object : {},
73 |
74 | vertices : [],
75 | normals : [],
76 | uvs : [],
77 |
78 | materialLibraries : [],
79 |
80 | startObject: function ( name, fromDeclaration ) {
81 |
82 | // If the current object (initial from reset) is not from a g/o declaration in the parsed
83 | // file. We need to use it for the first parsed g/o to keep things in sync.
84 | if ( this.object && this.object.fromDeclaration === false ) {
85 |
86 | this.object.name = name;
87 | this.object.fromDeclaration = ( fromDeclaration !== false );
88 | return;
89 |
90 | }
91 |
92 | if ( this.object && typeof this.object._finalize === 'function' ) {
93 |
94 | this.object._finalize();
95 |
96 | }
97 |
98 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
99 |
100 | this.object = {
101 | name : name || '',
102 | fromDeclaration : ( fromDeclaration !== false ),
103 |
104 | geometry : {
105 | vertices : [],
106 | normals : [],
107 | uvs : []
108 | },
109 | materials : [],
110 | smooth : true,
111 |
112 | startMaterial : function( name, libraries ) {
113 |
114 | var previous = this._finalize( false );
115 |
116 | // New usemtl declaration overwrites an inherited material, except if faces were declared
117 | // after the material, then it must be preserved for proper MultiMaterial continuation.
118 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
119 |
120 | this.materials.splice( previous.index, 1 );
121 |
122 | }
123 |
124 | var material = {
125 | index : this.materials.length,
126 | name : name || '',
127 | mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
128 | smooth : ( previous !== undefined ? previous.smooth : this.smooth ),
129 | groupStart : ( previous !== undefined ? previous.groupEnd : 0 ),
130 | groupEnd : -1,
131 | groupCount : -1,
132 | inherited : false,
133 |
134 | clone : function( index ) {
135 | return {
136 | index : ( typeof index === 'number' ? index : this.index ),
137 | name : this.name,
138 | mtllib : this.mtllib,
139 | smooth : this.smooth,
140 | groupStart : this.groupEnd,
141 | groupEnd : -1,
142 | groupCount : -1,
143 | inherited : false
144 | };
145 | }
146 | };
147 |
148 | this.materials.push( material );
149 |
150 | return material;
151 |
152 | },
153 |
154 | currentMaterial : function() {
155 |
156 | if ( this.materials.length > 0 ) {
157 | return this.materials[ this.materials.length - 1 ];
158 | }
159 |
160 | return undefined;
161 |
162 | },
163 |
164 | _finalize : function( end ) {
165 |
166 | var lastMultiMaterial = this.currentMaterial();
167 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) {
168 |
169 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
170 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
171 | lastMultiMaterial.inherited = false;
172 |
173 | }
174 |
175 | // Guarantee at least one empty material, this makes the creation later more straight forward.
176 | if ( end !== false && this.materials.length === 0 ) {
177 | this.materials.push({
178 | name : '',
179 | smooth : this.smooth
180 | });
181 | }
182 |
183 | return lastMultiMaterial;
184 |
185 | }
186 | };
187 |
188 | // Inherit previous objects material.
189 | // Spec tells us that a declared material must be set to all objects until a new material is declared.
190 | // If a usemtl declaration is encountered while this new object is being parsed, it will
191 | // overwrite the inherited material. Exception being that there was already face declarations
192 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation.
193 |
194 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) {
195 |
196 | var declared = previousMaterial.clone( 0 );
197 | declared.inherited = true;
198 | this.object.materials.push( declared );
199 |
200 | }
201 |
202 | this.objects.push( this.object );
203 |
204 | },
205 |
206 | finalize : function() {
207 |
208 | if ( this.object && typeof this.object._finalize === 'function' ) {
209 |
210 | this.object._finalize();
211 |
212 | }
213 |
214 | },
215 |
216 | parseVertexIndex: function ( value, len ) {
217 |
218 | var index = parseInt( value, 10 );
219 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
220 |
221 | },
222 |
223 | parseNormalIndex: function ( value, len ) {
224 |
225 | var index = parseInt( value, 10 );
226 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
227 |
228 | },
229 |
230 | parseUVIndex: function ( value, len ) {
231 |
232 | var index = parseInt( value, 10 );
233 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
234 |
235 | },
236 |
237 | addVertex: function ( a, b, c ) {
238 |
239 | var src = this.vertices;
240 | var dst = this.object.geometry.vertices;
241 |
242 | dst.push( src[ a + 0 ] );
243 | dst.push( src[ a + 1 ] );
244 | dst.push( src[ a + 2 ] );
245 | dst.push( src[ b + 0 ] );
246 | dst.push( src[ b + 1 ] );
247 | dst.push( src[ b + 2 ] );
248 | dst.push( src[ c + 0 ] );
249 | dst.push( src[ c + 1 ] );
250 | dst.push( src[ c + 2 ] );
251 |
252 | },
253 |
254 | addVertexLine: function ( a ) {
255 |
256 | var src = this.vertices;
257 | var dst = this.object.geometry.vertices;
258 |
259 | dst.push( src[ a + 0 ] );
260 | dst.push( src[ a + 1 ] );
261 | dst.push( src[ a + 2 ] );
262 |
263 | },
264 |
265 | addNormal : function ( a, b, c ) {
266 |
267 | var src = this.normals;
268 | var dst = this.object.geometry.normals;
269 |
270 | dst.push( src[ a + 0 ] );
271 | dst.push( src[ a + 1 ] );
272 | dst.push( src[ a + 2 ] );
273 | dst.push( src[ b + 0 ] );
274 | dst.push( src[ b + 1 ] );
275 | dst.push( src[ b + 2 ] );
276 | dst.push( src[ c + 0 ] );
277 | dst.push( src[ c + 1 ] );
278 | dst.push( src[ c + 2 ] );
279 |
280 | },
281 |
282 | addUV: function ( a, b, c ) {
283 |
284 | var src = this.uvs;
285 | var dst = this.object.geometry.uvs;
286 |
287 | dst.push( src[ a + 0 ] );
288 | dst.push( src[ a + 1 ] );
289 | dst.push( src[ b + 0 ] );
290 | dst.push( src[ b + 1 ] );
291 | dst.push( src[ c + 0 ] );
292 | dst.push( src[ c + 1 ] );
293 |
294 | },
295 |
296 | addUVLine: function ( a ) {
297 |
298 | var src = this.uvs;
299 | var dst = this.object.geometry.uvs;
300 |
301 | dst.push( src[ a + 0 ] );
302 | dst.push( src[ a + 1 ] );
303 |
304 | },
305 |
306 | addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) {
307 |
308 | var vLen = this.vertices.length;
309 |
310 | var ia = this.parseVertexIndex( a, vLen );
311 | var ib = this.parseVertexIndex( b, vLen );
312 | var ic = this.parseVertexIndex( c, vLen );
313 | var id;
314 |
315 | if ( d === undefined ) {
316 |
317 | this.addVertex( ia, ib, ic );
318 |
319 | } else {
320 |
321 | id = this.parseVertexIndex( d, vLen );
322 |
323 | this.addVertex( ia, ib, id );
324 | this.addVertex( ib, ic, id );
325 |
326 | }
327 |
328 | if ( ua !== undefined ) {
329 |
330 | var uvLen = this.uvs.length;
331 |
332 | ia = this.parseUVIndex( ua, uvLen );
333 | ib = this.parseUVIndex( ub, uvLen );
334 | ic = this.parseUVIndex( uc, uvLen );
335 |
336 | if ( d === undefined ) {
337 |
338 | this.addUV( ia, ib, ic );
339 |
340 | } else {
341 |
342 | id = this.parseUVIndex( ud, uvLen );
343 |
344 | this.addUV( ia, ib, id );
345 | this.addUV( ib, ic, id );
346 |
347 | }
348 |
349 | }
350 |
351 | if ( na !== undefined ) {
352 |
353 | // Normals are many times the same. If so, skip function call and parseInt.
354 | var nLen = this.normals.length;
355 | ia = this.parseNormalIndex( na, nLen );
356 |
357 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
358 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
359 |
360 | if ( d === undefined ) {
361 |
362 | this.addNormal( ia, ib, ic );
363 |
364 | } else {
365 |
366 | id = this.parseNormalIndex( nd, nLen );
367 |
368 | this.addNormal( ia, ib, id );
369 | this.addNormal( ib, ic, id );
370 |
371 | }
372 |
373 | }
374 |
375 | },
376 |
377 | addLineGeometry: function ( vertices, uvs ) {
378 |
379 | this.object.geometry.type = 'Line';
380 |
381 | var vLen = this.vertices.length;
382 | var uvLen = this.uvs.length;
383 |
384 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
385 |
386 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
387 |
388 | }
389 |
390 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
391 |
392 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
393 |
394 | }
395 |
396 | }
397 |
398 | };
399 |
400 | state.startObject( '', false );
401 |
402 | return state;
403 |
404 | },
405 |
406 | parse: function ( text ) {
407 |
408 | console.time( 'OBJLoader' );
409 |
410 | var state = this._createParserState();
411 |
412 | if ( text.indexOf( '\r\n' ) !== - 1 ) {
413 |
414 | // This is faster than String.split with regex that splits on both
415 | text = text.replace( '\r\n', '\n' );
416 |
417 | }
418 |
419 | var lines = text.split( '\n' );
420 | var line = '', lineFirstChar = '', lineSecondChar = '';
421 | var lineLength = 0;
422 | var result = [];
423 |
424 | // Faster to just trim left side of the line. Use if available.
425 | var trimLeft = ( typeof ''.trimLeft === 'function' );
426 |
427 | for ( var i = 0, l = lines.length; i < l; i ++ ) {
428 |
429 | line = lines[ i ];
430 |
431 | line = trimLeft ? line.trimLeft() : line.trim();
432 |
433 | lineLength = line.length;
434 |
435 | if ( lineLength === 0 ) continue;
436 |
437 | lineFirstChar = line.charAt( 0 );
438 |
439 | // @todo invoke passed in handler if any
440 | if ( lineFirstChar === '#' ) continue;
441 |
442 | if ( lineFirstChar === 'v' ) {
443 |
444 | lineSecondChar = line.charAt( 1 );
445 |
446 | if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) {
447 |
448 | // 0 1 2 3
449 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
450 |
451 | state.vertices.push(
452 | parseFloat( result[ 1 ] ),
453 | parseFloat( result[ 2 ] ),
454 | parseFloat( result[ 3 ] )
455 | );
456 |
457 | } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) {
458 |
459 | // 0 1 2 3
460 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
461 |
462 | state.normals.push(
463 | parseFloat( result[ 1 ] ),
464 | parseFloat( result[ 2 ] ),
465 | parseFloat( result[ 3 ] )
466 | );
467 |
468 | } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) {
469 |
470 | // 0 1 2
471 | // ["vt 0.1 0.2", "0.1", "0.2"]
472 |
473 | state.uvs.push(
474 | parseFloat( result[ 1 ] ),
475 | parseFloat( result[ 2 ] )
476 | );
477 |
478 | } else {
479 |
480 | throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" );
481 |
482 | }
483 |
484 | } else if ( lineFirstChar === "f" ) {
485 |
486 | if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) {
487 |
488 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal
489 | // 0 1 2 3 4 5 6 7 8 9 10 11 12
490 | // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined]
491 |
492 | state.addFace(
493 | result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ],
494 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ],
495 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ]
496 | );
497 |
498 | } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) {
499 |
500 | // f vertex/uv vertex/uv vertex/uv
501 | // 0 1 2 3 4 5 6 7 8
502 | // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined]
503 |
504 | state.addFace(
505 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ],
506 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ]
507 | );
508 |
509 | } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) {
510 |
511 | // f vertex//normal vertex//normal vertex//normal
512 | // 0 1 2 3 4 5 6 7 8
513 | // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined]
514 |
515 | state.addFace(
516 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ],
517 | undefined, undefined, undefined, undefined,
518 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ]
519 | );
520 |
521 | } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) {
522 |
523 | // f vertex vertex vertex
524 | // 0 1 2 3 4
525 | // ["f 1 2 3", "1", "2", "3", undefined]
526 |
527 | state.addFace(
528 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ]
529 | );
530 |
531 | } else {
532 |
533 | throw new Error( "Unexpected face line: '" + line + "'" );
534 |
535 | }
536 |
537 | } else if ( lineFirstChar === "l" ) {
538 |
539 | var lineParts = line.substring( 1 ).trim().split( " " );
540 | var lineVertices = [], lineUVs = [];
541 |
542 | if ( line.indexOf( "/" ) === - 1 ) {
543 |
544 | lineVertices = lineParts;
545 |
546 | } else {
547 |
548 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {
549 |
550 | var parts = lineParts[ li ].split( "/" );
551 |
552 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] );
553 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] );
554 |
555 | }
556 |
557 | }
558 | state.addLineGeometry( lineVertices, lineUVs );
559 |
560 | } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) {
561 |
562 | // o object_name
563 | // or
564 | // g group_name
565 |
566 | var name = result[ 0 ].substr( 1 ).trim();
567 | state.startObject( name );
568 |
569 | } else if ( this.regexp.material_use_pattern.test( line ) ) {
570 |
571 | // material
572 |
573 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
574 |
575 | } else if ( this.regexp.material_library_pattern.test( line ) ) {
576 |
577 | // mtl file
578 |
579 | state.materialLibraries.push( line.substring( 7 ).trim() );
580 |
581 | } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) {
582 |
583 | // smooth shading
584 |
585 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
586 | // but does not define a usemtl for each face set.
587 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
588 | // This requires some care to not create extra material on each smooth value for "normal" obj files.
589 | // where explicit usemtl defines geometry groups.
590 | // Example asset: examples/models/obj/cerberus/Cerberus.obj
591 |
592 | var value = result[ 1 ].trim().toLowerCase();
593 | state.object.smooth = ( value === '1' || value === 'on' );
594 |
595 | var material = state.object.currentMaterial();
596 | if ( material ) {
597 |
598 | material.smooth = state.object.smooth;
599 |
600 | }
601 |
602 | } else {
603 |
604 | // Handle null terminated files without exception
605 | if ( line === '\0' ) continue;
606 |
607 | throw new Error( "Unexpected line: '" + line + "'" );
608 |
609 | }
610 |
611 | }
612 |
613 | state.finalize();
614 |
615 | var container = new THREE.Group();
616 | container.materialLibraries = [].concat( state.materialLibraries );
617 |
618 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) {
619 |
620 | var object = state.objects[ i ];
621 | var geometry = object.geometry;
622 | var materials = object.materials;
623 | var isLine = ( geometry.type === 'Line' );
624 |
625 | // Skip o/g line declarations that did not follow with any faces
626 | if ( geometry.vertices.length === 0 ) continue;
627 |
628 | var buffergeometry = new THREE.BufferGeometry();
629 |
630 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) );
631 |
632 | if ( geometry.normals.length > 0 ) {
633 |
634 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) );
635 |
636 | } else {
637 |
638 | buffergeometry.computeVertexNormals();
639 |
640 | }
641 |
642 | if ( geometry.uvs.length > 0 ) {
643 |
644 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) );
645 |
646 | }
647 |
648 | // Create materials
649 |
650 | var createdMaterials = [];
651 |
652 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {
653 |
654 | var sourceMaterial = materials[mi];
655 | var material = undefined;
656 |
657 | if ( this.materials !== null ) {
658 |
659 | material = this.materials.create( sourceMaterial.name );
660 |
661 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
662 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {
663 |
664 | var materialLine = new THREE.LineBasicMaterial();
665 | materialLine.copy( material );
666 | material = materialLine;
667 |
668 | }
669 |
670 | }
671 |
672 | if ( ! material ) {
673 |
674 | material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() );
675 | material.name = sourceMaterial.name;
676 |
677 | }
678 |
679 | material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading;
680 |
681 | createdMaterials.push(material);
682 |
683 | }
684 |
685 | // Create mesh
686 |
687 | var mesh;
688 |
689 | if ( createdMaterials.length > 1 ) {
690 |
691 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {
692 |
693 | var sourceMaterial = materials[mi];
694 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
695 |
696 | }
697 |
698 | var multiMaterial = new THREE.MultiMaterial( createdMaterials );
699 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.Line( buffergeometry, multiMaterial ) );
700 |
701 | } else {
702 |
703 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.Line( buffergeometry, createdMaterials[ 0 ] ) );
704 | }
705 |
706 | mesh.name = object.name;
707 |
708 | container.add( mesh );
709 |
710 | }
711 |
712 | console.timeEnd( 'OBJLoader' );
713 |
714 | return container;
715 |
716 | }
717 |
718 | };
719 |
--------------------------------------------------------------------------------
/three/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | */
8 |
9 | // This set of controls performs orbiting, dollying (zooming), and panning.
10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
11 | //
12 | // Orbit - left mouse / touch: one finger move
13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
14 | // Pan - right mouse, or arrow keys / touch: three finter swipe
15 |
16 | THREE.OrbitControls = function ( object, domElement ) {
17 |
18 | this.object = object;
19 |
20 | this.domElement = ( domElement !== undefined ) ? domElement : document;
21 |
22 | // Set to false to disable this control
23 | this.enabled = true;
24 |
25 | // "target" sets the location of focus, where the object orbits around
26 | this.target = new THREE.Vector3();
27 |
28 | // How far you can dolly in and out ( PerspectiveCamera only )
29 | this.minDistance = 0;
30 | this.maxDistance = Infinity;
31 |
32 | // How far you can zoom in and out ( OrthographicCamera only )
33 | this.minZoom = 0;
34 | this.maxZoom = Infinity;
35 |
36 | // How far you can orbit vertically, upper and lower limits.
37 | // Range is 0 to Math.PI radians.
38 | this.minPolarAngle = 0; // radians
39 | this.maxPolarAngle = Math.PI; // radians
40 |
41 | // How far you can orbit horizontally, upper and lower limits.
42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
43 | this.minAzimuthAngle = - Infinity; // radians
44 | this.maxAzimuthAngle = Infinity; // radians
45 |
46 | // Set to true to enable damping (inertia)
47 | // If damping is enabled, you must call controls.update() in your animation loop
48 | this.enableDamping = false;
49 | this.dampingFactor = 0.25;
50 |
51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
52 | // Set to false to disable zooming
53 | this.enableZoom = true;
54 | this.zoomSpeed = 1.0;
55 |
56 | // Set to false to disable rotating
57 | this.enableRotate = true;
58 | this.rotateSpeed = 1.0;
59 |
60 | // Set to false to disable panning
61 | this.enablePan = true;
62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
63 |
64 | // Set to true to automatically rotate around the target
65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop
66 | this.autoRotate = false;
67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
68 |
69 | // Set to false to disable use of the keys
70 | this.enableKeys = true;
71 |
72 | // The four arrow keys
73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
74 |
75 | // Mouse buttons
76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
77 |
78 | // for reset
79 | this.target0 = this.target.clone();
80 | this.position0 = this.object.position.clone();
81 | this.zoom0 = this.object.zoom;
82 |
83 | //
84 | // public methods
85 | //
86 |
87 | this.getPolarAngle = function () {
88 |
89 | return spherical.phi;
90 |
91 | };
92 |
93 | this.getAzimuthalAngle = function () {
94 |
95 | return spherical.theta;
96 |
97 | };
98 |
99 | this.reset = function () {
100 |
101 | scope.target.copy( scope.target0 );
102 | scope.object.position.copy( scope.position0 );
103 | scope.object.zoom = scope.zoom0;
104 |
105 | scope.object.updateProjectionMatrix();
106 | scope.dispatchEvent( changeEvent );
107 |
108 | scope.update();
109 |
110 | state = STATE.NONE;
111 |
112 | };
113 |
114 | // this method is exposed, but perhaps it would be better if we can make it private...
115 | this.update = function() {
116 |
117 | var offset = new THREE.Vector3();
118 |
119 | // so camera.up is the orbit axis
120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
121 | var quatInverse = quat.clone().inverse();
122 |
123 | var lastPosition = new THREE.Vector3();
124 | var lastQuaternion = new THREE.Quaternion();
125 |
126 | return function update () {
127 |
128 | var position = scope.object.position;
129 |
130 | offset.copy( position ).sub( scope.target );
131 |
132 | // rotate offset to "y-axis-is-up" space
133 | offset.applyQuaternion( quat );
134 |
135 | // angle from z-axis around y-axis
136 | spherical.setFromVector3( offset );
137 |
138 | if ( scope.autoRotate && state === STATE.NONE ) {
139 |
140 | rotateLeft( getAutoRotationAngle() );
141 |
142 | }
143 |
144 | spherical.theta += sphericalDelta.theta;
145 | spherical.phi += sphericalDelta.phi;
146 |
147 | // restrict theta to be between desired limits
148 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
149 |
150 | // restrict phi to be between desired limits
151 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
152 |
153 | spherical.makeSafe();
154 |
155 |
156 | spherical.radius *= scale;
157 |
158 | // restrict radius to be between desired limits
159 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
160 |
161 | // move target to panned location
162 | scope.target.add( panOffset );
163 |
164 | offset.setFromSpherical( spherical );
165 |
166 | // rotate offset back to "camera-up-vector-is-up" space
167 | offset.applyQuaternion( quatInverse );
168 |
169 | position.copy( scope.target ).add( offset );
170 |
171 | scope.object.lookAt( scope.target );
172 |
173 | if ( scope.enableDamping === true ) {
174 |
175 | sphericalDelta.theta *= ( 1 - scope.dampingFactor );
176 | sphericalDelta.phi *= ( 1 - scope.dampingFactor );
177 |
178 | } else {
179 |
180 | sphericalDelta.set( 0, 0, 0 );
181 |
182 | }
183 |
184 | scale = 1;
185 | panOffset.set( 0, 0, 0 );
186 |
187 | // update condition is:
188 | // min(camera displacement, camera rotation in radians)^2 > EPS
189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
190 |
191 | if ( zoomChanged ||
192 | lastPosition.distanceToSquared( scope.object.position ) > EPS ||
193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
194 |
195 | scope.dispatchEvent( changeEvent );
196 |
197 | lastPosition.copy( scope.object.position );
198 | lastQuaternion.copy( scope.object.quaternion );
199 | zoomChanged = false;
200 |
201 | return true;
202 |
203 | }
204 |
205 | return false;
206 |
207 | };
208 |
209 | }();
210 |
211 | this.dispose = function() {
212 |
213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
215 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
216 |
217 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
218 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
219 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
220 |
221 | document.removeEventListener( 'mousemove', onMouseMove, false );
222 | document.removeEventListener( 'mouseup', onMouseUp, false );
223 |
224 | window.removeEventListener( 'keydown', onKeyDown, false );
225 |
226 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
227 |
228 | };
229 |
230 | //
231 | // internals
232 | //
233 |
234 | var scope = this;
235 |
236 | var changeEvent = { type: 'change' };
237 | var startEvent = { type: 'start' };
238 | var endEvent = { type: 'end' };
239 |
240 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
241 |
242 | var state = STATE.NONE;
243 |
244 | var EPS = 0.000001;
245 |
246 | // current position in spherical coordinates
247 | var spherical = new THREE.Spherical();
248 | var sphericalDelta = new THREE.Spherical();
249 |
250 | var scale = 1;
251 | var panOffset = new THREE.Vector3();
252 | var zoomChanged = false;
253 |
254 | var rotateStart = new THREE.Vector2();
255 | var rotateEnd = new THREE.Vector2();
256 | var rotateDelta = new THREE.Vector2();
257 |
258 | var panStart = new THREE.Vector2();
259 | var panEnd = new THREE.Vector2();
260 | var panDelta = new THREE.Vector2();
261 |
262 | var dollyStart = new THREE.Vector2();
263 | var dollyEnd = new THREE.Vector2();
264 | var dollyDelta = new THREE.Vector2();
265 |
266 | function getAutoRotationAngle() {
267 |
268 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
269 |
270 | }
271 |
272 | function getZoomScale() {
273 |
274 | return Math.pow( 0.95, scope.zoomSpeed );
275 |
276 | }
277 |
278 | function rotateLeft( angle ) {
279 |
280 | sphericalDelta.theta -= angle;
281 |
282 | }
283 |
284 | function rotateUp( angle ) {
285 |
286 | sphericalDelta.phi -= angle;
287 |
288 | }
289 |
290 | var panLeft = function() {
291 |
292 | var v = new THREE.Vector3();
293 |
294 | return function panLeft( distance, objectMatrix ) {
295 |
296 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
297 | v.multiplyScalar( - distance );
298 |
299 | panOffset.add( v );
300 |
301 | };
302 |
303 | }();
304 |
305 | var panUp = function() {
306 |
307 | var v = new THREE.Vector3();
308 |
309 | return function panUp( distance, objectMatrix ) {
310 |
311 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
312 | v.multiplyScalar( distance );
313 |
314 | panOffset.add( v );
315 |
316 | };
317 |
318 | }();
319 |
320 | // deltaX and deltaY are in pixels; right and down are positive
321 | var pan = function() {
322 |
323 | var offset = new THREE.Vector3();
324 |
325 | return function pan ( deltaX, deltaY ) {
326 |
327 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
328 |
329 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
330 |
331 | // perspective
332 | var position = scope.object.position;
333 | offset.copy( position ).sub( scope.target );
334 | var targetDistance = offset.length();
335 |
336 | // half of the fov is center to top of screen
337 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
338 |
339 | // we actually don't use screenWidth, since perspective camera is fixed to screen height
340 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
341 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
342 |
343 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
344 |
345 | // orthographic
346 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
347 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
348 |
349 | } else {
350 |
351 | // camera neither orthographic nor perspective
352 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
353 | scope.enablePan = false;
354 |
355 | }
356 |
357 | };
358 |
359 | }();
360 |
361 | function dollyIn( dollyScale ) {
362 |
363 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
364 |
365 | scale /= dollyScale;
366 |
367 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
368 |
369 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
370 | scope.object.updateProjectionMatrix();
371 | zoomChanged = true;
372 |
373 | } else {
374 |
375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
376 | scope.enableZoom = false;
377 |
378 | }
379 |
380 | }
381 |
382 | function dollyOut( dollyScale ) {
383 |
384 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
385 |
386 | scale *= dollyScale;
387 |
388 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
389 |
390 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
391 | scope.object.updateProjectionMatrix();
392 | zoomChanged = true;
393 |
394 | } else {
395 |
396 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
397 | scope.enableZoom = false;
398 |
399 | }
400 |
401 | }
402 |
403 | //
404 | // event callbacks - update the object state
405 | //
406 |
407 | function handleMouseDownRotate( event ) {
408 |
409 | //console.log( 'handleMouseDownRotate' );
410 |
411 | rotateStart.set( event.clientX, event.clientY );
412 |
413 | }
414 |
415 | function handleMouseDownDolly( event ) {
416 |
417 | //console.log( 'handleMouseDownDolly' );
418 |
419 | dollyStart.set( event.clientX, event.clientY );
420 |
421 | }
422 |
423 | function handleMouseDownPan( event ) {
424 |
425 | //console.log( 'handleMouseDownPan' );
426 |
427 | panStart.set( event.clientX, event.clientY );
428 |
429 | }
430 |
431 | function handleMouseMoveRotate( event ) {
432 |
433 | //console.log( 'handleMouseMoveRotate' );
434 |
435 | rotateEnd.set( event.clientX, event.clientY );
436 | rotateDelta.subVectors( rotateEnd, rotateStart );
437 |
438 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
439 |
440 | // rotating across whole screen goes 360 degrees around
441 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
442 |
443 | // rotating up and down along whole screen attempts to go 360, but limited to 180
444 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
445 |
446 | rotateStart.copy( rotateEnd );
447 |
448 | scope.update();
449 |
450 | }
451 |
452 | function handleMouseMoveDolly( event ) {
453 |
454 | //console.log( 'handleMouseMoveDolly' );
455 |
456 | dollyEnd.set( event.clientX, event.clientY );
457 |
458 | dollyDelta.subVectors( dollyEnd, dollyStart );
459 |
460 | if ( dollyDelta.y > 0 ) {
461 |
462 | dollyIn( getZoomScale() );
463 |
464 | } else if ( dollyDelta.y < 0 ) {
465 |
466 | dollyOut( getZoomScale() );
467 |
468 | }
469 |
470 | dollyStart.copy( dollyEnd );
471 |
472 | scope.update();
473 |
474 | }
475 |
476 | function handleMouseMovePan( event ) {
477 |
478 | //console.log( 'handleMouseMovePan' );
479 |
480 | panEnd.set( event.clientX, event.clientY );
481 |
482 | panDelta.subVectors( panEnd, panStart );
483 |
484 | pan( panDelta.x, panDelta.y );
485 |
486 | panStart.copy( panEnd );
487 |
488 | scope.update();
489 |
490 | }
491 |
492 | function handleMouseUp( event ) {
493 |
494 | //console.log( 'handleMouseUp' );
495 |
496 | }
497 |
498 | function handleMouseWheel( event ) {
499 |
500 | //console.log( 'handleMouseWheel' );
501 |
502 | if ( event.deltaY < 0 ) {
503 |
504 | dollyOut( getZoomScale() );
505 |
506 | } else if ( event.deltaY > 0 ) {
507 |
508 | dollyIn( getZoomScale() );
509 |
510 | }
511 |
512 | scope.update();
513 |
514 | }
515 |
516 | function handleKeyDown( event ) {
517 |
518 | //console.log( 'handleKeyDown' );
519 |
520 | switch ( event.keyCode ) {
521 |
522 | case scope.keys.UP:
523 | pan( 0, scope.keyPanSpeed );
524 | scope.update();
525 | break;
526 |
527 | case scope.keys.BOTTOM:
528 | pan( 0, - scope.keyPanSpeed );
529 | scope.update();
530 | break;
531 |
532 | case scope.keys.LEFT:
533 | pan( scope.keyPanSpeed, 0 );
534 | scope.update();
535 | break;
536 |
537 | case scope.keys.RIGHT:
538 | pan( - scope.keyPanSpeed, 0 );
539 | scope.update();
540 | break;
541 |
542 | }
543 |
544 | }
545 |
546 | function handleTouchStartRotate( event ) {
547 |
548 | //console.log( 'handleTouchStartRotate' );
549 |
550 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
551 |
552 | }
553 |
554 | function handleTouchStartDolly( event ) {
555 |
556 | //console.log( 'handleTouchStartDolly' );
557 |
558 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
559 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
560 |
561 | var distance = Math.sqrt( dx * dx + dy * dy );
562 |
563 | dollyStart.set( 0, distance );
564 |
565 | }
566 |
567 | function handleTouchStartPan( event ) {
568 |
569 | //console.log( 'handleTouchStartPan' );
570 |
571 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
572 |
573 | }
574 |
575 | function handleTouchMoveRotate( event ) {
576 |
577 | //console.log( 'handleTouchMoveRotate' );
578 |
579 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
580 | rotateDelta.subVectors( rotateEnd, rotateStart );
581 |
582 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
583 |
584 | // rotating across whole screen goes 360 degrees around
585 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
586 |
587 | // rotating up and down along whole screen attempts to go 360, but limited to 180
588 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
589 |
590 | rotateStart.copy( rotateEnd );
591 |
592 | scope.update();
593 |
594 | }
595 |
596 | function handleTouchMoveDolly( event ) {
597 |
598 | //console.log( 'handleTouchMoveDolly' );
599 |
600 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
601 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
602 |
603 | var distance = Math.sqrt( dx * dx + dy * dy );
604 |
605 | dollyEnd.set( 0, distance );
606 |
607 | dollyDelta.subVectors( dollyEnd, dollyStart );
608 |
609 | if ( dollyDelta.y > 0 ) {
610 |
611 | dollyOut( getZoomScale() );
612 |
613 | } else if ( dollyDelta.y < 0 ) {
614 |
615 | dollyIn( getZoomScale() );
616 |
617 | }
618 |
619 | dollyStart.copy( dollyEnd );
620 |
621 | scope.update();
622 |
623 | }
624 |
625 | function handleTouchMovePan( event ) {
626 |
627 | //console.log( 'handleTouchMovePan' );
628 |
629 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
630 |
631 | panDelta.subVectors( panEnd, panStart );
632 |
633 | pan( panDelta.x, panDelta.y );
634 |
635 | panStart.copy( panEnd );
636 |
637 | scope.update();
638 |
639 | }
640 |
641 | function handleTouchEnd( event ) {
642 |
643 | //console.log( 'handleTouchEnd' );
644 |
645 | }
646 |
647 | //
648 | // event handlers - FSM: listen for events and reset state
649 | //
650 |
651 | function onMouseDown( event ) {
652 |
653 | if ( scope.enabled === false ) return;
654 |
655 | event.preventDefault();
656 |
657 | if ( event.button === scope.mouseButtons.ORBIT ) {
658 |
659 | if ( scope.enableRotate === false ) return;
660 |
661 | handleMouseDownRotate( event );
662 |
663 | state = STATE.ROTATE;
664 |
665 | } else if ( event.button === scope.mouseButtons.ZOOM ) {
666 |
667 | if ( scope.enableZoom === false ) return;
668 |
669 | handleMouseDownDolly( event );
670 |
671 | state = STATE.DOLLY;
672 |
673 | } else if ( event.button === scope.mouseButtons.PAN ) {
674 |
675 | if ( scope.enablePan === false ) return;
676 |
677 | handleMouseDownPan( event );
678 |
679 | state = STATE.PAN;
680 |
681 | }
682 |
683 | if ( state !== STATE.NONE ) {
684 |
685 | document.addEventListener( 'mousemove', onMouseMove, false );
686 | document.addEventListener( 'mouseup', onMouseUp, false );
687 |
688 | scope.dispatchEvent( startEvent );
689 |
690 | }
691 |
692 | }
693 |
694 | function onMouseMove( event ) {
695 |
696 | if ( scope.enabled === false ) return;
697 |
698 | event.preventDefault();
699 |
700 | if ( state === STATE.ROTATE ) {
701 |
702 | if ( scope.enableRotate === false ) return;
703 |
704 | handleMouseMoveRotate( event );
705 |
706 | } else if ( state === STATE.DOLLY ) {
707 |
708 | if ( scope.enableZoom === false ) return;
709 |
710 | handleMouseMoveDolly( event );
711 |
712 | } else if ( state === STATE.PAN ) {
713 |
714 | if ( scope.enablePan === false ) return;
715 |
716 | handleMouseMovePan( event );
717 |
718 | }
719 |
720 | }
721 |
722 | function onMouseUp( event ) {
723 |
724 | if ( scope.enabled === false ) return;
725 |
726 | handleMouseUp( event );
727 |
728 | document.removeEventListener( 'mousemove', onMouseMove, false );
729 | document.removeEventListener( 'mouseup', onMouseUp, false );
730 |
731 | scope.dispatchEvent( endEvent );
732 |
733 | state = STATE.NONE;
734 |
735 | }
736 |
737 | function onMouseWheel( event ) {
738 |
739 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
740 |
741 | event.preventDefault();
742 | event.stopPropagation();
743 |
744 | handleMouseWheel( event );
745 |
746 | scope.dispatchEvent( startEvent ); // not sure why these are here...
747 | scope.dispatchEvent( endEvent );
748 |
749 | }
750 |
751 | function onKeyDown( event ) {
752 |
753 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
754 |
755 | handleKeyDown( event );
756 |
757 | }
758 |
759 | function onTouchStart( event ) {
760 |
761 | if ( scope.enabled === false ) return;
762 |
763 | switch ( event.touches.length ) {
764 |
765 | case 1: // one-fingered touch: rotate
766 |
767 | if ( scope.enableRotate === false ) return;
768 |
769 | handleTouchStartRotate( event );
770 |
771 | state = STATE.TOUCH_ROTATE;
772 |
773 | break;
774 |
775 | case 2: // two-fingered touch: dolly
776 |
777 | if ( scope.enableZoom === false ) return;
778 |
779 | handleTouchStartDolly( event );
780 |
781 | state = STATE.TOUCH_DOLLY;
782 |
783 | break;
784 |
785 | case 3: // three-fingered touch: pan
786 |
787 | if ( scope.enablePan === false ) return;
788 |
789 | handleTouchStartPan( event );
790 |
791 | state = STATE.TOUCH_PAN;
792 |
793 | break;
794 |
795 | default:
796 |
797 | state = STATE.NONE;
798 |
799 | }
800 |
801 | if ( state !== STATE.NONE ) {
802 |
803 | scope.dispatchEvent( startEvent );
804 |
805 | }
806 |
807 | }
808 |
809 | function onTouchMove( event ) {
810 |
811 | if ( scope.enabled === false ) return;
812 |
813 | event.preventDefault();
814 | event.stopPropagation();
815 |
816 | switch ( event.touches.length ) {
817 |
818 | case 1: // one-fingered touch: rotate
819 |
820 | if ( scope.enableRotate === false ) return;
821 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...
822 |
823 | handleTouchMoveRotate( event );
824 |
825 | break;
826 |
827 | case 2: // two-fingered touch: dolly
828 |
829 | if ( scope.enableZoom === false ) return;
830 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...
831 |
832 | handleTouchMoveDolly( event );
833 |
834 | break;
835 |
836 | case 3: // three-fingered touch: pan
837 |
838 | if ( scope.enablePan === false ) return;
839 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...
840 |
841 | handleTouchMovePan( event );
842 |
843 | break;
844 |
845 | default:
846 |
847 | state = STATE.NONE;
848 |
849 | }
850 |
851 | }
852 |
853 | function onTouchEnd( event ) {
854 |
855 | if ( scope.enabled === false ) return;
856 |
857 | handleTouchEnd( event );
858 |
859 | scope.dispatchEvent( endEvent );
860 |
861 | state = STATE.NONE;
862 |
863 | }
864 |
865 | function onContextMenu( event ) {
866 |
867 | event.preventDefault();
868 |
869 | }
870 |
871 | //
872 |
873 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
874 |
875 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
876 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
877 |
878 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
879 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
880 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
881 |
882 | window.addEventListener( 'keydown', onKeyDown, false );
883 |
884 | // force an update at start
885 |
886 | this.update();
887 |
888 | };
889 |
890 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
891 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
892 |
893 | Object.defineProperties( THREE.OrbitControls.prototype, {
894 |
895 | center: {
896 |
897 | get: function () {
898 |
899 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
900 | return this.target;
901 |
902 | }
903 |
904 | },
905 |
906 | // backward compatibility
907 |
908 | noZoom: {
909 |
910 | get: function () {
911 |
912 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
913 | return ! this.enableZoom;
914 |
915 | },
916 |
917 | set: function ( value ) {
918 |
919 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
920 | this.enableZoom = ! value;
921 |
922 | }
923 |
924 | },
925 |
926 | noRotate: {
927 |
928 | get: function () {
929 |
930 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
931 | return ! this.enableRotate;
932 |
933 | },
934 |
935 | set: function ( value ) {
936 |
937 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
938 | this.enableRotate = ! value;
939 |
940 | }
941 |
942 | },
943 |
944 | noPan: {
945 |
946 | get: function () {
947 |
948 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
949 | return ! this.enablePan;
950 |
951 | },
952 |
953 | set: function ( value ) {
954 |
955 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
956 | this.enablePan = ! value;
957 |
958 | }
959 |
960 | },
961 |
962 | noKeys: {
963 |
964 | get: function () {
965 |
966 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
967 | return ! this.enableKeys;
968 |
969 | },
970 |
971 | set: function ( value ) {
972 |
973 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
974 | this.enableKeys = ! value;
975 |
976 | }
977 |
978 | },
979 |
980 | staticMoving : {
981 |
982 | get: function () {
983 |
984 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
985 | return ! this.enableDamping;
986 |
987 | },
988 |
989 | set: function ( value ) {
990 |
991 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
992 | this.enableDamping = ! value;
993 |
994 | }
995 |
996 | },
997 |
998 | dynamicDampingFactor : {
999 |
1000 | get: function () {
1001 |
1002 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
1003 | return this.dampingFactor;
1004 |
1005 | },
1006 |
1007 | set: function ( value ) {
1008 |
1009 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
1010 | this.dampingFactor = value;
1011 |
1012 | }
1013 |
1014 | }
1015 |
1016 | } );
1017 |
--------------------------------------------------------------------------------