├── test ├── test.simulator.js ├── screeny.png └── test.utils.js ├── .gitignore ├── lib ├── util.js ├── box.js ├── math │ ├── point.js │ └── matrix.js ├── simulator.js ├── scene.js └── OrbitControls.js ├── readme.md ├── package.json ├── bin └── gsim └── index.html /test/test.simulator.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /test/screeny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/em/gsim/master/test/screeny.png -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var util = module.exports = {}; 2 | 3 | util.pointOnPath = function(path,t) { 4 | } 5 | -------------------------------------------------------------------------------- /test/test.utils.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect 2 | , utils = require('../lib/utils'); 3 | 4 | describe('Simulator', function() { 5 | it('#arcToPoints', function() { 6 | expect(result.end.y).closeTo(5, 0.000001); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Gsim 2 | 3 | Cross-platform command-line gcode simulator. 4 | 5 | ``` 6 | cat smile.gcode | gsim 7 | ``` 8 | 9 |  10 | 11 | 12 | ## Installation 13 | ``` 14 | npm install -g gsim 15 | ``` 16 | 17 | ## Meta Comments 18 | 19 | Gsim can be annotated with addition information about the job with meta attribute comments. 20 | The format is simply `(key=value key2=value2)`. 21 | 22 | ``` 23 | (tooldiameter=0.5) 24 | G0 X1 25 | G0 Y1 26 | ... 27 | ``` 28 | 29 | My hope is that other generators and simulators will adopt this convention and collaborate towards a standard. 30 | 31 | Currently `tooldiameter` is the only attribute. 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.html", 3 | "name": "gsim", 4 | "description": "Gcode Simulator", 5 | "version": "0.0.6", 6 | "keywords": [ 7 | "gcode", 8 | "simulator" 9 | ], 10 | "bin": { 11 | "gsim": "./bin/gsim" 12 | }, 13 | "window": { 14 | "title": "Gsim", 15 | "icon": "link.png", 16 | "toolbar": false, 17 | "frame": true, 18 | "width": 800, 19 | "height": 500, 20 | "position": "mouse", 21 | "min_width": 400, 22 | "min_height": 200 23 | }, 24 | "webkit": { 25 | "plugin": true 26 | }, 27 | "dependencies": { 28 | "nodewebkit": "~0.8.6-3", 29 | "readline": "0.0.3" 30 | }, 31 | "devDependencies": { 32 | "mocha": "~1.20.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/box.js: -------------------------------------------------------------------------------- 1 | module.exports = Box; 2 | 3 | function Box() { 4 | this.xmin = 0; 5 | this.xmax = 0; 6 | this.ymin = 0; 7 | this.ymax = 0; 8 | this.zmin = 0; 9 | this.zmax = 0; 10 | } 11 | 12 | Box.prototype.fitTo = function(p) { 13 | this.xmin = Math.min(p.x, this.xmin); 14 | this.ymin = Math.min(p.y, this.ymin); 15 | this.zmin = Math.min(p.z, this.zmin); 16 | this.xmax = Math.max(p.x, this.xmax); 17 | this.ymax = Math.max(p.y, this.ymax); 18 | this.zmax = Math.max(p.z, this.zmax); 19 | } 20 | 21 | Box.prototype.width = function() { 22 | return this.xmax - this.xmin; 23 | } 24 | 25 | Box.prototype.height = function() { 26 | return this.ymax - this.ymin; 27 | } 28 | 29 | Box.prototype.cx = function() { 30 | return this.xmin + this.width() / 2; 31 | } 32 | 33 | Box.prototype.cy = function() { 34 | return this.ymin + this.height() / 2; 35 | } 36 | -------------------------------------------------------------------------------- /bin/gsim: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var net = require('net'); 5 | var exec = require('child_process').spawn; 6 | var repl = require('repl'); 7 | var readline = require('readline'); 8 | 9 | 10 | // This server listens on a Unix socket at /var/run/mysocket 11 | var unixServer = net.createServer(function(client) { 12 | var rl = readline.createInterface({ 13 | input: process.stdin, 14 | output: process.stdout 15 | }); 16 | 17 | rl.on('line', function(gcode) { 18 | client.write(gcode+'\n'); 19 | }); 20 | }); 21 | 22 | 23 | // var socket = path.join(__dirname, '/socket'); 24 | var socket = 9999; 25 | 26 | unixServer.listen(socket); 27 | 28 | var gui = exec( path.join(__dirname, '../node_modules/.bin/nodewebkit'), [], { 29 | cwd: path.join(__dirname, '../') 30 | }); 31 | 32 | gui.on('exit', function(code, signal) { 33 | process.exit(code); 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |7 | * _ _ 8 | * | a c tx | 9 | * | b d ty | 10 | * |_0 0 1 _| 11 | *12 | * Creates a matrix for 2d affine transformations. 13 | * 14 | * concat, inverse, rotate, scale and translate return new matrices with the 15 | * transformations applied. The matrix is not modified in place. 16 | * 17 | * Returns the identity matrix when called with no arguments. 18 | * @name Matrix 19 | * @param {Number} [a] 20 | * @param {Number} [b] 21 | * @param {Number} [c] 22 | * @param {Number} [d] 23 | * @param {Number} [tx] 24 | * @param {Number} [ty] 25 | * @constructor 26 | */ 27 | function Matrix(a, b, c, d, tx, ty) { 28 | this.a = a !== undefined ? a : 1; 29 | this.b = b || 0; 30 | this.c = c || 0; 31 | this.d = d !== undefined ? d : 1; 32 | this.tx = tx || 0; 33 | this.ty = ty || 0; 34 | } 35 | 36 | Matrix.prototype = { 37 | 38 | clone: function() { 39 | return new Matrix( 40 | this.a, 41 | this.b, 42 | this.c, 43 | this.d, 44 | this.tx, 45 | this.ty 46 | ); 47 | }, 48 | 49 | /** 50 | * Returns the result of this matrix multiplied by another matrix 51 | * combining the geometric effects of the two. In mathematical terms, 52 | * concatenating two matrixes is the same as combining them using matrix multiplication. 53 | * If this matrix is A and the matrix passed in is B, the resulting matrix is A x B 54 | * http://mathworld.wolfram.com/MatrixMultiplication.html 55 | * @name concat 56 | * @methodOf Matrix# 57 | * 58 | * @param {Matrix} matrix The matrix to multiply this matrix by. 59 | * @returns The result of the matrix multiplication, a new matrix. 60 | * @type Matrix 61 | */ 62 | concat: function(matrix) { 63 | return new Matrix( 64 | this.a * matrix.a + this.c * matrix.b, 65 | this.b * matrix.a + this.d * matrix.b, 66 | this.a * matrix.c + this.c * matrix.d, 67 | this.b * matrix.c + this.d * matrix.d, 68 | this.a * matrix.tx + this.c * matrix.ty + this.tx, 69 | this.b * matrix.tx + this.d * matrix.ty + this.ty 70 | ); 71 | }, 72 | 73 | /** 74 | * Given a point in the pretransform coordinate space, returns the coordinates of 75 | * that point after the transformation occurs. Unlike the standard transformation 76 | * applied using the transformnew Point() method, the deltaTransformnew Point() method's 77 | * transformation does not consider the translation parameters tx and ty. 78 | * @name deltaTransformPoint 79 | * @methodOf Matrix# 80 | * @see #transformPoint 81 | * 82 | * @return A new point transformed by this matrix ignoring tx and ty. 83 | * @type Point 84 | */ 85 | deltaTransformPoint: function(point) { 86 | return new Point( 87 | this.a * point.x + this.c * point.y, 88 | this.b * point.x + this.d * point.y 89 | ); 90 | }, 91 | 92 | /** 93 | * Returns the inverse of the matrix. 94 | * http://mathworld.wolfram.com/MatrixInverse.html 95 | * @name inverse 96 | * @methodOf Matrix# 97 | * 98 | * @returns A new matrix that is the inverse of this matrix. 99 | * @type Matrix 100 | */ 101 | inverse: function() { 102 | var determinant = this.a * this.d - this.b * this.c; 103 | return new Matrix( 104 | this.d / determinant, 105 | -this.b / determinant, 106 | -this.c / determinant, 107 | this.a / determinant, 108 | (this.c * this.ty - this.d * this.tx) / determinant, 109 | (this.b * this.tx - this.a * this.ty) / determinant 110 | ); 111 | }, 112 | 113 | /** 114 | * Returns a new matrix that corresponds this matrix multiplied by a 115 | * a rotation matrix. 116 | * @name rotate 117 | * @methodOf Matrix# 118 | * @see Matrix.rotation 119 | * 120 | * @param {Number} theta Amount to rotate in radians. 121 | * @param {Point} [aboutPoint] The point about which this rotation occurs. Defaults to (0,0). 122 | * @returns A new matrix, rotated by the specified amount. 123 | * @type Matrix 124 | */ 125 | rotate: function(theta, aboutPoint) { 126 | return this.concat(Matrix.rotation(theta, aboutPoint)); 127 | }, 128 | 129 | /** 130 | * Returns a new matrix that corresponds this matrix multiplied by a 131 | * a scaling matrix. 132 | * @name scale 133 | * @methodOf Matrix# 134 | * @see Matrix.scale 135 | * 136 | * @param {Number} sx 137 | * @param {Number} [sy] 138 | * @param {Point} [aboutPoint] The point that remains fixed during the scaling 139 | * @type Matrix 140 | */ 141 | scale: function(sx, sy, aboutPoint) { 142 | return this.concat(Matrix.scale(sx, sy, aboutPoint)); 143 | }, 144 | 145 | /** 146 | * Returns the result of applying the geometric transformation represented by the 147 | * Matrix object to the specified point. 148 | * @name transformPoint 149 | * @methodOf Matrix# 150 | * @see #deltaTransformPoint 151 | * 152 | * @returns A new point with the transformation applied. 153 | * @type Point 154 | */ 155 | transformPoint: function(point) { 156 | return new Point( 157 | this.a * point.x + this.c * point.y + this.tx, 158 | this.b * point.x + this.d * point.y + this.ty 159 | ); 160 | }, 161 | 162 | /** 163 | * Translates the matrix along the x and y axes, as specified by the tx and ty parameters. 164 | * @name translate 165 | * @methodOf Matrix# 166 | * @see Matrix.translation 167 | * 168 | * @param {Number} tx The translation along the x axis. 169 | * @param {Number} ty The translation along the y axis. 170 | * @returns A new matrix with the translation applied. 171 | * @type Matrix 172 | */ 173 | translate: function(tx, ty) { 174 | return this.concat(Matrix.translation(tx, ty)); 175 | } 176 | }; 177 | 178 | /** 179 | * Creates a matrix transformation that corresponds to the given rotation, 180 | * around (0,0) or the specified point. 181 | * @see Matrix#rotate 182 | * 183 | * @param {Number} theta Rotation in radians. 184 | * @param {Point} [aboutPoint] The point about which this rotation occurs. Defaults to (0,0). 185 | * @returns 186 | * @type Matrix 187 | */ 188 | Matrix.rotation = function(theta, aboutPoint) { 189 | var rotationMatrix = new Matrix( 190 | Math.cos(theta), 191 | Math.sin(theta), 192 | -Math.sin(theta), 193 | Math.cos(theta) 194 | ); 195 | 196 | if(aboutPoint) { 197 | rotationMatrix = 198 | Matrix.translation(aboutPoint.x, aboutPoint.y).concat( 199 | rotationMatrix 200 | ).concat( 201 | Matrix.translation(-aboutPoint.x, -aboutPoint.y) 202 | ); 203 | } 204 | 205 | return rotationMatrix; 206 | }; 207 | 208 | /** 209 | * Returns a matrix that corresponds to scaling by factors of sx, sy along 210 | * the x and y axis respectively. 211 | * If only one parameter is given the matrix is scaled uniformly along both axis. 212 | * If the optional aboutPoint parameter is given the scaling takes place 213 | * about the given point. 214 | * @see Matrix#scale 215 | * 216 | * @param {Number} sx The amount to scale by along the x axis or uniformly if no sy is given. 217 | * @param {Number} [sy] The amount to scale by along the y axis. 218 | * @param {Point} [aboutPoint] The point about which the scaling occurs. Defaults to (0,0). 219 | * @returns A matrix transformation representing scaling by sx and sy. 220 | * @type Matrix 221 | */ 222 | Matrix.scale = function(sx, sy, aboutPoint) { 223 | sy = sy || sx; 224 | 225 | var scaleMatrix = new Matrix(sx, 0, 0, sy); 226 | 227 | if(aboutPoint) { 228 | scaleMatrix = 229 | Matrix.translation(aboutPoint.x, aboutPoint.y).concat( 230 | scaleMatrix 231 | ).concat( 232 | Matrix.translation(-aboutPoint.x, -aboutPoint.y) 233 | ); 234 | } 235 | 236 | return scaleMatrix; 237 | }; 238 | 239 | /** 240 | * Returns a matrix that corresponds to a translation of tx, ty. 241 | * @see Matrix#translate 242 | * 243 | * @param {Number} tx The amount to translate in the x direction. 244 | * @param {Number} ty The amount to translate in the y direction. 245 | * @return A matrix transformation representing a translation by tx and ty. 246 | * @type Matrix 247 | */ 248 | Matrix.translation = function(tx, ty) { 249 | return new Matrix(1, 0, 0, 1, tx, ty); 250 | }; 251 | 252 | /** 253 | * A constant representing the identity matrix. 254 | * @name IDENTITY 255 | * @fieldOf Matrix 256 | */ 257 | Matrix.IDENTITY = new Matrix(); 258 | /** 259 | * A constant representing the horizontal flip transformation matrix. 260 | * @name HORIZONTAL_FLIP 261 | * @fieldOf Matrix 262 | */ 263 | Matrix.HORIZONTAL_FLIP = new Matrix(-1, 0, 0, 1); 264 | /** 265 | * A constant representing the vertical flip transformation matrix. 266 | * @name VERTICAL_FLIP 267 | * @fieldOf Matrix 268 | */ 269 | Matrix.VERTICAL_FLIP = new Matrix(1, 0, 0, -1); 270 | -------------------------------------------------------------------------------- /lib/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 | /*global THREE, console */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 12 | // supported. 13 | // 14 | // Orbit - left mouse / touch: one finger move 15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 16 | // Pan - right mouse, or arrow keys / touch: three finter swipe 17 | // 18 | // This is a drop-in replacement for (most) TrackballControls used in examples. 19 | // That is, include this js file and wherever you see: 20 | // controls = new THREE.TrackballControls( camera ); 21 | // controls.target.z = 150; 22 | // Simple substitute "OrbitControls" and the control should work as-is. 23 | 24 | THREE.OrbitControls = function ( object, domElement ) { 25 | 26 | this.object = object; 27 | this.domElement = ( domElement !== undefined ) ? domElement : document; 28 | 29 | // API 30 | 31 | // Set to false to disable this control 32 | this.enabled = true; 33 | 34 | // "target" sets the location of focus, where the control orbits around 35 | // and where it pans with respect to. 36 | this.target = new THREE.Vector3(); 37 | 38 | // center is old, deprecated; use "target" instead 39 | this.center = this.target; 40 | 41 | // This option actually enables dollying in and out; left as "zoom" for 42 | // backwards compatibility 43 | this.noZoom = false; 44 | this.zoomSpeed = 1.0; 45 | 46 | // Limits to how far you can dolly in and out 47 | this.minDistance = 0; 48 | this.maxDistance = Infinity; 49 | 50 | // Set to true to disable this control 51 | this.noRotate = false; 52 | this.rotateSpeed = 1.0; 53 | 54 | // Set to true to disable this control 55 | this.noPan = false; 56 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 57 | 58 | // Set to true to automatically rotate around the target 59 | this.autoRotate = false; 60 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 61 | 62 | // How far you can orbit vertically, upper and lower limits. 63 | // Range is 0 to Math.PI radians. 64 | this.minPolarAngle = 0; // radians 65 | this.maxPolarAngle = Math.PI; // radians 66 | 67 | // Set to true to disable use of the keys 68 | this.noKeys = false; 69 | 70 | // The four arrow keys 71 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 72 | 73 | //////////// 74 | // internals 75 | 76 | var scope = this; 77 | 78 | var EPS = 0.000001; 79 | 80 | var rotateStart = new THREE.Vector2(); 81 | var rotateEnd = new THREE.Vector2(); 82 | var rotateDelta = new THREE.Vector2(); 83 | 84 | var panStart = new THREE.Vector2(); 85 | var panEnd = new THREE.Vector2(); 86 | var panDelta = new THREE.Vector2(); 87 | var panOffset = new THREE.Vector3(); 88 | 89 | var offset = new THREE.Vector3(); 90 | 91 | var dollyStart = new THREE.Vector2(); 92 | var dollyEnd = new THREE.Vector2(); 93 | var dollyDelta = new THREE.Vector2(); 94 | 95 | var phiDelta = 0; 96 | var thetaDelta = 0; 97 | var scale = 1; 98 | var pan = new THREE.Vector3(); 99 | 100 | var lastPosition = new THREE.Vector3(); 101 | 102 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 103 | 104 | var state = STATE.NONE; 105 | 106 | // for reset 107 | 108 | this.target0 = this.target.clone(); 109 | this.position0 = this.object.position.clone(); 110 | 111 | // so camera.up is the orbit axis 112 | 113 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 114 | var quatInverse = quat.clone().inverse(); 115 | 116 | // events 117 | 118 | var changeEvent = { type: 'change' }; 119 | var startEvent = { type: 'start'}; 120 | var endEvent = { type: 'end'}; 121 | 122 | this.rotateLeft = function ( angle ) { 123 | 124 | if ( angle === undefined ) { 125 | 126 | angle = getAutoRotationAngle(); 127 | 128 | } 129 | 130 | thetaDelta -= angle; 131 | 132 | }; 133 | 134 | this.rotateUp = function ( angle ) { 135 | 136 | if ( angle === undefined ) { 137 | 138 | angle = getAutoRotationAngle(); 139 | 140 | } 141 | 142 | phiDelta -= angle; 143 | 144 | }; 145 | 146 | // pass in distance in world space to move left 147 | this.panLeft = function ( distance ) { 148 | 149 | var te = this.object.matrix.elements; 150 | 151 | // get X column of matrix 152 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 153 | panOffset.multiplyScalar( - distance ); 154 | 155 | pan.add( panOffset ); 156 | 157 | }; 158 | 159 | // pass in distance in world space to move up 160 | this.panUp = function ( distance ) { 161 | 162 | var te = this.object.matrix.elements; 163 | 164 | // get Y column of matrix 165 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 166 | panOffset.multiplyScalar( distance ); 167 | 168 | pan.add( panOffset ); 169 | 170 | }; 171 | 172 | // pass in x,y of change desired in pixel space, 173 | // right and down are positive 174 | this.pan = function ( deltaX, deltaY ) { 175 | 176 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 177 | 178 | if ( scope.object.fov !== undefined ) { 179 | 180 | // perspective 181 | var position = scope.object.position; 182 | var offset = position.clone().sub( scope.target ); 183 | var targetDistance = offset.length(); 184 | 185 | // half of the fov is center to top of screen 186 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 187 | 188 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 189 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); 190 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); 191 | 192 | } else if ( scope.object.top !== undefined ) { 193 | 194 | // orthographic 195 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); 196 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); 197 | 198 | } else { 199 | 200 | // camera neither orthographic or perspective 201 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 202 | 203 | } 204 | 205 | }; 206 | 207 | this.dollyIn = function ( dollyScale ) { 208 | 209 | if ( dollyScale === undefined ) { 210 | 211 | dollyScale = getZoomScale(); 212 | 213 | } 214 | 215 | scale /= dollyScale; 216 | 217 | }; 218 | 219 | this.dollyOut = function ( dollyScale ) { 220 | 221 | if ( dollyScale === undefined ) { 222 | 223 | dollyScale = getZoomScale(); 224 | 225 | } 226 | 227 | scale *= dollyScale; 228 | 229 | }; 230 | 231 | this.update = function () { 232 | 233 | var position = this.object.position; 234 | 235 | offset.copy( position ).sub( this.target ); 236 | 237 | // rotate offset to "y-axis-is-up" space 238 | offset.applyQuaternion( quat ); 239 | 240 | // angle from z-axis around y-axis 241 | 242 | var theta = Math.atan2( offset.x, offset.z ); 243 | 244 | // angle from y-axis 245 | 246 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 247 | 248 | if ( this.autoRotate ) { 249 | 250 | this.rotateLeft( getAutoRotationAngle() ); 251 | 252 | } 253 | 254 | theta += thetaDelta; 255 | phi += phiDelta; 256 | 257 | // restrict phi to be between desired limits 258 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 259 | 260 | // restrict phi to be betwee EPS and PI-EPS 261 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 262 | 263 | var radius = offset.length() * scale; 264 | 265 | // restrict radius to be between desired limits 266 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 267 | 268 | // move target to panned location 269 | this.target.add( pan ); 270 | 271 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 272 | offset.y = radius * Math.cos( phi ); 273 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 274 | 275 | // rotate offset back to "camera-up-vector-is-up" space 276 | offset.applyQuaternion( quatInverse ); 277 | 278 | position.copy( this.target ).add( offset ); 279 | 280 | this.object.lookAt( this.target ); 281 | 282 | thetaDelta = 0; 283 | phiDelta = 0; 284 | scale = 1; 285 | pan.set( 0, 0, 0 ); 286 | 287 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS ) { 288 | 289 | this.dispatchEvent( changeEvent ); 290 | 291 | lastPosition.copy( this.object.position ); 292 | 293 | } 294 | 295 | }; 296 | 297 | 298 | this.reset = function () { 299 | 300 | state = STATE.NONE; 301 | 302 | this.target.copy( this.target0 ); 303 | this.object.position.copy( this.position0 ); 304 | 305 | this.update(); 306 | 307 | }; 308 | 309 | function getAutoRotationAngle() { 310 | 311 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 312 | 313 | } 314 | 315 | function getZoomScale() { 316 | 317 | return Math.pow( 0.95, scope.zoomSpeed ); 318 | 319 | } 320 | 321 | function onMouseDown( event ) { 322 | 323 | if ( scope.enabled === false ) return; 324 | event.preventDefault(); 325 | 326 | if ( event.button === 0 ) { 327 | if ( scope.noRotate === true ) return; 328 | 329 | state = STATE.ROTATE; 330 | 331 | rotateStart.set( event.clientX, event.clientY ); 332 | 333 | } else if ( event.button === 1 ) { 334 | if ( scope.noZoom === true ) return; 335 | 336 | state = STATE.DOLLY; 337 | 338 | dollyStart.set( event.clientX, event.clientY ); 339 | 340 | } else if ( event.button === 2 ) { 341 | if ( scope.noPan === true ) return; 342 | 343 | state = STATE.PAN; 344 | 345 | panStart.set( event.clientX, event.clientY ); 346 | 347 | } 348 | 349 | scope.domElement.addEventListener( 'mousemove', onMouseMove, false ); 350 | scope.domElement.addEventListener( 'mouseup', onMouseUp, false ); 351 | scope.dispatchEvent( startEvent ); 352 | 353 | } 354 | 355 | function onMouseMove( event ) { 356 | 357 | if ( scope.enabled === false ) return; 358 | 359 | event.preventDefault(); 360 | 361 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 362 | 363 | if ( state === STATE.ROTATE ) { 364 | 365 | if ( scope.noRotate === true ) return; 366 | 367 | rotateEnd.set( event.clientX, event.clientY ); 368 | rotateDelta.subVectors( rotateEnd, rotateStart ); 369 | 370 | // rotating across whole screen goes 360 degrees around 371 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 372 | 373 | // rotating up and down along whole screen attempts to go 360, but limited to 180 374 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 375 | 376 | rotateStart.copy( rotateEnd ); 377 | 378 | } else if ( state === STATE.DOLLY ) { 379 | 380 | if ( scope.noZoom === true ) return; 381 | 382 | dollyEnd.set( event.clientX, event.clientY ); 383 | dollyDelta.subVectors( dollyEnd, dollyStart ); 384 | 385 | if ( dollyDelta.y > 0 ) { 386 | 387 | scope.dollyIn(); 388 | 389 | } else { 390 | 391 | scope.dollyOut(); 392 | 393 | } 394 | 395 | dollyStart.copy( dollyEnd ); 396 | 397 | } else if ( state === STATE.PAN ) { 398 | 399 | if ( scope.noPan === true ) return; 400 | 401 | panEnd.set( event.clientX, event.clientY ); 402 | panDelta.subVectors( panEnd, panStart ); 403 | 404 | scope.pan( panDelta.x, panDelta.y ); 405 | 406 | panStart.copy( panEnd ); 407 | 408 | } 409 | 410 | scope.update(); 411 | 412 | } 413 | 414 | function onMouseUp( /* event */ ) { 415 | 416 | if ( scope.enabled === false ) return; 417 | 418 | scope.domElement.removeEventListener( 'mousemove', onMouseMove, false ); 419 | scope.domElement.removeEventListener( 'mouseup', onMouseUp, false ); 420 | scope.dispatchEvent( endEvent ); 421 | state = STATE.NONE; 422 | 423 | } 424 | 425 | function onMouseWheel( event ) { 426 | 427 | if ( scope.enabled === false || scope.noZoom === true ) return; 428 | 429 | event.preventDefault(); 430 | event.stopPropagation(); 431 | 432 | var delta = 0; 433 | 434 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 435 | 436 | delta = event.wheelDelta; 437 | 438 | } else if ( event.detail !== undefined ) { // Firefox 439 | 440 | delta = - event.detail; 441 | 442 | } 443 | 444 | if ( delta > 0 ) { 445 | 446 | scope.dollyOut(); 447 | 448 | } else { 449 | 450 | scope.dollyIn(); 451 | 452 | } 453 | 454 | scope.update(); 455 | scope.dispatchEvent( startEvent ); 456 | scope.dispatchEvent( endEvent ); 457 | 458 | } 459 | 460 | function onKeyDown( event ) { 461 | 462 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; 463 | 464 | switch ( event.keyCode ) { 465 | 466 | case scope.keys.UP: 467 | scope.pan( 0, scope.keyPanSpeed ); 468 | scope.update(); 469 | break; 470 | 471 | case scope.keys.BOTTOM: 472 | scope.pan( 0, - scope.keyPanSpeed ); 473 | scope.update(); 474 | break; 475 | 476 | case scope.keys.LEFT: 477 | scope.pan( scope.keyPanSpeed, 0 ); 478 | scope.update(); 479 | break; 480 | 481 | case scope.keys.RIGHT: 482 | scope.pan( - scope.keyPanSpeed, 0 ); 483 | scope.update(); 484 | break; 485 | 486 | } 487 | 488 | } 489 | 490 | function touchstart( event ) { 491 | 492 | if ( scope.enabled === false ) return; 493 | 494 | switch ( event.touches.length ) { 495 | 496 | case 1: // one-fingered touch: rotate 497 | 498 | if ( scope.noRotate === true ) return; 499 | 500 | state = STATE.TOUCH_ROTATE; 501 | 502 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 503 | break; 504 | 505 | case 2: // two-fingered touch: dolly 506 | 507 | if ( scope.noZoom === true ) return; 508 | 509 | state = STATE.TOUCH_DOLLY; 510 | 511 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 512 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 513 | var distance = Math.sqrt( dx * dx + dy * dy ); 514 | dollyStart.set( 0, distance ); 515 | break; 516 | 517 | case 3: // three-fingered touch: pan 518 | 519 | if ( scope.noPan === true ) return; 520 | 521 | state = STATE.TOUCH_PAN; 522 | 523 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 524 | break; 525 | 526 | default: 527 | 528 | state = STATE.NONE; 529 | 530 | } 531 | 532 | scope.dispatchEvent( startEvent ); 533 | 534 | } 535 | 536 | function touchmove( event ) { 537 | 538 | if ( scope.enabled === false ) return; 539 | 540 | event.preventDefault(); 541 | event.stopPropagation(); 542 | 543 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 544 | 545 | switch ( event.touches.length ) { 546 | 547 | case 1: // one-fingered touch: rotate 548 | 549 | if ( scope.noRotate === true ) return; 550 | if ( state !== STATE.TOUCH_ROTATE ) return; 551 | 552 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 553 | rotateDelta.subVectors( rotateEnd, rotateStart ); 554 | 555 | // rotating across whole screen goes 360 degrees around 556 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 557 | // rotating up and down along whole screen attempts to go 360, but limited to 180 558 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 559 | 560 | rotateStart.copy( rotateEnd ); 561 | 562 | scope.update(); 563 | break; 564 | 565 | case 2: // two-fingered touch: dolly 566 | 567 | if ( scope.noZoom === true ) return; 568 | if ( state !== STATE.TOUCH_DOLLY ) return; 569 | 570 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 571 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 572 | var distance = Math.sqrt( dx * dx + dy * dy ); 573 | 574 | dollyEnd.set( 0, distance ); 575 | dollyDelta.subVectors( dollyEnd, dollyStart ); 576 | 577 | if ( dollyDelta.y > 0 ) { 578 | 579 | scope.dollyOut(); 580 | 581 | } else { 582 | 583 | scope.dollyIn(); 584 | 585 | } 586 | 587 | dollyStart.copy( dollyEnd ); 588 | 589 | scope.update(); 590 | break; 591 | 592 | case 3: // three-fingered touch: pan 593 | 594 | if ( scope.noPan === true ) return; 595 | if ( state !== STATE.TOUCH_PAN ) return; 596 | 597 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 598 | panDelta.subVectors( panEnd, panStart ); 599 | 600 | scope.pan( panDelta.x, panDelta.y ); 601 | 602 | panStart.copy( panEnd ); 603 | 604 | scope.update(); 605 | break; 606 | 607 | default: 608 | 609 | state = STATE.NONE; 610 | 611 | } 612 | 613 | } 614 | 615 | function touchend( /* event */ ) { 616 | 617 | if ( scope.enabled === false ) return; 618 | 619 | scope.dispatchEvent( endEvent ); 620 | state = STATE.NONE; 621 | 622 | } 623 | 624 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 625 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 626 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 627 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 628 | 629 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 630 | this.domElement.addEventListener( 'touchend', touchend, false ); 631 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 632 | 633 | window.addEventListener( 'keydown', onKeyDown, false ); 634 | 635 | // force an update at start 636 | this.update(); 637 | 638 | }; 639 | 640 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 641 | --------------------------------------------------------------------------------