├── .gitignore ├── README.md ├── assets ├── codechisel-github-image.png └── codechisel-github-image.psd ├── examples ├── CodeChisel3D_2014-12-16_demo.html ├── simple.html └── webvr.html ├── lib └── experiments.js └── vendor ├── dat.gui.js ├── lively.ast.dev.js ├── lively.lang.dev.js ├── lively.vm.dev-bundle.js ├── lively.vm.dev.js ├── mousetrap.js ├── three-codeeditor ├── codeeditor3d.dev.js ├── keybinding-emacs.js ├── mode-javascript.js ├── snippets │ ├── javascript.js │ └── text.js └── theme-twilight.js ├── three-world-with-tquery.dev.js └── three ├── OrbitControls.js ├── TransformControls.js ├── VRControls.js ├── VREffect.js ├── three.js └── tquery.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](blob/master/assets/codechisel-github-image.png?raw=true) 2 | 3 | A 3D live programming experiment. 4 | 5 | For more info see the [project page](https://robert.kra.hn/projects/live-programming-with-three-and-webvr). 6 | -------------------------------------------------------------------------------- /assets/codechisel-github-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdglabs/CodeChisel3D/67db648bbef0ffe8614b15d5d05369fd83ac3cab/assets/codechisel-github-image.png -------------------------------------------------------------------------------- /assets/codechisel-github-image.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdglabs/CodeChisel3D/67db648bbef0ffe8614b15d5d05369fd83ac3cab/assets/codechisel-github-image.psd -------------------------------------------------------------------------------- /examples/CodeChisel3D_2014-12-16_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CodeChisel3D 2014-12-16 demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /examples/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple THREE codeeditor setup 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /examples/webvr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple THREE codeeditor setup 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /lib/experiments.js: -------------------------------------------------------------------------------- 1 | // "imports" 2 | 3 | var pickingRay = THREE.CodeEditor.raycasting.pickingRay; 4 | var getRelativeMouseXYFromEvent = THREE.CodeEditor.raycasting.getRelativeMouseXYFromEvent; 5 | var getRelativeMouseXY = THREE.CodeEditor.raycasting.getRelativeMouseXY; 6 | var pickObjFromDOMEvent = THREE.CodeEditor.raycasting.pickObjFromDOMEvent; 7 | var isFullscreen = THREE.CodeEditor.domevents.isFullscreen; 8 | 9 | var userOptions = lively.lang.obj.extend({ 10 | "fullscreen": function() { world.enterFullScreen(); }, 11 | "align": "not aligned", 12 | "editor height": codeEditor.getHeight(), 13 | "editor width": codeEditor.getWidth(), 14 | "shoot ray": function() { drawRay({x:0,y:0}); }, 15 | "remove rays": removeRays, 16 | "show console": false, 17 | _setConsole: function _setConsole(val) { 18 | gui && gui.saveToLocalStorageIfPossible(); 19 | var log = document.querySelector("#log"); 20 | if (!log && !val) return; 21 | if (!log) return loadConsoleScript(function() { _setConsole(val); }); 22 | var style = log.style; 23 | style.display = val ? "" : "none"; 24 | } 25 | }, userOptions || {}); 26 | 27 | 28 | // some convenient extension of global objects: 29 | lively.lang.deprecatedLivelyPatches(); 30 | 31 | var storage = setupLocalstorage(); 32 | storage.restore(); 33 | var gui = setupDatGui(gui, userOptions, world, codeEditor); 34 | gui.update(); 35 | 36 | var inputState = initEvents(inputState); 37 | 38 | 39 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 40 | // events 41 | // -=-=-=- 42 | function initEvents(inputState) { 43 | 44 | inputState = inputState || { 45 | transformControl: null, 46 | 47 | keysPressed: [], 48 | 49 | keyPressed: function(key) { 50 | console.log("[inputState] key pressed: " + key); 51 | lively.lang.arr.pushIfNotIncluded(inputState.keysPressed, key); 52 | }, 53 | 54 | keyReleased: function(key) { 55 | console.log("[inputState] key released: " + key); 56 | inputState.keysPressed = lively.lang.arr.without(inputState.keysPressed, key); 57 | }, 58 | 59 | metaKeyPressed: function() { 60 | console.log(inputState.keysPressed); 61 | return inputState.keysPressed.indexOf("command") > -1 62 | || inputState.keysPressed.indexOf("ctrl") > -1; 63 | }, 64 | 65 | browserMousePosition: {x:0,y:0}, 66 | 67 | get mouseHandler() { return this._mouseHandler; }, 68 | set mouseHandler(val) { 69 | console.log("[inputState] mouseHandler changed: %s -> %s", 70 | printmouseHandler(this._mouseHandler), printmouseHandler(val)); 71 | return this._mouseHandler = val; 72 | function printmouseHandler(val) { 73 | if (!val) return "no handler"; 74 | else if (val.name) return val.name; 75 | else return "unknown handler " + val; 76 | } 77 | } 78 | } 79 | 80 | var el = world.renderer.domElement; 81 | 82 | function mouseDownRaw(evt) { window.onMouseDown(evt); } 83 | function mouseUpRaw(evt) { window.onMouseUp(evt); } 84 | function mouseMoveRaw(evt) { 85 | inputState.browserMousePosition.x = evt.pageX; 86 | inputState.browserMousePosition.y = evt.pageY; 87 | window.onMouseMove(evt); 88 | } 89 | 90 | el.addEventListener("mousedown", mouseDownRaw, false); 91 | el.addEventListener("mouseup", mouseUpRaw, false); 92 | el.addEventListener("mousemove", mouseMoveRaw, false); 93 | 94 | Mousetrap.bind("ctrl", function(evt) { inputState.keyReleased("ctrl"); }, 'keyup'); 95 | Mousetrap.bind("ctrl", function(evt) { inputState.keyPressed("ctrl"); }, 'keydown'); 96 | Mousetrap.bind("command", function(evt) { inputState.keyReleased("command"); }, 'keyup'); 97 | Mousetrap.bind("command", function(evt) { inputState.keyPressed("command"); }, 'keydown'); 98 | Mousetrap.bind("alt", function(evt) { inputState.keyReleased("alt"); }, 'keyup'); 99 | Mousetrap.bind("alt", function(evt) { inputState.keyPressed("alt"); }, 'keydown'); 100 | 101 | // editor alignment shortcuts 102 | [["alt+2", "center"], ["alt+1", "left"], ["alt+3", "right"]].forEach(function(keyCommand) { 103 | Mousetrap.bind(keyCommand[0], function(evt) { 104 | evt.preventDefault(); evt.stopPropagation(); 105 | if (userOptions.align === keyCommand[1]) userOptions.align = "not aligned"; 106 | else userOptions.align = keyCommand[1]; 107 | gui.update(); 108 | }, 'keydown'); 109 | }); 110 | 111 | Mousetrap.bind("f4", function() { drawRay(); }); 112 | 113 | Mousetrap.stopCallback = lively.lang.fun.wrap(Mousetrap.stopCallback, function(proceed, e, element) { return false; }) 114 | 115 | // transformControl 116 | inputState.transformControl = new THREE.TransformControls(world.camera, el); 117 | 118 | inputState.transformControl.addEventListener('change', function() { 119 | inputState.transformControl.update(); 120 | world.renderer.render( world.scene, world.camera ); 121 | }); 122 | 123 | 124 | var oc = world.orbitControl; 125 | if (oc) { 126 | // oc.__defineGetter__("enabled", function() { return !codeEditor.aceEditor.isFocused(); }); 127 | oc.__defineGetter__("enabled", function() { return inputState.keysPressed.indexOf("alt") > -1; }); 128 | 129 | oc.addEventListener('change', function(evt) { console.log('[inputState] orbit change ' + evt.type); }) 130 | oc.addEventListener('start', function(evt) { console.log('[inputState] orbit start'); }) 131 | oc.addEventListener('end', function(evt) { console.log('[inputState] orbit end'); }) 132 | } 133 | 134 | return inputState; 135 | } 136 | 137 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 138 | // mouse handlers 139 | // -=-=-=-=-=-=-=- 140 | 141 | // handlers 142 | 143 | function transformControlHandler(evt) { 144 | console.log("meta pressed? " + inputState.metaKeyPressed()); 145 | if (!inputState.metaKeyPressed()) return null; 146 | 147 | 148 | var hit = pickObjFromDOMEvent(evt, world.camera, world.scene.children, !!codeEditor.vr); 149 | if (!hit) return null; 150 | world.scene.add(inputState.transformControl); 151 | inputState.transformControl.attach(hit.object); 152 | 153 | var ctrl = inputState.transformControl; 154 | 155 | Mousetrap.bind("q", function(evt) { ctrl.setSpace(ctrl.space == "local" ? "world" : "local"); }); 156 | Mousetrap.bind("w", function(evt) { ctrl.setMode("translate"); }); 157 | Mousetrap.bind("e", function(evt) { ctrl.setMode("rotate"); }); 158 | Mousetrap.bind("r", function(evt) { ctrl.setMode("scale"); }); 159 | Mousetrap.bind("-", function(evt) { ctrl.setSize(ctrl.size - 0.1);}) 160 | Mousetrap.bind("+", function(evt) { ctrl.setSize(ctrl.size + 0.1);}) 161 | 162 | return { 163 | name: "transformControlHandler", 164 | handleMouseDown: function(evt) { 165 | if (!inputState.metaKeyPressed()) { inputState.transformControl.update(); return; } 166 | console.log('[inputState] transform control released'); 167 | inputState.transformControl.detach(inputState.transformControl.object); 168 | world.scene.remove(inputState.transformControl); 169 | inputState.mouseHandler = null; 170 | 171 | }, 172 | handleMouseMove: function(evt) { 173 | inputState.transformControl.update(); 174 | }, 175 | handleMouseUp: function(evt) {}, 176 | } 177 | } 178 | 179 | function dragHandler(evt) { 180 | evt.preventDefault(); evt.stopPropagation(); 181 | var camera = world.camera; 182 | var hit = pickObjFromDOMEvent(evt, world.camera, tQuery('box')._lists, !!codeEditor.vr); 183 | if (!hit) return null; 184 | 185 | var dragSphere = new THREE.Sphere( 186 | camera.position, 187 | camera.position.distanceTo( 188 | hit.object.position.clone())); 189 | 190 | console.log('[inputState] spherical drag handler installed'); 191 | return { 192 | name: "dragHandler", 193 | dragTarget: hit.object, 194 | hit: hit, 195 | dragSphere: dragSphere, 196 | handleMouseDown: function(evt) { inputState.mouseHandler = null; }, 197 | handleMouseMove: function(evt) { 198 | evt.preventDefault(); evt.stopPropagation(); 199 | var coords = getRelativeMouseXYFromEvent(evt, !!codeEditor.vr); 200 | var raycaster = pickingRay(coords, camera) 201 | var dragToPoint = raycaster.ray.intersectSphere(dragSphere); 202 | hit.object.position.copy(dragToPoint); 203 | }, 204 | handleMouseUp: function() { 205 | inputState.mouseHandler = null; 206 | console.log('[inputState] spherical drag handler released'); 207 | } 208 | } 209 | } 210 | 211 | function onMouseDown(evt) { 212 | console.log("window down"); 213 | window.LastEvent = evt; 214 | if (inputState.mouseHandler) inputState.mouseHandler.handleMouseDown(evt); 215 | 216 | if (inputState.mouseHandler) return; 217 | 218 | inputState.mouseHandler = transformControlHandler(evt) 219 | || dragHandler(evt); 220 | } 221 | 222 | function onMouseUp(evt) { 223 | if (inputState.mouseHandler) 224 | inputState.mouseHandler.handleMouseUp(evt); 225 | } 226 | 227 | function onMouseMove(evt) { 228 | if (inputState.mouseHandler) 229 | inputState.mouseHandler.handleMouseMove(evt); 230 | } 231 | 232 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 233 | // raycasting 234 | // -=-=-=-=-=- 235 | 236 | function drawRay(coords) { 237 | coords = coords || getRelativeMouseXY(inputState.browserMousePosition.x,inputState.browserMousePosition.y, world.renderer.domElement, !!codeEditor.vr); 238 | var raycaster = pickingRay(coords, world.camera) 239 | var intersection = raycaster.intersectObjects(world.scene.children.withoutAll(world.scene.children.groupByKey("type").Line || []))[0]; 240 | var from = randomPointOnSphere(5, raycaster.ray.origin) 241 | var to = intersection ? intersection.point : raycaster.ray.at(10000); 242 | console.log("[RAY] distance: %s, hit: %s", from.distanceTo(to), intersection ? intersection.object.type : 'none'); 243 | return tQuery.createLine(from, to).addTo(tQueryWorld) 244 | } 245 | 246 | function removeRays() { 247 | tQuery(world.scene.children.groupByKey("type").Line || []).removeFrom(tQueryWorld) 248 | } 249 | 250 | 251 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 252 | // dat gui setup 253 | // -=-=-=-=-=-=-=- 254 | 255 | function setupDatGui(gui, userOptions, world, codeEditor) { 256 | if (gui) gui.destroy(); 257 | gui = new dat.GUI({hideable: false}); 258 | 259 | gui.update = function() { 260 | var controllers = lively.lang.tree.map(this, 261 | function(ea) { return ea.__controllers; }, 262 | function(ea) { return lively.lang.obj.values(ea.__folders); }) 263 | lively.lang.chain(controllers).flatten() 264 | .filter(function(ea) { return ["number", "string", "boolean"].indexOf(typeof ea.getValue()) > -1; }) 265 | .forEach(function(ea) { ea.setValue(ea.getValue()); }); 266 | }; 267 | 268 | gui.addUserOptions = function(userOptions, world, codeEditor) { 269 | gui.add(userOptions, "fullscreen"); 270 | 271 | var f1 = gui.addFolder('editor') 272 | f1.open(); 273 | var f2 = gui.addFolder('debugging'); 274 | 275 | f1.add(userOptions, "align", ["not aligned", "left", "right", "center"]).onChange(function(dir) { 276 | if (dir === "not aligned") codeEditor.stopAutoAlignWithCamera(); 277 | else codeEditor.autoAlignWithCamera(dir, world.camera); 278 | storage.store(); 279 | }); 280 | 281 | f1.add(userOptions, 'editor height', 100, window.innerHeight+400).listen().onChange(function(val) { 282 | codeEditor.setHeight(val); 283 | storage.store(); 284 | }); 285 | var ctl = f1.add(userOptions, 'editor width', 100, window.innerWidth).listen().onChange(function(val) { 286 | codeEditor.setWidth(val); 287 | storage.store(); 288 | }); 289 | 290 | f2.add(userOptions, "shoot ray"); 291 | f2.add(userOptions, "remove rays"); 292 | f2.add(userOptions, 'show console').onChange(userOptions._setConsole) 293 | }; 294 | 295 | gui.addUserOptions(userOptions, world, codeEditor); 296 | 297 | return gui; 298 | } 299 | 300 | 301 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 302 | // local storage of editor content 303 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 304 | 305 | function setupLocalstorage() { 306 | 307 | var key = "editor:" + document.URL; 308 | var ed = codeEditor.aceEditor; 309 | 310 | if (codeEditor._autosaveOnChange) 311 | codeEditor.aceEditor.off("change", codeEditor._autosaveOnChange) 312 | codeEditor._autosaveOnChange = function() { lively.lang.fun.debounceNamed("editor-localstorage-process", 1000, store)(); } 313 | codeEditor.aceEditor.on("change", codeEditor._autosaveOnChange); 314 | 315 | window.addEventListener('beforeunload', function(evt) { store(); }); 316 | 317 | return { 318 | deleteStoredContent: deleteStoredContent, 319 | getStoredContent: getStoredContent, 320 | restore: restore, 321 | store: store 322 | } 323 | 324 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 325 | 326 | function deleteStoredContent() { delete localStorage[key]; } 327 | 328 | function getStoredContent() { 329 | try { var stored = JSON.parse(localStorage[key]); } catch(e) {} 330 | stored = stored || {}; 331 | if (!stored.editorContent) stored.editorContent = ""; 332 | if (!stored.versions) stored.versions = []; 333 | return stored; 334 | } 335 | 336 | function restore() { 337 | var stored = getStoredContent(); 338 | 339 | stored.versions.forEach(function(ea) { 340 | if (!ea || !ea.trim()) return; 341 | ed.setValue(ea); 342 | ed.session.markUndoGroup(); 343 | }); 344 | if (stored.editorContent && stored.editorContent.trim().length) 345 | ed.setValue(stored.editorContent); 346 | 347 | userOptions = lively.lang.obj.extend(userOptions || {}, stored.userOptions || {}); 348 | 349 | console.log("[storage] restored editor content from localStorage"); 350 | } 351 | 352 | function store() { 353 | var stored = getStoredContent(); 354 | if (stored.versions.length > 50) stored.versions.shift(); 355 | if (stored.editorContent && stored.editorContent !== stored.versions[stored.versions.length-1]) 356 | stored.versions.push(stored.editorContent); 357 | stored.editorContent = ed.getValue(); 358 | 359 | stored.userOptions = userOptions; 360 | 361 | localStorage[key] = JSON.stringify(stored); 362 | 363 | // console.log("[storage] stored editor content to localStorage"); 364 | } 365 | 366 | } 367 | 368 | 369 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 370 | // some helper 371 | 372 | function randomPointOnSphere(radius, sphereCenter) { 373 | // https://gielberkers.com/evenly-distribute-particles-shape-sphere-threejs/ 374 | var x = -1 + Math.random() * 2; 375 | var y = -1 + Math.random() * 2; 376 | var z = -1 + Math.random() * 2; 377 | var d = 1 / Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)); 378 | x *= d; 379 | y *= d; 380 | z *= d; 381 | return new THREE.Vector3(x * radius,y * radius,z * radius).add(sphereCenter); 382 | } 383 | 384 | 385 | function planeAtObjectParallelToCamera(rayHit) { 386 | var hitPos = rayHit.point.clone(); 387 | var dist = hitPos.distanceTo(tQuery.v(0,0,0)); 388 | // var dist = hitPos.distanceTo(rayHit.object.position.clone()); 389 | var vectorToPlane = hitPos.clone().sub(world.camera.position); 390 | var normal = vectorToPlane.negate().normalize() 391 | var plane = new THREE.Plane(normal, dist); 392 | return plane; 393 | } 394 | 395 | function loadConsoleScript(thenDo) { 396 | lively.lang.arr.mapAsyncSeries([ 397 | ["https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js", function() { return !!window.jQuery; }], 398 | ["https://lively-web.org/users/robertkrahn/just-the-core/html-console.js", function() { return !!document.querySelector("#log"); }] 399 | ], function(loadData, _, n) { 400 | var s = document.createElement("script"); 401 | s.src = loadData[0]; 402 | document.body.appendChild(s); 403 | lively.lang.fun.waitFor(3000, loadData[1], n) 404 | }, thenDo); 405 | } 406 | -------------------------------------------------------------------------------- /vendor/lively.vm.dev.js: -------------------------------------------------------------------------------- 1 | var isCommonJS = typeof module !== "undefined" && module.require; 2 | var Global = typeof window !== "undefined" ? window : global; 3 | var lang = typeof lively !== "undefined" ? lively.lang : isCommonJS ? module.require("lively.lang") : {}; 4 | var ast = typeof lively !== "undefined" && lively.ast ? lively.ast : (isCommonJS ? module.require("lively.ast") : (function() { throw new Error("Cannot find lively.lang") })()); 5 | var lv = Global.lively || {}; 6 | lv.ast = ast; 7 | lv.lang = lang; 8 | 9 | var env = { 10 | isCommonJS: isCommonJS, 11 | Global: Global, 12 | lively: lv 13 | } 14 | 15 | lang.obj.extend(isCommonJS ? module.exports : Global, env); 16 | ; 17 | /*global module,lively*/ 18 | 19 | var exports = typeof module !== "undefined" && module.require ? module.exports : (lively.vm = {}); 20 | var lang = typeof module !== "undefined" && module.require ? module.require("lively.lang") : lively.lang; 21 | var ast = typeof module !== "undefined" && module.require ? module.require("lively.ast") : lively.ast; 22 | 23 | var arr = lang.arr; 24 | 25 | lang.obj.extend(exports, { 26 | 27 | transformForVarRecord: function(code, varRecorder, varRecorderName, blacklist, defRangeRecorder) { 28 | // variable declaration and references in the the source code get 29 | // transformed so that they are bound to `varRecorderName` aren't local 30 | // state. THis makes it possible to capture eval results, e.g. for 31 | // inspection, watching and recording changes, workspace vars, and 32 | // incrementally evaluating var declarations and having values bound later. 33 | blacklist = blacklist || []; 34 | var undeclaredToTransform = lang.arr.withoutAll(Object.keys(varRecorder), blacklist), 35 | transformed = ast.transform.replaceTopLevelVarDeclAndUsageForCapturing( 36 | code, {name: varRecorderName, type: "Identifier"}, 37 | {ignoreUndeclaredExcept: undeclaredToTransform, 38 | exclude: blacklist, recordDefRanges: !!defRangeRecorder}); 39 | code = transformed.source; 40 | if (defRangeRecorder) lang.obj.extend(defRangeRecorder, transformed.defRanges); 41 | return code; 42 | }, 43 | 44 | transformSingleExpression: function(code) { 45 | // evaling certain expressions such as single functions or object 46 | // literals will fail or not work as intended. When the code being 47 | // evaluated consists just out of a single expression we will wrap it in 48 | // parens to allow for those cases 49 | try { 50 | var parsed = ast.fuzzyParse(code); 51 | if (parsed.body.length === 1 && 52 | (parsed.body[0].type === 'FunctionDeclaration' 53 | || parsed.body[0].type === 'BlockStatement')) { 54 | code = '(' + code.replace(/;\s*$/, '') + ')'; 55 | } 56 | } catch(e) { 57 | if (typeof lively && lively.Config && lively.Config.showImprovedJavaScriptEvalErrors) $world.logError(e) 58 | else console.error("Eval preprocess error: %s", e.stack || e); 59 | } 60 | return code; 61 | }, 62 | 63 | evalCodeTransform: function(code, options) { 64 | var vm = exports, 65 | recorder = options.topLevelVarRecorder, 66 | varRecorderName = options.varRecorderName || '__lvVarRecorder'; 67 | 68 | if (recorder) code = vm.transformForVarRecord( 69 | code, recorder, varRecorderName, 70 | options.dontTransform, options.topLevelDefRangeRecorder); 71 | code = vm.transformSingleExpression(code); 72 | 73 | if (options.sourceURL) code += "\n//# sourceURL=" + options.sourceURL.replace(/\s/g, "_"); 74 | 75 | return code; 76 | }, 77 | 78 | getGlobal: function() { 79 | return (function() { return this; })(); 80 | }, 81 | 82 | _eval: function(__lvEvalStatement, __lvVarRecorder/*needed as arg for capturing*/) { 83 | return eval(__lvEvalStatement); 84 | }, 85 | 86 | runEval: function (code, options, thenDo) { 87 | // The main function where all eval options are configured. 88 | // options can include { 89 | // varRecorderName: STRING, // default is '__lvVarRecorder' 90 | // topLevelVarRecorder: OBJECT, 91 | // context: OBJECT, 92 | // sourceURL: STRING 93 | // } 94 | if (typeof options === 'function' && arguments.length === 2) { 95 | thenDo = options; options = {}; 96 | } else if (!options) options = {}; 97 | 98 | var vm = exports, result, err, 99 | context = options.context || vm.getGlobal(), 100 | recorder = options.topLevelVarRecorder; 101 | 102 | try { 103 | code = vm.evalCodeTransform(code, options); 104 | typeof $morph !== "undefined" && $morph('log') && ($morph('log').textString = code); 105 | result = vm._eval.call(context, code, recorder); 106 | } catch (e) { err = e; } finally { thenDo(err, result); } 107 | }, 108 | 109 | syncEval: function(string, options) { 110 | // See #runEval for options. 111 | // Although the defaul eval is synchronous we assume that the general 112 | // evaluation might not return immediatelly. This makes is possible to 113 | // change the evaluation backend, e.g. to be a remotely attached runtime 114 | var result; 115 | exports.runEval(string, options, function(e, r) { result = e || r; }); 116 | return result; 117 | } 118 | 119 | }); 120 | 121 | //# sourceMappingURL=lively.vm.dev.js.map -------------------------------------------------------------------------------- /vendor/mousetrap.js: -------------------------------------------------------------------------------- 1 | /*global define:false */ 2 | /** 3 | * Copyright 2013 Craig Campbell 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * Mousetrap is a simple keyboard shortcut library for Javascript with 18 | * no external dependencies 19 | * 20 | * @version 1.4.6 21 | * @url craig.is/killing/mice 22 | */ 23 | (function(window, document, undefined) { 24 | 25 | /** 26 | * mapping of special keycodes to their corresponding keys 27 | * 28 | * everything in this dictionary cannot use keypress events 29 | * so it has to be here to map to the correct keycodes for 30 | * keyup/keydown events 31 | * 32 | * @type {Object} 33 | */ 34 | var _MAP = { 35 | 8: 'backspace', 36 | 9: 'tab', 37 | 13: 'enter', 38 | 16: 'shift', 39 | 17: 'ctrl', 40 | 18: 'alt', 41 | 20: 'capslock', 42 | 27: 'esc', 43 | 32: 'space', 44 | 33: 'pageup', 45 | 34: 'pagedown', 46 | 35: 'end', 47 | 36: 'home', 48 | 37: 'left', 49 | 38: 'up', 50 | 39: 'right', 51 | 40: 'down', 52 | 45: 'ins', 53 | 46: 'del', 54 | 91: 'meta', 55 | 93: 'meta', 56 | 224: 'meta' 57 | }, 58 | 59 | /** 60 | * mapping for special characters so they can support 61 | * 62 | * this dictionary is only used incase you want to bind a 63 | * keyup or keydown event to one of these keys 64 | * 65 | * @type {Object} 66 | */ 67 | _KEYCODE_MAP = { 68 | 106: '*', 69 | 107: '+', 70 | 109: '-', 71 | 110: '.', 72 | 111 : '/', 73 | 186: ';', 74 | 187: '=', 75 | 188: ',', 76 | 189: '-', 77 | 190: '.', 78 | 191: '/', 79 | 192: '`', 80 | 219: '[', 81 | 220: '\\', 82 | 221: ']', 83 | 222: '\'' 84 | }, 85 | 86 | /** 87 | * this is a mapping of keys that require shift on a US keypad 88 | * back to the non shift equivelents 89 | * 90 | * this is so you can use keyup events with these keys 91 | * 92 | * note that this will only work reliably on US keyboards 93 | * 94 | * @type {Object} 95 | */ 96 | _SHIFT_MAP = { 97 | '~': '`', 98 | '!': '1', 99 | '@': '2', 100 | '#': '3', 101 | '$': '4', 102 | '%': '5', 103 | '^': '6', 104 | '&': '7', 105 | '*': '8', 106 | '(': '9', 107 | ')': '0', 108 | '_': '-', 109 | '+': '=', 110 | ':': ';', 111 | '\"': '\'', 112 | '<': ',', 113 | '>': '.', 114 | '?': '/', 115 | '|': '\\' 116 | }, 117 | 118 | /** 119 | * this is a list of special strings you can use to map 120 | * to modifier keys when you specify your keyboard shortcuts 121 | * 122 | * @type {Object} 123 | */ 124 | _SPECIAL_ALIASES = { 125 | 'option': 'alt', 126 | 'command': 'meta', 127 | 'return': 'enter', 128 | 'escape': 'esc', 129 | 'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl' 130 | }, 131 | 132 | /** 133 | * variable to store the flipped version of _MAP from above 134 | * needed to check if we should use keypress or not when no action 135 | * is specified 136 | * 137 | * @type {Object|undefined} 138 | */ 139 | _REVERSE_MAP, 140 | 141 | /** 142 | * a list of all the callbacks setup via Mousetrap.bind() 143 | * 144 | * @type {Object} 145 | */ 146 | _callbacks = {}, 147 | 148 | /** 149 | * direct map of string combinations to callbacks used for trigger() 150 | * 151 | * @type {Object} 152 | */ 153 | _directMap = {}, 154 | 155 | /** 156 | * keeps track of what level each sequence is at since multiple 157 | * sequences can start out with the same sequence 158 | * 159 | * @type {Object} 160 | */ 161 | _sequenceLevels = {}, 162 | 163 | /** 164 | * variable to store the setTimeout call 165 | * 166 | * @type {null|number} 167 | */ 168 | _resetTimer, 169 | 170 | /** 171 | * temporary state where we will ignore the next keyup 172 | * 173 | * @type {boolean|string} 174 | */ 175 | _ignoreNextKeyup = false, 176 | 177 | /** 178 | * temporary state where we will ignore the next keypress 179 | * 180 | * @type {boolean} 181 | */ 182 | _ignoreNextKeypress = false, 183 | 184 | /** 185 | * are we currently inside of a sequence? 186 | * type of action ("keyup" or "keydown" or "keypress") or false 187 | * 188 | * @type {boolean|string} 189 | */ 190 | _nextExpectedAction = false; 191 | 192 | /** 193 | * loop through the f keys, f1 to f19 and add them to the map 194 | * programatically 195 | */ 196 | for (var i = 1; i < 20; ++i) { 197 | _MAP[111 + i] = 'f' + i; 198 | } 199 | 200 | /** 201 | * loop through to map numbers on the numeric keypad 202 | */ 203 | for (i = 0; i <= 9; ++i) { 204 | _MAP[i + 96] = i; 205 | } 206 | 207 | /** 208 | * cross browser add event method 209 | * 210 | * @param {Element|HTMLDocument} object 211 | * @param {string} type 212 | * @param {Function} callback 213 | * @returns void 214 | */ 215 | function _addEvent(object, type, callback) { 216 | if (object.addEventListener) { 217 | object.addEventListener(type, callback, false); 218 | return; 219 | } 220 | 221 | object.attachEvent('on' + type, callback); 222 | } 223 | 224 | /** 225 | * takes the event and returns the key character 226 | * 227 | * @param {Event} e 228 | * @return {string} 229 | */ 230 | function _characterFromEvent(e) { 231 | 232 | // for keypress events we should return the character as is 233 | if (e.type == 'keypress') { 234 | var character = String.fromCharCode(e.which); 235 | 236 | // if the shift key is not pressed then it is safe to assume 237 | // that we want the character to be lowercase. this means if 238 | // you accidentally have caps lock on then your key bindings 239 | // will continue to work 240 | // 241 | // the only side effect that might not be desired is if you 242 | // bind something like 'A' cause you want to trigger an 243 | // event when capital A is pressed caps lock will no longer 244 | // trigger the event. shift+a will though. 245 | if (!e.shiftKey) { 246 | character = character.toLowerCase(); 247 | } 248 | 249 | return character; 250 | } 251 | 252 | // for non keypress events the special maps are needed 253 | if (_MAP[e.which]) { 254 | return _MAP[e.which]; 255 | } 256 | 257 | if (_KEYCODE_MAP[e.which]) { 258 | return _KEYCODE_MAP[e.which]; 259 | } 260 | 261 | // if it is not in the special map 262 | 263 | // with keydown and keyup events the character seems to always 264 | // come in as an uppercase character whether you are pressing shift 265 | // or not. we should make sure it is always lowercase for comparisons 266 | return String.fromCharCode(e.which).toLowerCase(); 267 | } 268 | 269 | /** 270 | * checks if two arrays are equal 271 | * 272 | * @param {Array} modifiers1 273 | * @param {Array} modifiers2 274 | * @returns {boolean} 275 | */ 276 | function _modifiersMatch(modifiers1, modifiers2) { 277 | return modifiers1.sort().join(',') === modifiers2.sort().join(','); 278 | } 279 | 280 | /** 281 | * resets all sequence counters except for the ones passed in 282 | * 283 | * @param {Object} doNotReset 284 | * @returns void 285 | */ 286 | function _resetSequences(doNotReset) { 287 | doNotReset = doNotReset || {}; 288 | 289 | var activeSequences = false, 290 | key; 291 | 292 | for (key in _sequenceLevels) { 293 | if (doNotReset[key]) { 294 | activeSequences = true; 295 | continue; 296 | } 297 | _sequenceLevels[key] = 0; 298 | } 299 | 300 | if (!activeSequences) { 301 | _nextExpectedAction = false; 302 | } 303 | } 304 | 305 | /** 306 | * finds all callbacks that match based on the keycode, modifiers, 307 | * and action 308 | * 309 | * @param {string} character 310 | * @param {Array} modifiers 311 | * @param {Event|Object} e 312 | * @param {string=} sequenceName - name of the sequence we are looking for 313 | * @param {string=} combination 314 | * @param {number=} level 315 | * @returns {Array} 316 | */ 317 | function _getMatches(character, modifiers, e, sequenceName, combination, level) { 318 | var i, 319 | callback, 320 | matches = [], 321 | action = e.type; 322 | 323 | // if there are no events related to this keycode 324 | if (!_callbacks[character]) { 325 | return []; 326 | } 327 | 328 | // if a modifier key is coming up on its own we should allow it 329 | if (action == 'keyup' && _isModifier(character)) { 330 | modifiers = [character]; 331 | } 332 | 333 | // loop through all callbacks for the key that was pressed 334 | // and see if any of them match 335 | for (i = 0; i < _callbacks[character].length; ++i) { 336 | callback = _callbacks[character][i]; 337 | 338 | // if a sequence name is not specified, but this is a sequence at 339 | // the wrong level then move onto the next match 340 | if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) { 341 | continue; 342 | } 343 | 344 | // if the action we are looking for doesn't match the action we got 345 | // then we should keep going 346 | if (action != callback.action) { 347 | continue; 348 | } 349 | 350 | // if this is a keypress event and the meta key and control key 351 | // are not pressed that means that we need to only look at the 352 | // character, otherwise check the modifiers as well 353 | // 354 | // chrome will not fire a keypress if meta or control is down 355 | // safari will fire a keypress if meta or meta+shift is down 356 | // firefox will fire a keypress if meta or control is down 357 | if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) { 358 | 359 | // when you bind a combination or sequence a second time it 360 | // should overwrite the first one. if a sequenceName or 361 | // combination is specified in this call it does just that 362 | // 363 | // @todo make deleting its own method? 364 | var deleteCombo = !sequenceName && callback.combo == combination; 365 | var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level; 366 | if (deleteCombo || deleteSequence) { 367 | _callbacks[character].splice(i, 1); 368 | } 369 | 370 | matches.push(callback); 371 | } 372 | } 373 | 374 | return matches; 375 | } 376 | 377 | /** 378 | * takes a key event and figures out what the modifiers are 379 | * 380 | * @param {Event} e 381 | * @returns {Array} 382 | */ 383 | function _eventModifiers(e) { 384 | var modifiers = []; 385 | 386 | if (e.shiftKey) { 387 | modifiers.push('shift'); 388 | } 389 | 390 | if (e.altKey) { 391 | modifiers.push('alt'); 392 | } 393 | 394 | if (e.ctrlKey) { 395 | modifiers.push('ctrl'); 396 | } 397 | 398 | if (e.metaKey) { 399 | modifiers.push('meta'); 400 | } 401 | 402 | return modifiers; 403 | } 404 | 405 | /** 406 | * prevents default for this event 407 | * 408 | * @param {Event} e 409 | * @returns void 410 | */ 411 | function _preventDefault(e) { 412 | if (e.preventDefault) { 413 | e.preventDefault(); 414 | return; 415 | } 416 | 417 | e.returnValue = false; 418 | } 419 | 420 | /** 421 | * stops propogation for this event 422 | * 423 | * @param {Event} e 424 | * @returns void 425 | */ 426 | function _stopPropagation(e) { 427 | if (e.stopPropagation) { 428 | e.stopPropagation(); 429 | return; 430 | } 431 | 432 | e.cancelBubble = true; 433 | } 434 | 435 | /** 436 | * actually calls the callback function 437 | * 438 | * if your callback function returns false this will use the jquery 439 | * convention - prevent default and stop propogation on the event 440 | * 441 | * @param {Function} callback 442 | * @param {Event} e 443 | * @returns void 444 | */ 445 | function _fireCallback(callback, e, combo, sequence) { 446 | 447 | // if this event should not happen stop here 448 | if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) { 449 | return; 450 | } 451 | 452 | if (callback(e, combo) === false) { 453 | _preventDefault(e); 454 | _stopPropagation(e); 455 | } 456 | } 457 | 458 | /** 459 | * handles a character key event 460 | * 461 | * @param {string} character 462 | * @param {Array} modifiers 463 | * @param {Event} e 464 | * @returns void 465 | */ 466 | function _handleKey(character, modifiers, e) { 467 | var callbacks = _getMatches(character, modifiers, e), 468 | i, 469 | doNotReset = {}, 470 | maxLevel = 0, 471 | processedSequenceCallback = false; 472 | 473 | // Calculate the maxLevel for sequences so we can only execute the longest callback sequence 474 | for (i = 0; i < callbacks.length; ++i) { 475 | if (callbacks[i].seq) { 476 | maxLevel = Math.max(maxLevel, callbacks[i].level); 477 | } 478 | } 479 | 480 | // loop through matching callbacks for this key event 481 | for (i = 0; i < callbacks.length; ++i) { 482 | 483 | // fire for all sequence callbacks 484 | // this is because if for example you have multiple sequences 485 | // bound such as "g i" and "g t" they both need to fire the 486 | // callback for matching g cause otherwise you can only ever 487 | // match the first one 488 | if (callbacks[i].seq) { 489 | 490 | // only fire callbacks for the maxLevel to prevent 491 | // subsequences from also firing 492 | // 493 | // for example 'a option b' should not cause 'option b' to fire 494 | // even though 'option b' is part of the other sequence 495 | // 496 | // any sequences that do not match here will be discarded 497 | // below by the _resetSequences call 498 | if (callbacks[i].level != maxLevel) { 499 | continue; 500 | } 501 | 502 | processedSequenceCallback = true; 503 | 504 | // keep a list of which sequences were matches for later 505 | doNotReset[callbacks[i].seq] = 1; 506 | _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); 507 | continue; 508 | } 509 | 510 | // if there were no sequence matches but we are still here 511 | // that means this is a regular match so we should fire that 512 | if (!processedSequenceCallback) { 513 | _fireCallback(callbacks[i].callback, e, callbacks[i].combo); 514 | } 515 | } 516 | 517 | // if the key you pressed matches the type of sequence without 518 | // being a modifier (ie "keyup" or "keypress") then we should 519 | // reset all sequences that were not matched by this event 520 | // 521 | // this is so, for example, if you have the sequence "h a t" and you 522 | // type "h e a r t" it does not match. in this case the "e" will 523 | // cause the sequence to reset 524 | // 525 | // modifier keys are ignored because you can have a sequence 526 | // that contains modifiers such as "enter ctrl+space" and in most 527 | // cases the modifier key will be pressed before the next key 528 | // 529 | // also if you have a sequence such as "ctrl+b a" then pressing the 530 | // "b" key will trigger a "keypress" and a "keydown" 531 | // 532 | // the "keydown" is expected when there is a modifier, but the 533 | // "keypress" ends up matching the _nextExpectedAction since it occurs 534 | // after and that causes the sequence to reset 535 | // 536 | // we ignore keypresses in a sequence that directly follow a keydown 537 | // for the same character 538 | var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress; 539 | if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) { 540 | _resetSequences(doNotReset); 541 | } 542 | 543 | _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown'; 544 | } 545 | 546 | /** 547 | * handles a keydown event 548 | * 549 | * @param {Event} e 550 | * @returns void 551 | */ 552 | function _handleKeyEvent(e) { 553 | 554 | // normalize e.which for key events 555 | // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion 556 | if (typeof e.which !== 'number') { 557 | e.which = e.keyCode; 558 | } 559 | 560 | var character = _characterFromEvent(e); 561 | 562 | // no character found then stop 563 | if (!character) { 564 | return; 565 | } 566 | 567 | // need to use === for the character check because the character can be 0 568 | if (e.type == 'keyup' && _ignoreNextKeyup === character) { 569 | _ignoreNextKeyup = false; 570 | return; 571 | } 572 | 573 | Mousetrap.handleKey(character, _eventModifiers(e), e); 574 | } 575 | 576 | /** 577 | * determines if the keycode specified is a modifier key or not 578 | * 579 | * @param {string} key 580 | * @returns {boolean} 581 | */ 582 | function _isModifier(key) { 583 | return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta'; 584 | } 585 | 586 | /** 587 | * called to set a 1 second timeout on the specified sequence 588 | * 589 | * this is so after each key press in the sequence you have 1 second 590 | * to press the next key before you have to start over 591 | * 592 | * @returns void 593 | */ 594 | function _resetSequenceTimer() { 595 | clearTimeout(_resetTimer); 596 | _resetTimer = setTimeout(_resetSequences, 1000); 597 | } 598 | 599 | /** 600 | * reverses the map lookup so that we can look for specific keys 601 | * to see what can and can't use keypress 602 | * 603 | * @return {Object} 604 | */ 605 | function _getReverseMap() { 606 | if (!_REVERSE_MAP) { 607 | _REVERSE_MAP = {}; 608 | for (var key in _MAP) { 609 | 610 | // pull out the numeric keypad from here cause keypress should 611 | // be able to detect the keys from the character 612 | if (key > 95 && key < 112) { 613 | continue; 614 | } 615 | 616 | if (_MAP.hasOwnProperty(key)) { 617 | _REVERSE_MAP[_MAP[key]] = key; 618 | } 619 | } 620 | } 621 | return _REVERSE_MAP; 622 | } 623 | 624 | /** 625 | * picks the best action based on the key combination 626 | * 627 | * @param {string} key - character for key 628 | * @param {Array} modifiers 629 | * @param {string=} action passed in 630 | */ 631 | function _pickBestAction(key, modifiers, action) { 632 | 633 | // if no action was picked in we should try to pick the one 634 | // that we think would work best for this key 635 | if (!action) { 636 | action = _getReverseMap()[key] ? 'keydown' : 'keypress'; 637 | } 638 | 639 | // modifier keys don't work as expected with keypress, 640 | // switch to keydown 641 | if (action == 'keypress' && modifiers.length) { 642 | action = 'keydown'; 643 | } 644 | 645 | return action; 646 | } 647 | 648 | /** 649 | * binds a key sequence to an event 650 | * 651 | * @param {string} combo - combo specified in bind call 652 | * @param {Array} keys 653 | * @param {Function} callback 654 | * @param {string=} action 655 | * @returns void 656 | */ 657 | function _bindSequence(combo, keys, callback, action) { 658 | 659 | // start off by adding a sequence level record for this combination 660 | // and setting the level to 0 661 | _sequenceLevels[combo] = 0; 662 | 663 | /** 664 | * callback to increase the sequence level for this sequence and reset 665 | * all other sequences that were active 666 | * 667 | * @param {string} nextAction 668 | * @returns {Function} 669 | */ 670 | function _increaseSequence(nextAction) { 671 | return function() { 672 | _nextExpectedAction = nextAction; 673 | ++_sequenceLevels[combo]; 674 | _resetSequenceTimer(); 675 | }; 676 | } 677 | 678 | /** 679 | * wraps the specified callback inside of another function in order 680 | * to reset all sequence counters as soon as this sequence is done 681 | * 682 | * @param {Event} e 683 | * @returns void 684 | */ 685 | function _callbackAndReset(e) { 686 | _fireCallback(callback, e, combo); 687 | 688 | // we should ignore the next key up if the action is key down 689 | // or keypress. this is so if you finish a sequence and 690 | // release the key the final key will not trigger a keyup 691 | if (action !== 'keyup') { 692 | _ignoreNextKeyup = _characterFromEvent(e); 693 | } 694 | 695 | // weird race condition if a sequence ends with the key 696 | // another sequence begins with 697 | setTimeout(_resetSequences, 10); 698 | } 699 | 700 | // loop through keys one at a time and bind the appropriate callback 701 | // function. for any key leading up to the final one it should 702 | // increase the sequence. after the final, it should reset all sequences 703 | // 704 | // if an action is specified in the original bind call then that will 705 | // be used throughout. otherwise we will pass the action that the 706 | // next key in the sequence should match. this allows a sequence 707 | // to mix and match keypress and keydown events depending on which 708 | // ones are better suited to the key provided 709 | for (var i = 0; i < keys.length; ++i) { 710 | var isFinal = i + 1 === keys.length; 711 | var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); 712 | _bindSingle(keys[i], wrappedCallback, action, combo, i); 713 | } 714 | } 715 | 716 | /** 717 | * Converts from a string key combination to an array 718 | * 719 | * @param {string} combination like "command+shift+l" 720 | * @return {Array} 721 | */ 722 | function _keysFromString(combination) { 723 | if (combination === '+') { 724 | return ['+']; 725 | } 726 | 727 | return combination.split('+'); 728 | } 729 | 730 | /** 731 | * Gets info for a specific key combination 732 | * 733 | * @param {string} combination key combination ("command+s" or "a" or "*") 734 | * @param {string=} action 735 | * @returns {Object} 736 | */ 737 | function _getKeyInfo(combination, action) { 738 | var keys, 739 | key, 740 | i, 741 | modifiers = []; 742 | 743 | // take the keys from this pattern and figure out what the actual 744 | // pattern is all about 745 | keys = _keysFromString(combination); 746 | 747 | for (i = 0; i < keys.length; ++i) { 748 | key = keys[i]; 749 | 750 | // normalize key names 751 | if (_SPECIAL_ALIASES[key]) { 752 | key = _SPECIAL_ALIASES[key]; 753 | } 754 | 755 | // if this is not a keypress event then we should 756 | // be smart about using shift keys 757 | // this will only work for US keyboards however 758 | if (action && action != 'keypress' && _SHIFT_MAP[key]) { 759 | key = _SHIFT_MAP[key]; 760 | modifiers.push('shift'); 761 | } 762 | 763 | // if this key is a modifier then add it to the list of modifiers 764 | if (_isModifier(key)) { 765 | modifiers.push(key); 766 | } 767 | } 768 | 769 | // depending on what the key combination is 770 | // we will try to pick the best event for it 771 | action = _pickBestAction(key, modifiers, action); 772 | 773 | return { 774 | key: key, 775 | modifiers: modifiers, 776 | action: action 777 | }; 778 | } 779 | 780 | /** 781 | * binds a single keyboard combination 782 | * 783 | * @param {string} combination 784 | * @param {Function} callback 785 | * @param {string=} action 786 | * @param {string=} sequenceName - name of sequence if part of sequence 787 | * @param {number=} level - what part of the sequence the command is 788 | * @returns void 789 | */ 790 | function _bindSingle(combination, callback, action, sequenceName, level) { 791 | 792 | // store a direct mapped reference for use with Mousetrap.trigger 793 | _directMap[combination + ':' + action] = callback; 794 | 795 | // make sure multiple spaces in a row become a single space 796 | combination = combination.replace(/\s+/g, ' '); 797 | 798 | var sequence = combination.split(' '), 799 | info; 800 | 801 | // if this pattern is a sequence of keys then run through this method 802 | // to reprocess each pattern one key at a time 803 | if (sequence.length > 1) { 804 | _bindSequence(combination, sequence, callback, action); 805 | return; 806 | } 807 | 808 | info = _getKeyInfo(combination, action); 809 | 810 | // make sure to initialize array if this is the first time 811 | // a callback is added for this key 812 | _callbacks[info.key] = _callbacks[info.key] || []; 813 | 814 | // remove an existing match if there is one 815 | _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level); 816 | 817 | // add this call back to the array 818 | // if it is a sequence put it at the beginning 819 | // if not put it at the end 820 | // 821 | // this is important because the way these are processed expects 822 | // the sequence ones to come first 823 | _callbacks[info.key][sequenceName ? 'unshift' : 'push']({ 824 | callback: callback, 825 | modifiers: info.modifiers, 826 | action: info.action, 827 | seq: sequenceName, 828 | level: level, 829 | combo: combination 830 | }); 831 | } 832 | 833 | /** 834 | * binds multiple combinations to the same callback 835 | * 836 | * @param {Array} combinations 837 | * @param {Function} callback 838 | * @param {string|undefined} action 839 | * @returns void 840 | */ 841 | function _bindMultiple(combinations, callback, action) { 842 | for (var i = 0; i < combinations.length; ++i) { 843 | _bindSingle(combinations[i], callback, action); 844 | } 845 | } 846 | 847 | // start! 848 | _addEvent(document, 'keypress', _handleKeyEvent); 849 | _addEvent(document, 'keydown', _handleKeyEvent); 850 | _addEvent(document, 'keyup', _handleKeyEvent); 851 | 852 | var Mousetrap = { 853 | 854 | /** 855 | * binds an event to mousetrap 856 | * 857 | * can be a single key, a combination of keys separated with +, 858 | * an array of keys, or a sequence of keys separated by spaces 859 | * 860 | * be sure to list the modifier keys first to make sure that the 861 | * correct key ends up getting bound (the last key in the pattern) 862 | * 863 | * @param {string|Array} keys 864 | * @param {Function} callback 865 | * @param {string=} action - 'keypress', 'keydown', or 'keyup' 866 | * @returns void 867 | */ 868 | bind: function(keys, callback, action) { 869 | keys = keys instanceof Array ? keys : [keys]; 870 | _bindMultiple(keys, callback, action); 871 | return this; 872 | }, 873 | 874 | /** 875 | * unbinds an event to mousetrap 876 | * 877 | * the unbinding sets the callback function of the specified key combo 878 | * to an empty function and deletes the corresponding key in the 879 | * _directMap dict. 880 | * 881 | * TODO: actually remove this from the _callbacks dictionary instead 882 | * of binding an empty function 883 | * 884 | * the keycombo+action has to be exactly the same as 885 | * it was defined in the bind method 886 | * 887 | * @param {string|Array} keys 888 | * @param {string} action 889 | * @returns void 890 | */ 891 | unbind: function(keys, action) { 892 | return Mousetrap.bind(keys, function() {}, action); 893 | }, 894 | 895 | /** 896 | * triggers an event that has already been bound 897 | * 898 | * @param {string} keys 899 | * @param {string=} action 900 | * @returns void 901 | */ 902 | trigger: function(keys, action) { 903 | if (_directMap[keys + ':' + action]) { 904 | _directMap[keys + ':' + action]({}, keys); 905 | } 906 | return this; 907 | }, 908 | 909 | /** 910 | * resets the library back to its initial state. this is useful 911 | * if you want to clear out the current keyboard shortcuts and bind 912 | * new ones - for example if you switch to another page 913 | * 914 | * @returns void 915 | */ 916 | reset: function() { 917 | _callbacks = {}; 918 | _directMap = {}; 919 | return this; 920 | }, 921 | 922 | /** 923 | * should we stop this event before firing off callbacks 924 | * 925 | * @param {Event} e 926 | * @param {Element} element 927 | * @return {boolean} 928 | */ 929 | stopCallback: function(e, element) { 930 | 931 | // if the element has the class "mousetrap" then no need to stop 932 | if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { 933 | return false; 934 | } 935 | 936 | // stop for input, select, and textarea 937 | return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; 938 | }, 939 | 940 | /** 941 | * exposes _handleKey publicly so it can be overwritten by extensions 942 | */ 943 | handleKey: _handleKey 944 | }; 945 | 946 | // expose mousetrap to the global object 947 | window.Mousetrap = Mousetrap; 948 | 949 | // expose mousetrap as an AMD module 950 | if (typeof define === 'function' && define.amd) { 951 | define(Mousetrap); 952 | } 953 | }) (window, document); 954 | -------------------------------------------------------------------------------- /vendor/three-codeeditor/keybinding-emacs.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/occur",["require","exports","module","ace/lib/oop","ace/range","ace/search","ace/edit_session","ace/search_highlight","ace/lib/dom"], function(require, exports, module) { 2 | "use strict"; 3 | 4 | var oop = require("./lib/oop"); 5 | var Range = require("./range").Range; 6 | var Search = require("./search").Search; 7 | var EditSession = require("./edit_session").EditSession; 8 | var SearchHighlight = require("./search_highlight").SearchHighlight; 9 | function Occur() {} 10 | 11 | oop.inherits(Occur, Search); 12 | 13 | (function() { 14 | this.enter = function(editor, options) { 15 | if (!options.needle) return false; 16 | var pos = editor.getCursorPosition(); 17 | this.displayOccurContent(editor, options); 18 | var translatedPos = this.originalToOccurPosition(editor.session, pos); 19 | editor.moveCursorToPosition(translatedPos); 20 | return true; 21 | } 22 | this.exit = function(editor, options) { 23 | var pos = options.translatePosition && editor.getCursorPosition(); 24 | var translatedPos = pos && this.occurToOriginalPosition(editor.session, pos); 25 | this.displayOriginalContent(editor); 26 | if (translatedPos) 27 | editor.moveCursorToPosition(translatedPos); 28 | return true; 29 | } 30 | 31 | this.highlight = function(sess, regexp) { 32 | var hl = sess.$occurHighlight = sess.$occurHighlight || sess.addDynamicMarker( 33 | new SearchHighlight(null, "ace_occur-highlight", "text")); 34 | hl.setRegexp(regexp); 35 | sess._emit("changeBackMarker"); // force highlight layer redraw 36 | } 37 | 38 | this.displayOccurContent = function(editor, options) { 39 | this.$originalSession = editor.session; 40 | var found = this.matchingLines(editor.session, options); 41 | var lines = found.map(function(foundLine) { return foundLine.content; }); 42 | var occurSession = new EditSession(lines.join('\n')); 43 | occurSession.$occur = this; 44 | occurSession.$occurMatchingLines = found; 45 | editor.setSession(occurSession); 46 | this.$useEmacsStyleLineStart = this.$originalSession.$useEmacsStyleLineStart; 47 | occurSession.$useEmacsStyleLineStart = this.$useEmacsStyleLineStart; 48 | this.highlight(occurSession, options.re); 49 | occurSession._emit('changeBackMarker'); 50 | } 51 | 52 | this.displayOriginalContent = function(editor) { 53 | editor.setSession(this.$originalSession); 54 | this.$originalSession.$useEmacsStyleLineStart = this.$useEmacsStyleLineStart; 55 | } 56 | this.originalToOccurPosition = function(session, pos) { 57 | var lines = session.$occurMatchingLines; 58 | var nullPos = {row: 0, column: 0}; 59 | if (!lines) return nullPos; 60 | for (var i = 0; i < lines.length; i++) { 61 | if (lines[i].row === pos.row) 62 | return {row: i, column: pos.column}; 63 | } 64 | return nullPos; 65 | } 66 | this.occurToOriginalPosition = function(session, pos) { 67 | var lines = session.$occurMatchingLines; 68 | if (!lines || !lines[pos.row]) 69 | return pos; 70 | return {row: lines[pos.row].row, column: pos.column}; 71 | } 72 | 73 | this.matchingLines = function(session, options) { 74 | options = oop.mixin({}, options); 75 | if (!session || !options.needle) return []; 76 | var search = new Search(); 77 | search.set(options); 78 | return search.findAll(session).reduce(function(lines, range) { 79 | var row = range.start.row; 80 | var last = lines[lines.length-1]; 81 | return last && last.row === row ? 82 | lines : 83 | lines.concat({row: row, content: session.getLine(row)}); 84 | }, []); 85 | } 86 | 87 | }).call(Occur.prototype); 88 | 89 | var dom = require('./lib/dom'); 90 | dom.importCssString(".ace_occur-highlight {\n\ 91 | border-radius: 4px;\n\ 92 | background-color: rgba(87, 255, 8, 0.25);\n\ 93 | position: absolute;\n\ 94 | z-index: 4;\n\ 95 | -moz-box-sizing: border-box;\n\ 96 | -webkit-box-sizing: border-box;\n\ 97 | box-sizing: border-box;\n\ 98 | box-shadow: 0 0 4px rgb(91, 255, 50);\n\ 99 | }\n\ 100 | .ace_dark .ace_occur-highlight {\n\ 101 | background-color: rgb(80, 140, 85);\n\ 102 | box-shadow: 0 0 4px rgb(60, 120, 70);\n\ 103 | }\n", "incremental-occur-highlighting"); 104 | 105 | exports.Occur = Occur; 106 | 107 | }); 108 | 109 | ace.define("ace/commands/occur_commands",["require","exports","module","ace/config","ace/occur","ace/keyboard/hash_handler","ace/lib/oop"], function(require, exports, module) { 110 | 111 | var config = require("../config"), 112 | Occur = require("../occur").Occur; 113 | var occurStartCommand = { 114 | name: "occur", 115 | exec: function(editor, options) { 116 | var alreadyInOccur = !!editor.session.$occur; 117 | var occurSessionActive = new Occur().enter(editor, options); 118 | if (occurSessionActive && !alreadyInOccur) 119 | OccurKeyboardHandler.installIn(editor); 120 | }, 121 | readOnly: true 122 | }; 123 | 124 | var occurCommands = [{ 125 | name: "occurexit", 126 | bindKey: 'esc|Ctrl-G', 127 | exec: function(editor) { 128 | var occur = editor.session.$occur; 129 | if (!occur) return; 130 | occur.exit(editor, {}); 131 | if (!editor.session.$occur) OccurKeyboardHandler.uninstallFrom(editor); 132 | }, 133 | readOnly: true 134 | }, { 135 | name: "occuraccept", 136 | bindKey: 'enter', 137 | exec: function(editor) { 138 | var occur = editor.session.$occur; 139 | if (!occur) return; 140 | occur.exit(editor, {translatePosition: true}); 141 | if (!editor.session.$occur) OccurKeyboardHandler.uninstallFrom(editor); 142 | }, 143 | readOnly: true 144 | }]; 145 | 146 | var HashHandler = require("../keyboard/hash_handler").HashHandler; 147 | var oop = require("../lib/oop"); 148 | 149 | 150 | function OccurKeyboardHandler() {} 151 | 152 | oop.inherits(OccurKeyboardHandler, HashHandler); 153 | 154 | ;(function() { 155 | 156 | this.isOccurHandler = true; 157 | 158 | this.attach = function(editor) { 159 | HashHandler.call(this, occurCommands, editor.commands.platform); 160 | this.$editor = editor; 161 | } 162 | 163 | var handleKeyboard$super = this.handleKeyboard; 164 | this.handleKeyboard = function(data, hashId, key, keyCode) { 165 | var cmd = handleKeyboard$super.call(this, data, hashId, key, keyCode); 166 | return (cmd && cmd.command) ? cmd : undefined; 167 | } 168 | 169 | }).call(OccurKeyboardHandler.prototype); 170 | 171 | OccurKeyboardHandler.installIn = function(editor) { 172 | var handler = new this(); 173 | editor.keyBinding.addKeyboardHandler(handler); 174 | editor.commands.addCommands(occurCommands); 175 | } 176 | 177 | OccurKeyboardHandler.uninstallFrom = function(editor) { 178 | editor.commands.removeCommands(occurCommands); 179 | var handler = editor.getKeyboardHandler(); 180 | if (handler.isOccurHandler) 181 | editor.keyBinding.removeKeyboardHandler(handler); 182 | } 183 | 184 | exports.occurStartCommand = occurStartCommand; 185 | 186 | }); 187 | 188 | ace.define("ace/commands/incremental_search_commands",["require","exports","module","ace/config","ace/lib/oop","ace/keyboard/hash_handler","ace/commands/occur_commands"], function(require, exports, module) { 189 | 190 | var config = require("../config"); 191 | var oop = require("../lib/oop"); 192 | var HashHandler = require("../keyboard/hash_handler").HashHandler; 193 | var occurStartCommand = require("./occur_commands").occurStartCommand; 194 | exports.iSearchStartCommands = [{ 195 | name: "iSearch", 196 | bindKey: {win: "Ctrl-F", mac: "Command-F"}, 197 | exec: function(editor, options) { 198 | config.loadModule(["core", "ace/incremental_search"], function(e) { 199 | var iSearch = e.iSearch = e.iSearch || new e.IncrementalSearch(); 200 | iSearch.activate(editor, options.backwards); 201 | if (options.jumpToFirstMatch) iSearch.next(options); 202 | }); 203 | }, 204 | readOnly: true 205 | }, { 206 | name: "iSearchBackwards", 207 | exec: function(editor, jumpToNext) { editor.execCommand('iSearch', {backwards: true}); }, 208 | readOnly: true 209 | }, { 210 | name: "iSearchAndGo", 211 | bindKey: {win: "Ctrl-K", mac: "Command-G"}, 212 | exec: function(editor, jumpToNext) { editor.execCommand('iSearch', {jumpToFirstMatch: true, useCurrentOrPrevSearch: true}); }, 213 | readOnly: true 214 | }, { 215 | name: "iSearchBackwardsAndGo", 216 | bindKey: {win: "Ctrl-Shift-K", mac: "Command-Shift-G"}, 217 | exec: function(editor) { editor.execCommand('iSearch', {jumpToFirstMatch: true, backwards: true, useCurrentOrPrevSearch: true}); }, 218 | readOnly: true 219 | }]; 220 | exports.iSearchCommands = [{ 221 | name: "restartSearch", 222 | bindKey: {win: "Ctrl-F", mac: "Command-F"}, 223 | exec: function(iSearch) { 224 | iSearch.cancelSearch(true); 225 | }, 226 | readOnly: true, 227 | isIncrementalSearchCommand: true 228 | }, { 229 | name: "searchForward", 230 | bindKey: {win: "Ctrl-S|Ctrl-K", mac: "Ctrl-S|Command-G"}, 231 | exec: function(iSearch, options) { 232 | options.useCurrentOrPrevSearch = true; 233 | iSearch.next(options); 234 | }, 235 | readOnly: true, 236 | isIncrementalSearchCommand: true 237 | }, { 238 | name: "searchBackward", 239 | bindKey: {win: "Ctrl-R|Ctrl-Shift-K", mac: "Ctrl-R|Command-Shift-G"}, 240 | exec: function(iSearch, options) { 241 | options.useCurrentOrPrevSearch = true; 242 | options.backwards = true; 243 | iSearch.next(options); 244 | }, 245 | readOnly: true, 246 | isIncrementalSearchCommand: true 247 | }, { 248 | name: "extendSearchTerm", 249 | exec: function(iSearch, string) { 250 | iSearch.addString(string); 251 | }, 252 | readOnly: true, 253 | isIncrementalSearchCommand: true 254 | }, { 255 | name: "extendSearchTermSpace", 256 | bindKey: "space", 257 | exec: function(iSearch) { iSearch.addString(' '); }, 258 | readOnly: true, 259 | isIncrementalSearchCommand: true 260 | }, { 261 | name: "shrinkSearchTerm", 262 | bindKey: "backspace", 263 | exec: function(iSearch) { 264 | iSearch.removeChar(); 265 | }, 266 | readOnly: true, 267 | isIncrementalSearchCommand: true 268 | }, { 269 | name: 'confirmSearch', 270 | bindKey: 'return', 271 | exec: function(iSearch) { iSearch.deactivate(); }, 272 | readOnly: true, 273 | isIncrementalSearchCommand: true 274 | }, { 275 | name: 'cancelSearch', 276 | bindKey: 'esc|Ctrl-G', 277 | exec: function(iSearch) { iSearch.deactivate(true); }, 278 | readOnly: true, 279 | isIncrementalSearchCommand: true 280 | }, { 281 | name: 'occurisearch', 282 | bindKey: 'Ctrl-O', 283 | exec: function(iSearch) { 284 | var options = oop.mixin({}, iSearch.$options); 285 | iSearch.deactivate(); 286 | occurStartCommand.exec(iSearch.$editor, options); 287 | }, 288 | readOnly: true, 289 | isIncrementalSearchCommand: true 290 | }, { 291 | name: "yankNextWord", 292 | bindKey: "Ctrl-w", 293 | exec: function(iSearch) { 294 | var ed = iSearch.$editor, 295 | range = ed.selection.getRangeOfMovements(function(sel) { sel.moveCursorWordRight(); }), 296 | string = ed.session.getTextRange(range); 297 | iSearch.addString(string); 298 | }, 299 | readOnly: true, 300 | isIncrementalSearchCommand: true 301 | }, { 302 | name: "yankNextChar", 303 | bindKey: "Ctrl-Alt-y", 304 | exec: function(iSearch) { 305 | var ed = iSearch.$editor, 306 | range = ed.selection.getRangeOfMovements(function(sel) { sel.moveCursorRight(); }), 307 | string = ed.session.getTextRange(range); 308 | iSearch.addString(string); 309 | }, 310 | readOnly: true, 311 | isIncrementalSearchCommand: true 312 | }, { 313 | name: 'recenterTopBottom', 314 | bindKey: 'Ctrl-l', 315 | exec: function(iSearch) { iSearch.$editor.execCommand('recenterTopBottom'); }, 316 | readOnly: true, 317 | isIncrementalSearchCommand: true 318 | }, { 319 | name: 'selectAllMatches', 320 | bindKey: 'Ctrl-space', 321 | exec: function(iSearch) { 322 | var ed = iSearch.$editor, 323 | hl = ed.session.$isearchHighlight, 324 | ranges = hl && hl.cache ? hl.cache 325 | .reduce(function(ranges, ea) { 326 | return ranges.concat(ea ? ea : []); }, []) : []; 327 | iSearch.deactivate(false); 328 | ranges.forEach(ed.selection.addRange.bind(ed.selection)); 329 | }, 330 | readOnly: true, 331 | isIncrementalSearchCommand: true 332 | }, { 333 | name: 'searchAsRegExp', 334 | bindKey: 'Alt-r', 335 | exec: function(iSearch) { 336 | iSearch.convertNeedleToRegExp(); 337 | }, 338 | readOnly: true, 339 | isIncrementalSearchCommand: true 340 | }]; 341 | 342 | function IncrementalSearchKeyboardHandler(iSearch) { 343 | this.$iSearch = iSearch; 344 | } 345 | 346 | oop.inherits(IncrementalSearchKeyboardHandler, HashHandler); 347 | 348 | ;(function() { 349 | 350 | this.attach = function(editor) { 351 | var iSearch = this.$iSearch; 352 | HashHandler.call(this, exports.iSearchCommands, editor.commands.platform); 353 | this.$commandExecHandler = editor.commands.addEventListener('exec', function(e) { 354 | if (!e.command.isIncrementalSearchCommand) return undefined; 355 | e.stopPropagation(); 356 | e.preventDefault(); 357 | return e.command.exec(iSearch, e.args || {}); 358 | }); 359 | } 360 | 361 | this.detach = function(editor) { 362 | if (!this.$commandExecHandler) return; 363 | editor.commands.removeEventListener('exec', this.$commandExecHandler); 364 | delete this.$commandExecHandler; 365 | } 366 | 367 | var handleKeyboard$super = this.handleKeyboard; 368 | this.handleKeyboard = function(data, hashId, key, keyCode) { 369 | if (((hashId === 1/*ctrl*/ || hashId === 8/*command*/) && key === 'v') 370 | || (hashId === 1/*ctrl*/ && key === 'y')) return null; 371 | var cmd = handleKeyboard$super.call(this, data, hashId, key, keyCode); 372 | if (cmd.command) { return cmd; } 373 | if (hashId == -1) { 374 | var extendCmd = this.commands.extendSearchTerm; 375 | if (extendCmd) { return {command: extendCmd, args: key}; } 376 | } 377 | return {command: "null", passEvent: hashId == 0 || hashId == 4}; 378 | } 379 | 380 | }).call(IncrementalSearchKeyboardHandler.prototype); 381 | 382 | 383 | exports.IncrementalSearchKeyboardHandler = IncrementalSearchKeyboardHandler; 384 | 385 | }); 386 | 387 | ace.define("ace/incremental_search",["require","exports","module","ace/lib/oop","ace/range","ace/search","ace/search_highlight","ace/commands/incremental_search_commands","ace/lib/dom","ace/commands/command_manager","ace/editor","ace/config"], function(require, exports, module) { 388 | "use strict"; 389 | 390 | var oop = require("./lib/oop"); 391 | var Range = require("./range").Range; 392 | var Search = require("./search").Search; 393 | var SearchHighlight = require("./search_highlight").SearchHighlight; 394 | var iSearchCommandModule = require("./commands/incremental_search_commands"); 395 | var ISearchKbd = iSearchCommandModule.IncrementalSearchKeyboardHandler; 396 | function IncrementalSearch() { 397 | this.$options = {wrap: false, skipCurrent: false}; 398 | this.$keyboardHandler = new ISearchKbd(this); 399 | } 400 | 401 | oop.inherits(IncrementalSearch, Search); 402 | 403 | function isRegExp(obj) { 404 | return obj instanceof RegExp; 405 | } 406 | 407 | function regExpToObject(re) { 408 | var string = String(re), 409 | start = string.indexOf('/'), 410 | flagStart = string.lastIndexOf('/'); 411 | return { 412 | expression: string.slice(start+1, flagStart), 413 | flags: string.slice(flagStart+1) 414 | } 415 | } 416 | 417 | function stringToRegExp(string, flags) { 418 | try { 419 | return new RegExp(string, flags); 420 | } catch (e) { return string; } 421 | } 422 | 423 | function objectToRegExp(obj) { 424 | return stringToRegExp(obj.expression, obj.flags); 425 | } 426 | 427 | ;(function() { 428 | 429 | this.activate = function(ed, backwards) { 430 | this.$editor = ed; 431 | this.$startPos = this.$currentPos = ed.getCursorPosition(); 432 | this.$options.needle = ''; 433 | this.$options.backwards = backwards; 434 | ed.keyBinding.addKeyboardHandler(this.$keyboardHandler); 435 | this.$originalEditorOnPaste = ed.onPaste; ed.onPaste = this.onPaste.bind(this); 436 | this.$mousedownHandler = ed.addEventListener('mousedown', this.onMouseDown.bind(this)); 437 | this.selectionFix(ed); 438 | this.statusMessage(true); 439 | } 440 | 441 | this.deactivate = function(reset) { 442 | this.cancelSearch(reset); 443 | var ed = this.$editor; 444 | ed.keyBinding.removeKeyboardHandler(this.$keyboardHandler); 445 | if (this.$mousedownHandler) { 446 | ed.removeEventListener('mousedown', this.$mousedownHandler); 447 | delete this.$mousedownHandler; 448 | } 449 | ed.onPaste = this.$originalEditorOnPaste; 450 | this.message(''); 451 | } 452 | 453 | this.selectionFix = function(editor) { 454 | if (editor.selection.isEmpty() && !editor.session.$emacsMark) { 455 | editor.clearSelection(); 456 | } 457 | } 458 | 459 | this.highlight = function(regexp) { 460 | var sess = this.$editor.session, 461 | hl = sess.$isearchHighlight = sess.$isearchHighlight || sess.addDynamicMarker( 462 | new SearchHighlight(null, "ace_isearch-result", "text")); 463 | hl.setRegexp(regexp); 464 | sess._emit("changeBackMarker"); // force highlight layer redraw 465 | } 466 | 467 | this.cancelSearch = function(reset) { 468 | var e = this.$editor; 469 | this.$prevNeedle = this.$options.needle; 470 | this.$options.needle = ''; 471 | if (reset) { 472 | e.moveCursorToPosition(this.$startPos); 473 | this.$currentPos = this.$startPos; 474 | } else { 475 | e.pushEmacsMark && e.pushEmacsMark(this.$startPos, false); 476 | } 477 | this.highlight(null); 478 | return Range.fromPoints(this.$currentPos, this.$currentPos); 479 | } 480 | 481 | this.highlightAndFindWithNeedle = function(moveToNext, needleUpdateFunc) { 482 | if (!this.$editor) return null; 483 | var options = this.$options; 484 | if (needleUpdateFunc) { 485 | options.needle = needleUpdateFunc.call(this, options.needle || '') || ''; 486 | } 487 | if (options.needle.length === 0) { 488 | this.statusMessage(true); 489 | return this.cancelSearch(true); 490 | }; 491 | options.start = this.$currentPos; 492 | var session = this.$editor.session, 493 | found = this.find(session), 494 | shouldSelect = this.$editor.emacsMark ? 495 | !!this.$editor.emacsMark() : !this.$editor.selection.isEmpty(); 496 | if (found) { 497 | if (options.backwards) found = Range.fromPoints(found.end, found.start); 498 | this.$editor.selection.setRange(Range.fromPoints(shouldSelect ? this.$startPos : found.end, found.end)); 499 | if (moveToNext) this.$currentPos = found.end; 500 | this.highlight(options.re) 501 | } 502 | 503 | this.statusMessage(found); 504 | 505 | return found; 506 | } 507 | 508 | this.addString = function(s) { 509 | return this.highlightAndFindWithNeedle(false, function(needle) { 510 | if (!isRegExp(needle)) 511 | return needle + s; 512 | var reObj = regExpToObject(needle); 513 | reObj.expression += s; 514 | return objectToRegExp(reObj); 515 | }); 516 | } 517 | 518 | this.removeChar = function(c) { 519 | return this.highlightAndFindWithNeedle(false, function(needle) { 520 | if (!isRegExp(needle)) 521 | return needle.substring(0, needle.length-1); 522 | var reObj = regExpToObject(needle); 523 | reObj.expression = reObj.expression.substring(0, reObj.expression.length-1); 524 | return objectToRegExp(reObj); 525 | }); 526 | } 527 | 528 | this.next = function(options) { 529 | options = options || {}; 530 | this.$options.backwards = !!options.backwards; 531 | this.$currentPos = this.$editor.getCursorPosition(); 532 | return this.highlightAndFindWithNeedle(true, function(needle) { 533 | return options.useCurrentOrPrevSearch && needle.length === 0 ? 534 | this.$prevNeedle || '' : needle; 535 | }); 536 | } 537 | 538 | this.onMouseDown = function(evt) { 539 | this.deactivate(); 540 | return true; 541 | } 542 | 543 | this.onPaste = function(text) { 544 | this.addString(text); 545 | } 546 | 547 | this.convertNeedleToRegExp = function() { 548 | return this.highlightAndFindWithNeedle(false, function(needle) { 549 | return isRegExp(needle) ? needle : stringToRegExp(needle, 'ig'); 550 | }); 551 | } 552 | 553 | this.convertNeedleToString = function() { 554 | return this.highlightAndFindWithNeedle(false, function(needle) { 555 | return isRegExp(needle) ? regExpToObject(needle).expression : needle; 556 | }); 557 | } 558 | 559 | this.statusMessage = function(found) { 560 | var options = this.$options, msg = ''; 561 | msg += options.backwards ? 'reverse-' : ''; 562 | msg += 'isearch: ' + options.needle; 563 | msg += found ? '' : ' (not found)'; 564 | this.message(msg); 565 | } 566 | 567 | this.message = function(msg) { 568 | if (this.$editor.showCommandLine) { 569 | this.$editor.showCommandLine(msg); 570 | this.$editor.focus(); 571 | } else { 572 | console.log(msg); 573 | } 574 | } 575 | 576 | }).call(IncrementalSearch.prototype); 577 | 578 | 579 | exports.IncrementalSearch = IncrementalSearch; 580 | 581 | var dom = require('./lib/dom'); 582 | dom.importCssString && dom.importCssString("\ 583 | .ace_marker-layer .ace_isearch-result {\ 584 | position: absolute;\ 585 | z-index: 6;\ 586 | -moz-box-sizing: border-box;\ 587 | -webkit-box-sizing: border-box;\ 588 | box-sizing: border-box;\ 589 | }\ 590 | div.ace_isearch-result {\ 591 | border-radius: 4px;\ 592 | background-color: rgba(255, 200, 0, 0.5);\ 593 | box-shadow: 0 0 4px rgb(255, 200, 0);\ 594 | }\ 595 | .ace_dark div.ace_isearch-result {\ 596 | background-color: rgb(100, 110, 160);\ 597 | box-shadow: 0 0 4px rgb(80, 90, 140);\ 598 | }", "incremental-search-highlighting"); 599 | var commands = require("./commands/command_manager"); 600 | (function() { 601 | this.setupIncrementalSearch = function(editor, val) { 602 | if (this.usesIncrementalSearch == val) return; 603 | this.usesIncrementalSearch = val; 604 | var iSearchCommands = iSearchCommandModule.iSearchStartCommands; 605 | var method = val ? 'addCommands' : 'removeCommands'; 606 | this[method](iSearchCommands); 607 | }; 608 | }).call(commands.CommandManager.prototype); 609 | var Editor = require("./editor").Editor; 610 | require("./config").defineOptions(Editor.prototype, "editor", { 611 | useIncrementalSearch: { 612 | set: function(val) { 613 | this.keyBinding.$handlers.forEach(function(handler) { 614 | if (handler.setupIncrementalSearch) { 615 | handler.setupIncrementalSearch(this, val); 616 | } 617 | }); 618 | this._emit('incrementalSearchSettingChanged', {isEnabled: val}); 619 | } 620 | } 621 | }); 622 | 623 | }); 624 | 625 | ace.define("ace/keyboard/emacs",["require","exports","module","ace/lib/dom","ace/incremental_search","ace/commands/incremental_search_commands","ace/keyboard/hash_handler","ace/lib/keys"], function(require, exports, module) { 626 | "use strict"; 627 | 628 | var dom = require("../lib/dom"); 629 | require("../incremental_search"); 630 | var iSearchCommandModule = require("../commands/incremental_search_commands"); 631 | 632 | 633 | var screenToTextBlockCoordinates = function(x, y) { 634 | var canvasPos = this.scroller.getBoundingClientRect(); 635 | 636 | var col = Math.floor( 637 | (x + this.scrollLeft - canvasPos.left - this.$padding) / this.characterWidth 638 | ); 639 | var row = Math.floor( 640 | (y + this.scrollTop - canvasPos.top) / this.lineHeight 641 | ); 642 | 643 | return this.session.screenToDocumentPosition(row, col); 644 | }; 645 | 646 | var HashHandler = require("./hash_handler").HashHandler; 647 | exports.handler = new HashHandler(); 648 | 649 | exports.handler.isEmacs = true; 650 | exports.handler.$id = "ace/keyboard/emacs"; 651 | 652 | var initialized = false; 653 | var $formerLongWords; 654 | var $formerLineStart; 655 | 656 | exports.handler.attach = function(editor) { 657 | if (!initialized) { 658 | initialized = true; 659 | dom.importCssString('\ 660 | .emacs-mode .ace_cursor{\ 661 | border: 1px rgba(50,250,50,0.8) solid!important;\ 662 | -moz-box-sizing: border-box!important;\ 663 | -webkit-box-sizing: border-box!important;\ 664 | box-sizing: border-box!important;\ 665 | background-color: rgba(0,250,0,0.9);\ 666 | opacity: 0.5;\ 667 | }\ 668 | .emacs-mode .ace_hidden-cursors .ace_cursor{\ 669 | opacity: 1;\ 670 | background-color: transparent;\ 671 | }\ 672 | .emacs-mode .ace_overwrite-cursors .ace_cursor {\ 673 | opacity: 1;\ 674 | background-color: transparent;\ 675 | border-width: 0 0 2px 2px !important;\ 676 | }\ 677 | .emacs-mode .ace_text-layer {\ 678 | z-index: 4\ 679 | }\ 680 | .emacs-mode .ace_cursor-layer {\ 681 | z-index: 2\ 682 | }', 'emacsMode' 683 | ); 684 | } 685 | $formerLongWords = editor.session.$selectLongWords; 686 | editor.session.$selectLongWords = true; 687 | $formerLineStart = editor.session.$useEmacsStyleLineStart; 688 | editor.session.$useEmacsStyleLineStart = true; 689 | 690 | editor.session.$emacsMark = null; // the active mark 691 | editor.session.$emacsMarkRing = editor.session.$emacsMarkRing || []; 692 | 693 | editor.emacsMark = function() { 694 | return this.session.$emacsMark; 695 | }; 696 | 697 | editor.setEmacsMark = function(p) { 698 | this.session.$emacsMark = p; 699 | }; 700 | 701 | editor.pushEmacsMark = function(p, activate) { 702 | var prevMark = this.session.$emacsMark; 703 | if (prevMark) 704 | this.session.$emacsMarkRing.push(prevMark); 705 | if (!p || activate) this.setEmacsMark(p); 706 | else this.session.$emacsMarkRing.push(p); 707 | }; 708 | 709 | editor.popEmacsMark = function() { 710 | var mark = this.emacsMark(); 711 | if (mark) { this.setEmacsMark(null); return mark; } 712 | return this.session.$emacsMarkRing.pop(); 713 | }; 714 | 715 | editor.getLastEmacsMark = function(p) { 716 | return this.session.$emacsMark || this.session.$emacsMarkRing.slice(-1)[0]; 717 | }; 718 | 719 | editor.emacsMarkForSelection = function(replacement) { 720 | var sel = this.selection, 721 | multiRangeLength = this.multiSelect ? 722 | this.multiSelect.getAllRanges().length : 1, 723 | selIndex = sel.index || 0, 724 | markRing = this.session.$emacsMarkRing, 725 | markIndex = markRing.length - (multiRangeLength - selIndex), 726 | lastMark = markRing[markIndex] || sel.anchor; 727 | if (replacement) { 728 | markRing.splice(markIndex, 1, 729 | "row" in replacement && "column" in replacement ? 730 | replacement : undefined); 731 | } 732 | return lastMark; 733 | } 734 | 735 | editor.on("click", $resetMarkMode); 736 | editor.on("changeSession", $kbSessionChange); 737 | editor.renderer.screenToTextCoordinates = screenToTextBlockCoordinates; 738 | editor.setStyle("emacs-mode"); 739 | editor.commands.addCommands(commands); 740 | exports.handler.platform = editor.commands.platform; 741 | editor.$emacsModeHandler = this; 742 | editor.addEventListener('copy', this.onCopy); 743 | editor.addEventListener('paste', this.onPaste); 744 | }; 745 | 746 | exports.handler.detach = function(editor) { 747 | delete editor.renderer.screenToTextCoordinates; 748 | editor.session.$selectLongWords = $formerLongWords; 749 | editor.session.$useEmacsStyleLineStart = $formerLineStart; 750 | editor.removeEventListener("click", $resetMarkMode); 751 | editor.removeEventListener("changeSession", $kbSessionChange); 752 | editor.unsetStyle("emacs-mode"); 753 | editor.commands.removeCommands(commands); 754 | editor.removeEventListener('copy', this.onCopy); 755 | editor.removeEventListener('paste', this.onPaste); 756 | editor.$emacsModeHandler = null; 757 | }; 758 | 759 | var $kbSessionChange = function(e) { 760 | if (e.oldSession) { 761 | e.oldSession.$selectLongWords = $formerLongWords; 762 | e.oldSession.$useEmacsStyleLineStart = $formerLineStart; 763 | } 764 | 765 | $formerLongWords = e.session.$selectLongWords; 766 | e.session.$selectLongWords = true; 767 | $formerLineStart = e.session.$useEmacsStyleLineStart; 768 | e.session.$useEmacsStyleLineStart = true; 769 | 770 | if (!e.session.hasOwnProperty('$emacsMark')) 771 | e.session.$emacsMark = null; 772 | if (!e.session.hasOwnProperty('$emacsMarkRing')) 773 | e.session.$emacsMarkRing = []; 774 | }; 775 | 776 | var $resetMarkMode = function(e) { 777 | e.editor.session.$emacsMark = null; 778 | }; 779 | 780 | var keys = require("../lib/keys").KEY_MODS; 781 | var eMods = {C: "ctrl", S: "shift", M: "alt", CMD: "command"}; 782 | var combinations = ["C-S-M-CMD", 783 | "S-M-CMD", "C-M-CMD", "C-S-CMD", "C-S-M", 784 | "M-CMD", "S-CMD", "S-M", "C-CMD", "C-M", "C-S", 785 | "CMD", "M", "S", "C"]; 786 | combinations.forEach(function(c) { 787 | var hashId = 0; 788 | c.split("-").forEach(function(c) { 789 | hashId = hashId | keys[eMods[c]]; 790 | }); 791 | eMods[hashId] = c.toLowerCase() + "-"; 792 | }); 793 | 794 | exports.handler.onCopy = function(e, editor) { 795 | if (editor.$handlesEmacsOnCopy) return; 796 | editor.$handlesEmacsOnCopy = true; 797 | exports.handler.commands.killRingSave.exec(editor); 798 | editor.$handlesEmacsOnCopy = false; 799 | }; 800 | 801 | exports.handler.onPaste = function(e, editor) { 802 | editor.pushEmacsMark(editor.getCursorPosition()); 803 | }; 804 | 805 | exports.handler.bindKey = function(key, command) { 806 | if (typeof key == "object") 807 | key = key[this.platform]; 808 | if (!key) 809 | return; 810 | 811 | var ckb = this.commandKeyBinding; 812 | key.split("|").forEach(function(keyPart) { 813 | keyPart = keyPart.toLowerCase(); 814 | ckb[keyPart] = command; 815 | var keyParts = keyPart.split(" ").slice(0,-1); 816 | keyParts.reduce(function(keyMapKeys, keyPart, i) { 817 | var prefix = keyMapKeys[i-1] ? keyMapKeys[i-1] + ' ' : ''; 818 | return keyMapKeys.concat([prefix + keyPart]); 819 | }, []).forEach(function(keyPart) { 820 | if (!ckb[keyPart]) ckb[keyPart] = "null"; 821 | }); 822 | }, this); 823 | }; 824 | 825 | exports.handler.getStatusText = function(editor, data) { 826 | var str = ""; 827 | if (data.count) 828 | str += data.count; 829 | if (data.keyChain) 830 | str += " " + data.keyChain 831 | return str; 832 | }; 833 | 834 | exports.handler.handleKeyboard = function(data, hashId, key, keyCode) { 835 | if (keyCode === -1) return undefined; 836 | 837 | var editor = data.editor; 838 | editor._signal("changeStatus"); 839 | if (hashId == -1) { 840 | editor.pushEmacsMark(); 841 | if (data.count) { 842 | var str = new Array(data.count + 1).join(key); 843 | data.count = null; 844 | return {command: "insertstring", args: str}; 845 | } 846 | } 847 | 848 | var modifier = eMods[hashId]; 849 | if (modifier == "c-" || data.count) { 850 | var count = parseInt(key[key.length - 1]); 851 | if (typeof count === 'number' && !isNaN(count)) { 852 | data.count = Math.max(data.count, 0) || 0; 853 | data.count = 10 * data.count + count; 854 | return {command: "null"}; 855 | } 856 | } 857 | if (modifier) key = modifier + key; 858 | if (data.keyChain) key = data.keyChain += " " + key; 859 | var command = this.commandKeyBinding[key]; 860 | data.keyChain = command == "null" ? key : ""; 861 | if (!command) return undefined; 862 | if (command === "null") return {command: "null"}; 863 | 864 | if (command === "universalArgument") { 865 | data.count = -4; 866 | return {command: "null"}; 867 | } 868 | var args; 869 | if (typeof command !== "string") { 870 | args = command.args; 871 | if (command.command) command = command.command; 872 | if (command === "goorselect") { 873 | command = editor.emacsMark() ? args[1] : args[0]; 874 | args = null; 875 | } 876 | } 877 | 878 | if (typeof command === "string") { 879 | if (command === "insertstring" || 880 | command === "splitline" || 881 | command === "togglecomment") { 882 | editor.pushEmacsMark(); 883 | } 884 | command = this.commands[command] || editor.commands.commands[command]; 885 | if (!command) return undefined; 886 | } 887 | 888 | if (!command.readOnly && !command.isYank) 889 | data.lastCommand = null; 890 | 891 | if (!command.readOnly && editor.emacsMark()) 892 | editor.setEmacsMark(null) 893 | 894 | if (data.count) { 895 | var count = data.count; 896 | data.count = 0; 897 | if (!command || !command.handlesCount) { 898 | return { 899 | args: args, 900 | command: { 901 | exec: function(editor, args) { 902 | for (var i = 0; i < count; i++) 903 | command.exec(editor, args); 904 | }, 905 | multiSelectAction: command.multiSelectAction 906 | } 907 | }; 908 | } else { 909 | if (!args) args = {}; 910 | if (typeof args === 'object') args.count = count; 911 | } 912 | } 913 | 914 | return {command: command, args: args}; 915 | }; 916 | 917 | exports.emacsKeys = { 918 | "Up|C-p" : {command: "goorselect", args: ["golineup","selectup"]}, 919 | "Down|C-n" : {command: "goorselect", args: ["golinedown","selectdown"]}, 920 | "Left|C-b" : {command: "goorselect", args: ["gotoleft","selectleft"]}, 921 | "Right|C-f" : {command: "goorselect", args: ["gotoright","selectright"]}, 922 | "C-Left|M-b" : {command: "goorselect", args: ["gotowordleft","selectwordleft"]}, 923 | "C-Right|M-f" : {command: "goorselect", args: ["gotowordright","selectwordright"]}, 924 | "Home|C-a" : {command: "goorselect", args: ["gotolinestart","selecttolinestart"]}, 925 | "End|C-e" : {command: "goorselect", args: ["gotolineend","selecttolineend"]}, 926 | "C-Home|S-M-,": {command: "goorselect", args: ["gotostart","selecttostart"]}, 927 | "C-End|S-M-." : {command: "goorselect", args: ["gotoend","selecttoend"]}, 928 | "S-Up|S-C-p" : "selectup", 929 | "S-Down|S-C-n" : "selectdown", 930 | "S-Left|S-C-b" : "selectleft", 931 | "S-Right|S-C-f" : "selectright", 932 | "S-C-Left|S-M-b" : "selectwordleft", 933 | "S-C-Right|S-M-f" : "selectwordright", 934 | "S-Home|S-C-a" : "selecttolinestart", 935 | "S-End|S-C-e" : "selecttolineend", 936 | "S-C-Home" : "selecttostart", 937 | "S-C-End" : "selecttoend", 938 | 939 | "C-l" : "recenterTopBottom", 940 | "M-s" : "centerselection", 941 | "M-g": "gotoline", 942 | "C-x C-p": "selectall", 943 | "C-Down": {command: "goorselect", args: ["gotopagedown","selectpagedown"]}, 944 | "C-Up": {command: "goorselect", args: ["gotopageup","selectpageup"]}, 945 | "PageDown|C-v": {command: "goorselect", args: ["gotopagedown","selectpagedown"]}, 946 | "PageUp|M-v": {command: "goorselect", args: ["gotopageup","selectpageup"]}, 947 | "S-C-Down": "selectpagedown", 948 | "S-C-Up": "selectpageup", 949 | 950 | "C-s": "iSearch", 951 | "C-r": "iSearchBackwards", 952 | 953 | "M-C-s": "findnext", 954 | "M-C-r": "findprevious", 955 | "S-M-5": "replace", 956 | "Backspace": "backspace", 957 | "Delete|C-d": "del", 958 | "Return|C-m": {command: "insertstring", args: "\n"}, // "newline" 959 | "C-o": "splitline", 960 | 961 | "M-d|C-Delete": {command: "killWord", args: "right"}, 962 | "C-Backspace|M-Backspace|M-Delete": {command: "killWord", args: "left"}, 963 | "C-k": "killLine", 964 | 965 | "C-y|S-Delete": "yank", 966 | "M-y": "yankRotate", 967 | "C-g": "keyboardQuit", 968 | 969 | "C-w|C-S-W": "killRegion", 970 | "M-w": "killRingSave", 971 | "C-Space": "setMark", 972 | "C-x C-x": "exchangePointAndMark", 973 | 974 | "C-t": "transposeletters", 975 | "M-u": "touppercase", // Doesn't work 976 | "M-l": "tolowercase", 977 | "M-/": "autocomplete", // Doesn't work 978 | "C-u": "universalArgument", 979 | 980 | "M-;": "togglecomment", 981 | 982 | "C-/|C-x u|S-C--|C-z": "undo", 983 | "S-C-/|S-C-x u|C--|S-C-z": "redo", //infinite undo? 984 | "C-x r": "selectRectangularRegion", 985 | "M-x": {command: "focusCommandLine", args: "M-x "} 986 | }; 987 | 988 | 989 | exports.handler.bindKeys(exports.emacsKeys); 990 | 991 | exports.handler.addCommands({ 992 | recenterTopBottom: function(editor) { 993 | var renderer = editor.renderer; 994 | var pos = renderer.$cursorLayer.getPixelPosition(); 995 | var h = renderer.$size.scrollerHeight - renderer.lineHeight; 996 | var scrollTop = renderer.scrollTop; 997 | if (Math.abs(pos.top - scrollTop) < 2) { 998 | scrollTop = pos.top - h; 999 | } else if (Math.abs(pos.top - scrollTop - h * 0.5) < 2) { 1000 | scrollTop = pos.top; 1001 | } else { 1002 | scrollTop = pos.top - h * 0.5; 1003 | } 1004 | editor.session.setScrollTop(scrollTop); 1005 | }, 1006 | selectRectangularRegion: function(editor) { 1007 | editor.multiSelect.toggleBlockSelection(); 1008 | }, 1009 | setMark: { 1010 | exec: function(editor, args) { 1011 | 1012 | if (args && args.count) { 1013 | if (editor.inMultiSelectMode) editor.forEachSelection(moveToMark); 1014 | else moveToMark(); 1015 | moveToMark(); 1016 | return; 1017 | } 1018 | 1019 | var mark = editor.emacsMark(), 1020 | ranges = editor.selection.getAllRanges(), 1021 | rangePositions = ranges.map(function(r) { return {row: r.start.row, column: r.start.column}; }), 1022 | transientMarkModeActive = true, 1023 | hasNoSelection = ranges.every(function(range) { return range.isEmpty(); }); 1024 | if (transientMarkModeActive && (mark || !hasNoSelection)) { 1025 | if (editor.inMultiSelectMode) editor.forEachSelection({exec: editor.clearSelection.bind(editor)}) 1026 | else editor.clearSelection(); 1027 | if (mark) editor.pushEmacsMark(null); 1028 | return; 1029 | } 1030 | 1031 | if (!mark) { 1032 | rangePositions.forEach(function(pos) { editor.pushEmacsMark(pos); }); 1033 | editor.setEmacsMark(rangePositions[rangePositions.length-1]); 1034 | return; 1035 | } 1036 | 1037 | function moveToMark() { 1038 | var mark = editor.popEmacsMark(); 1039 | mark && editor.moveCursorToPosition(mark); 1040 | } 1041 | 1042 | }, 1043 | readOnly: true, 1044 | handlesCount: true 1045 | }, 1046 | exchangePointAndMark: { 1047 | exec: function exchangePointAndMark$exec(editor, args) { 1048 | var sel = editor.selection; 1049 | if (!args.count && !sel.isEmpty()) { // just invert selection 1050 | sel.setSelectionRange(sel.getRange(), !sel.isBackwards()); 1051 | return; 1052 | } 1053 | 1054 | if (args.count) { // replace mark and point 1055 | var pos = {row: sel.lead.row, column: sel.lead.column}; 1056 | sel.clearSelection(); 1057 | sel.moveCursorToPosition(editor.emacsMarkForSelection(pos)); 1058 | } else { // create selection to last mark 1059 | sel.selectToPosition(editor.emacsMarkForSelection()); 1060 | } 1061 | }, 1062 | readOnly: true, 1063 | handlesCount: true, 1064 | multiSelectAction: "forEach" 1065 | }, 1066 | killWord: { 1067 | exec: function(editor, dir) { 1068 | editor.clearSelection(); 1069 | if (dir == "left") 1070 | editor.selection.selectWordLeft(); 1071 | else 1072 | editor.selection.selectWordRight(); 1073 | 1074 | var range = editor.getSelectionRange(); 1075 | var text = editor.session.getTextRange(range); 1076 | exports.killRing.add(text); 1077 | 1078 | editor.session.remove(range); 1079 | editor.clearSelection(); 1080 | }, 1081 | multiSelectAction: "forEach" 1082 | }, 1083 | killLine: function(editor) { 1084 | editor.pushEmacsMark(null); 1085 | var pos = editor.getCursorPosition(); 1086 | if (pos.column === 0 && 1087 | editor.session.doc.getLine(pos.row).length === 0) { 1088 | editor.selection.selectLine(); 1089 | } else { 1090 | editor.clearSelection(); 1091 | editor.selection.selectLineEnd(); 1092 | } 1093 | var range = editor.getSelectionRange(); 1094 | var text = editor.session.getTextRange(range); 1095 | exports.killRing.add(text); 1096 | 1097 | editor.session.remove(range); 1098 | editor.clearSelection(); 1099 | }, 1100 | yank: function(editor) { 1101 | editor.onPaste(exports.killRing.get() || ''); 1102 | editor.keyBinding.$data.lastCommand = "yank"; 1103 | }, 1104 | yankRotate: function(editor) { 1105 | if (editor.keyBinding.$data.lastCommand != "yank") 1106 | return; 1107 | editor.undo(); 1108 | editor.session.$emacsMarkRing.pop(); // also undo recording mark 1109 | editor.onPaste(exports.killRing.rotate()); 1110 | editor.keyBinding.$data.lastCommand = "yank"; 1111 | }, 1112 | killRegion: { 1113 | exec: function(editor) { 1114 | exports.killRing.add(editor.getCopyText()); 1115 | editor.commands.byName.cut.exec(editor); 1116 | }, 1117 | readOnly: true, 1118 | multiSelectAction: "forEach" 1119 | }, 1120 | killRingSave: { 1121 | exec: function(editor) { 1122 | 1123 | editor.$handlesEmacsOnCopy = true; 1124 | var marks = editor.session.$emacsMarkRing.slice(), 1125 | deselectedMarks = []; 1126 | exports.killRing.add(editor.getCopyText()); 1127 | 1128 | setTimeout(function() { 1129 | function deselect() { 1130 | var sel = editor.selection, range = sel.getRange(), 1131 | pos = sel.isBackwards() ? range.end : range.start; 1132 | deselectedMarks.push({row: pos.row, column: pos.column}); 1133 | sel.clearSelection(); 1134 | } 1135 | editor.$handlesEmacsOnCopy = false; 1136 | if (editor.inMultiSelectMode) editor.forEachSelection({exec: deselect}); 1137 | else deselect(); 1138 | editor.session.$emacsMarkRing = marks.concat(deselectedMarks.reverse()); 1139 | }, 0); 1140 | }, 1141 | readOnly: true 1142 | }, 1143 | keyboardQuit: function(editor) { 1144 | editor.selection.clearSelection(); 1145 | editor.setEmacsMark(null); 1146 | editor.keyBinding.$data.count = null; 1147 | }, 1148 | focusCommandLine: function(editor, arg) { 1149 | if (editor.showCommandLine) 1150 | editor.showCommandLine(arg); 1151 | } 1152 | }); 1153 | 1154 | exports.handler.addCommands(iSearchCommandModule.iSearchStartCommands); 1155 | 1156 | var commands = exports.handler.commands; 1157 | commands.yank.isYank = true; 1158 | commands.yankRotate.isYank = true; 1159 | 1160 | exports.killRing = { 1161 | $data: [], 1162 | add: function(str) { 1163 | str && this.$data.push(str); 1164 | if (this.$data.length > 30) 1165 | this.$data.shift(); 1166 | }, 1167 | get: function(n) { 1168 | n = n || 1; 1169 | return this.$data.slice(this.$data.length-n, this.$data.length).reverse().join('\n'); 1170 | }, 1171 | pop: function() { 1172 | if (this.$data.length > 1) 1173 | this.$data.pop(); 1174 | return this.get(); 1175 | }, 1176 | rotate: function() { 1177 | this.$data.unshift(this.$data.pop()); 1178 | return this.get(); 1179 | } 1180 | }; 1181 | 1182 | }); 1183 | -------------------------------------------------------------------------------- /vendor/three-codeeditor/snippets/javascript.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/snippets/javascript",["require","exports","module"], function(require, exports, module) { 2 | "use strict"; 3 | 4 | exports.snippetText = "# Prototype\nsnippet proto\n\t${1:class}.prototype.${2:method} = function(${3:arg}) {\n\t\t${4:// body...}\n\t};\n# Function\nsnippet fun\n\tfunction ${1?:functionName}(${2}) {${0}}\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/\nname f\n\tfunction($2) {${0:$TM_SELECTED_TEXT}}${M2?;}${M3?,}\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f2/(\\))?/\nname f2\n\tfunction${M1?: ${1:functionName}}($2) {${0:$TM_SELECTED_TEXT}}${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n\t(function ${1:functionName}(${2}) {\n\t\t${0:${TM_SELECTED_TEXT:/* code */}}\n\t})(${2});\n# if\nsnippet if\n\tif (${1:true}) {\n\t\t${0}\n\t}\n# if ... else\nsnippet ife\n\tif (${1:true}) {\n\t\t${2}\n\t} else {\n\t\t${0}\n\t}\nsnippet err\n\tif (${1:err}) { ${2:thenDo}(err, null); return; }\n# tertiary conditional\nsnippet ter\n\t${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n\tswitch (${1:expression}) {\n\t\tcase '${3:case}':\n\t\t\t${4:// code}\n\t\t\tbreak;\n\t\t${5}\n\t\tdefault:\n\t\t\t${2:// code}\n\t}\n# case\nsnippet case\n\tcase '${1:case}':\n\t\t${2:// code}\n\t\tbreak;\n\t${3}\n\n# while (...) {...}\nsnippet wh\n\twhile (${1:/* condition */}) {${0:/* code */}}\n# try\nsnippet try\n\ttry {${0:/* code */}} catch (e) {}\n# do...while\nsnippet do\n\tdo {${2:/* code */}} while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n\t${1:methodName}: function(${2:attribute}) {${0}}${3:,}\n# Object Property\nsnippet :p\nregex /([,{[])|^\\s*/:p/\n\t${1:propName}: ${0:value,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n\tsetTimeout(function() {${0:$TM_SELECTED_TEXT}}, ${1:100});\nsnippet setInterval\nregex /\\b/int|setI?n?t?e?r?v?a?l?/\n\tvar i = setInterval(function() {${0:$TM_SELECTED_TEXT}}, ${1:1000});\n\tclearInterval(i);\n# delay function\nsnippet delay\nregex /\\b/st|del(ay)?/\n\t(function() {${0}})${2:.bind(this)}.delay(${1:0});\n# Get Elements\nsnippet gett\n\tgetElementsBy${1:TagName}('${2}')${3}\n# Get Element\nsnippet get\n\tgetElementBy${1:Id}('${2}')${3}\n# console.log (Firebug)\nsnippet cl\n\tconsole.log(${1});\n# show (lively)\nsnippet s\n\tshow(\"${1:%o}\", ${2});\n# session snippet\nsnippet getS\n\tvar s = lively.net.SessionTracker.getSession();\n# return\nsnippet ret\n\treturn ${1:result};\n# for (property in object ) { ... }\nsnippet fori\n\tfor (var ${1:prop} in ${2:Things}) {\n\t\t${0:$2[$1]}\n\t}\n# hasOwnProperty\nsnippet has\n\thasOwnProperty(${1})\n# printit comment\nsnippet />\n\t// => \n# block comment\nsnippet /*\n\t/*\n\t * ${1:description}\n\t */\n# docstring\nsnippet /**\n\t/**\n\t * ${1:description}\n\t *\n\t */\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n\t@param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n\t@return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n\tJSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n\tJSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n\tvar ${1:function_name} = function(${2:argument}) {\n\t\t${3:// initial code ...}\n\t\t$1 = function($2) {${4:// main code}};\n\t}\n# class\nname subclass\nregex /^\\s*([^\\.]+)?/\\.?(sub)?class/\n\t${M1?:${1:Object}}.subclass(\"${2:Name}\",\n\t\"initializing\", {\n\t\tinitialize: function(${3}) {\n\t\t\t${0}\n\t\t}\n\t});\n# TestClass\nname TestClass\nregex /^\\s*/TestCl?a?s?s?/\n\tTestCase.subclass(\"${1:TestClass}\",\n\t\"running\", {\n\t\tsetUp: function($super) {},\n\t\ttearDown: function($super) {}\n\t},\n\t'testing', {\n\t\t${2:test1}: function() {${0}}\n\t});\n# initialize methods\nsnippet init\n\tinitialize: function(${1}) {${0}}\n# initialize category\nsnippet initCat\n\t'initializing', {\n\t\tinitialize: function(${1}) {${0}}\n\t}\n# category\nsnippet cat\n\t'${1:category name}', {${0}}${2:,}\n# addMethods\nname addMethods\nregex /^\\s*([^\\.]+)?/\\.?addMe?t?h?o?d?s?/\n\t${M1?:${1:SomeClass}}.addMethods(\n\t\"${2:category}\", {\n\t\t${0}\n\t});\n# Object.extend\nsnippet Oe\n\tObject.extend(${1:obj}, {$0});\n# collection iter\nregex /(\\.?[^\\.\\s]+)/\\.?((iter|forEa?c?h?)|(select|collect|reject|detect|map|filter))/\n\t${M1?:${1:list}}.${M4?${M4}:${2:forEach}}(function(${3:ea}) {$0});\n# collection inject\nregex /(\\.?[^\\.\\s]+|^\\s*)/\\.?inje?c?t?/\n\t${M1?:${1:list}}.inject(${2:collector}, function($2, ea) { ${0:return $2;} });\n# collection withAllSubmorphsDo\nregex /(\\.?[^\\.\\s]+|^\\s*)/\\.?withAl?l?S?u?b?m?o?r?p?h?s?D?o?/\n\t${M1?:${1:owner}}.withAllSubmorphsDo(function(ea) { return ${0:ea}; });\n# addScript\nregex /(\\.?[^\\.\\s]+|^\\s*)/\\.?addSc?r?i?p?t?/\n\t${M1?:${1:morph}}.addScript(function ${2:scriptName}(${3}) {${0}});\n# lively.bindings.connect\nsnippet con\n\tlively.bindings.connect(${1:source}, '${2:sourceAttr}', ${3:target}, '${4:targetAttr}');\n# lively.bindings.connect converter\nsnippet conC\n\tlively.bindings.connect(${1:source}, '${2:sourceAttr}', ${3:target}, '${4:targetAttr}', {\n\t\tconverter: function(val) { ${0:return val;} }});\n# lively.bindings.connect updater\nsnippet conU\n\tlively.bindings.connect(${1:source}, '${2:sourceAttr}', ${3:target}, '${4:targetAttr}', {\n\t\tupdater: function(\\$upd, val) { ${0:\\$upd(val);} }});\n# lively.bindings.signal\nsnippet sig\n\tlively.bindings.signal(${1:source}, '${2:sourceAttr}', ${3:value});\n# lively meta - grep\nsnippet $g\n\t\\$grep('${1:string}', '${0:location}')\n# lively world\nsnippet $w\n\t\\$world\n# $morph\nsnippet $m\n\t\\$morph('${1:name}')${0}\n# \nsnippet for-\n\tfor (var ${1:i} = ${2:THINGS}.length; ${1:i}--; ) {\n\t\t${0:${2:THINGS}[${1:i}];}\n\t}\n# for (...) {...}\nsnippet for\n\tfor (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n\t\t${3:$2[$1]}$0\n\t}\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n\tfor (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n\t\t${3:$2[$1]}$0\n\t}\n#modules\nsnippet def\n\tdefine(function(require, exports, module) {\n\t\"use strict\";\n\tvar ${1/.*\\///} = require(\"${1}\");\n\t\n\t$TM_SELECTED_TEXT\n\t});\nsnippet req\nguard ^\\s*\n\tvar ${1/.*\\///} = require(\"${1}\");\n\t$0\nsnippet requ\nguard ^\\s*\n\tvar ${1/.*\\/(.)/\\u$1/} = require(\"${1}\").${1/.*\\/(.)/\\u$1/};\n\t$0\n# Functions.composeAsync\nsnippet async\n\tFunctions.composeAsync(\n\t\tfunction(${1}next) { ${0:next(null);} }\n\t)(${1}function(err) { if (err) show(err); });\n# typeof tests\nsnippet typef\n\ttypeof ${1:object} === \"function\"${0}\nsnippet typeu\n\ttypeof ${1:object} === \"undefined\"${0}\nsnippet typenu\n\ttypeof ${1:object} !== \"undefined\"${0}\n# $world snippets\nsnippet prompt\n\t\\$world.prompt(\"${1:query text}\", function(input) {\n\t\tif (!input) return;\n\t\t${0}\n\t}, {input: \"${2:initial input}\", historyId: \"${3:prompt-id}\"});\nsnippet confirm\n\t\\$world.confirm(\"${1:query text}\", function(input) {\n\t\tif (!input) return;\n\t\t${0}\n\t});\n" 5 | exports.scope = "javascript"; 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /vendor/three-codeeditor/snippets/text.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/snippets/text",["require","exports","module"], function(require, exports, module) { 2 | "use strict"; 3 | 4 | exports.snippetText =undefined; 5 | exports.scope = "text"; 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /vendor/three-codeeditor/theme-twilight.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/theme/twilight",["require","exports","module","ace/lib/dom"], function(require, exports, module) { 2 | 3 | exports.isDark = true; 4 | exports.cssClass = "ace-twilight"; 5 | exports.cssText = ".ace-twilight .ace_gutter {\ 6 | background: #232323;\ 7 | color: #E2E2E2\ 8 | }\ 9 | .ace-twilight .ace_print-margin {\ 10 | width: 1px;\ 11 | background: #232323\ 12 | }\ 13 | .ace-twilight {\ 14 | background-color: #141414;\ 15 | color: #F8F8F8\ 16 | }\ 17 | .ace-twilight .ace_cursor {\ 18 | color: #A7A7A7\ 19 | }\ 20 | .ace-twilight .ace_marker-layer .ace_selection {\ 21 | background: rgba(221, 240, 255, 0.20)\ 22 | }\ 23 | .ace-twilight.ace_multiselect .ace_selection.ace_start {\ 24 | box-shadow: 0 0 3px 0px #141414;\ 25 | border-radius: 2px\ 26 | }\ 27 | .ace-twilight .ace_marker-layer .ace_step {\ 28 | background: rgb(102, 82, 0)\ 29 | }\ 30 | .ace-twilight .ace_marker-layer .ace_bracket {\ 31 | margin: -1px 0 0 -1px;\ 32 | border: 1px solid rgba(255, 255, 255, 0.25)\ 33 | }\ 34 | .ace-twilight .ace_marker-layer .ace_active-line {\ 35 | background: rgba(255, 255, 255, 0.031)\ 36 | }\ 37 | .ace-twilight .ace_gutter-active-line {\ 38 | background-color: rgba(255, 255, 255, 0.031)\ 39 | }\ 40 | .ace-twilight .ace_marker-layer .ace_selected-word {\ 41 | border: 1px solid rgba(221, 240, 255, 0.20)\ 42 | }\ 43 | .ace-twilight .ace_invisible {\ 44 | color: rgba(255, 255, 255, 0.25)\ 45 | }\ 46 | .ace-twilight .ace_keyword,\ 47 | .ace-twilight .ace_meta {\ 48 | color: #CDA869\ 49 | }\ 50 | .ace-twilight .ace_constant,\ 51 | .ace-twilight .ace_constant.ace_character,\ 52 | .ace-twilight .ace_constant.ace_character.ace_escape,\ 53 | .ace-twilight .ace_constant.ace_other,\ 54 | .ace-twilight .ace_heading,\ 55 | .ace-twilight .ace_markup.ace_heading,\ 56 | .ace-twilight .ace_support.ace_constant {\ 57 | color: #CF6A4C\ 58 | }\ 59 | .ace-twilight .ace_invalid.ace_illegal {\ 60 | color: #F8F8F8;\ 61 | background-color: rgba(86, 45, 86, 0.75)\ 62 | }\ 63 | .ace-twilight .ace_invalid.ace_deprecated {\ 64 | text-decoration: underline;\ 65 | font-style: italic;\ 66 | color: #D2A8A1\ 67 | }\ 68 | .ace-twilight .ace_support {\ 69 | color: #9B859D\ 70 | }\ 71 | .ace-twilight .ace_fold {\ 72 | background-color: #AC885B;\ 73 | border-color: #F8F8F8\ 74 | }\ 75 | .ace-twilight .ace_support.ace_function {\ 76 | color: #DAD085\ 77 | }\ 78 | .ace-twilight .ace_list,\ 79 | .ace-twilight .ace_markup.ace_list,\ 80 | .ace-twilight .ace_storage {\ 81 | color: #F9EE98\ 82 | }\ 83 | .ace-twilight .ace_entity.ace_name.ace_function,\ 84 | .ace-twilight .ace_meta.ace_tag,\ 85 | .ace-twilight .ace_variable {\ 86 | color: #AC885B\ 87 | }\ 88 | .ace-twilight .ace_string {\ 89 | color: #8F9D6A\ 90 | }\ 91 | .ace-twilight .ace_string.ace_regexp {\ 92 | color: #E9C062\ 93 | }\ 94 | .ace-twilight .ace_comment {\ 95 | font-style: italic;\ 96 | color: #5F5A60\ 97 | }\ 98 | .ace-twilight .ace_variable {\ 99 | color: #7587A6\ 100 | }\ 101 | .ace-twilight .ace_xml-pe {\ 102 | color: #494949\ 103 | }\ 104 | .ace-twilight .ace_indent-guide {\ 105 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWMQERFpYLC1tf0PAAgOAnPnhxyiAAAAAElFTkSuQmCC) right repeat-y\ 106 | }"; 107 | 108 | var dom = require("../lib/dom"); 109 | dom.importCssString(exports.cssText, exports.cssClass); 110 | }); 111 | -------------------------------------------------------------------------------- /vendor/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 | /*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 | // How far you can orbit horizontally, upper and lower limits. 68 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 69 | this.minAzimuthAngle = - Infinity; // radians 70 | this.maxAzimuthAngle = Infinity; // radians 71 | 72 | // Set to true to disable use of the keys 73 | this.noKeys = false; 74 | 75 | // The four arrow keys 76 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 77 | 78 | // Mouse buttons 79 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 80 | 81 | //////////// 82 | // internals 83 | 84 | var scope = this; 85 | 86 | var EPS = 0.000001; 87 | 88 | var rotateStart = new THREE.Vector2(); 89 | var rotateEnd = new THREE.Vector2(); 90 | var rotateDelta = new THREE.Vector2(); 91 | 92 | var panStart = new THREE.Vector2(); 93 | var panEnd = new THREE.Vector2(); 94 | var panDelta = new THREE.Vector2(); 95 | var panOffset = new THREE.Vector3(); 96 | 97 | var offset = new THREE.Vector3(); 98 | 99 | var dollyStart = new THREE.Vector2(); 100 | var dollyEnd = new THREE.Vector2(); 101 | var dollyDelta = new THREE.Vector2(); 102 | 103 | var phiDelta = 0; 104 | var thetaDelta = 0; 105 | var scale = 1; 106 | var pan = new THREE.Vector3(); 107 | 108 | var lastPosition = new THREE.Vector3(); 109 | var lastQuaternion = new THREE.Quaternion(); 110 | 111 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 112 | 113 | var state = STATE.NONE; 114 | 115 | // for reset 116 | 117 | this.target0 = this.target.clone(); 118 | this.position0 = this.object.position.clone(); 119 | 120 | // so camera.up is the orbit axis 121 | 122 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 123 | var quatInverse = quat.clone().inverse(); 124 | 125 | // events 126 | 127 | var changeEvent = { type: 'change' }; 128 | var startEvent = { type: 'start'}; 129 | var endEvent = { type: 'end'}; 130 | 131 | this.rotateLeft = function ( angle ) { 132 | 133 | if ( angle === undefined ) { 134 | 135 | angle = getAutoRotationAngle(); 136 | 137 | } 138 | 139 | thetaDelta -= angle; 140 | 141 | }; 142 | 143 | this.rotateUp = function ( angle ) { 144 | 145 | if ( angle === undefined ) { 146 | 147 | angle = getAutoRotationAngle(); 148 | 149 | } 150 | 151 | phiDelta -= angle; 152 | 153 | }; 154 | 155 | // pass in distance in world space to move left 156 | this.panLeft = function ( distance ) { 157 | 158 | var te = this.object.matrix.elements; 159 | 160 | // get X column of matrix 161 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 162 | panOffset.multiplyScalar( - distance ); 163 | 164 | pan.add( panOffset ); 165 | 166 | }; 167 | 168 | // pass in distance in world space to move up 169 | this.panUp = function ( distance ) { 170 | 171 | var te = this.object.matrix.elements; 172 | 173 | // get Y column of matrix 174 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 175 | panOffset.multiplyScalar( distance ); 176 | 177 | pan.add( panOffset ); 178 | 179 | }; 180 | 181 | // pass in x,y of change desired in pixel space, 182 | // right and down are positive 183 | this.pan = function ( deltaX, deltaY ) { 184 | 185 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 186 | 187 | if ( scope.object.fov !== undefined ) { 188 | 189 | // perspective 190 | var position = scope.object.position; 191 | var offset = position.clone().sub( scope.target ); 192 | var targetDistance = offset.length(); 193 | 194 | // half of the fov is center to top of screen 195 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 196 | 197 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 198 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); 199 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); 200 | 201 | } else if ( scope.object.top !== undefined ) { 202 | 203 | // orthographic 204 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); 205 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); 206 | 207 | } else { 208 | 209 | // camera neither orthographic or perspective 210 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 211 | 212 | } 213 | 214 | }; 215 | 216 | this.dollyIn = function ( dollyScale ) { 217 | 218 | if ( dollyScale === undefined ) { 219 | 220 | dollyScale = getZoomScale(); 221 | 222 | } 223 | 224 | scale /= dollyScale; 225 | 226 | }; 227 | 228 | this.dollyOut = function ( dollyScale ) { 229 | 230 | if ( dollyScale === undefined ) { 231 | 232 | dollyScale = getZoomScale(); 233 | 234 | } 235 | 236 | scale *= dollyScale; 237 | 238 | }; 239 | 240 | this.update = function () { 241 | 242 | var position = this.object.position; 243 | 244 | offset.copy( position ).sub( this.target ); 245 | 246 | // rotate offset to "y-axis-is-up" space 247 | offset.applyQuaternion( quat ); 248 | 249 | // angle from z-axis around y-axis 250 | 251 | var theta = Math.atan2( offset.x, offset.z ); 252 | 253 | // angle from y-axis 254 | 255 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 256 | 257 | if ( this.autoRotate ) { 258 | 259 | this.rotateLeft( getAutoRotationAngle() ); 260 | 261 | } 262 | 263 | theta += thetaDelta; 264 | phi += phiDelta; 265 | 266 | // restrict theta to be between desired limits 267 | theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); 268 | 269 | // restrict phi to be between desired limits 270 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 271 | 272 | // restrict phi to be betwee EPS and PI-EPS 273 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 274 | 275 | var radius = offset.length() * scale; 276 | 277 | // restrict radius to be between desired limits 278 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 279 | 280 | // move target to panned location 281 | this.target.add( pan ); 282 | 283 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 284 | offset.y = radius * Math.cos( phi ); 285 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 286 | 287 | // rotate offset back to "camera-up-vector-is-up" space 288 | offset.applyQuaternion( quatInverse ); 289 | 290 | position.copy( this.target ).add( offset ); 291 | 292 | this.object.lookAt( this.target ); 293 | 294 | thetaDelta = 0; 295 | phiDelta = 0; 296 | scale = 1; 297 | pan.set( 0, 0, 0 ); 298 | 299 | // update condition is: 300 | // min(camera displacement, camera rotation in radians)^2 > EPS 301 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 302 | 303 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS 304 | || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { 305 | 306 | this.dispatchEvent( changeEvent ); 307 | 308 | lastPosition.copy( this.object.position ); 309 | lastQuaternion.copy (this.object.quaternion ); 310 | 311 | } 312 | 313 | }; 314 | 315 | 316 | this.reset = function () { 317 | 318 | state = STATE.NONE; 319 | 320 | this.target.copy( this.target0 ); 321 | this.object.position.copy( this.position0 ); 322 | 323 | this.update(); 324 | 325 | }; 326 | 327 | function getAutoRotationAngle() { 328 | 329 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 330 | 331 | } 332 | 333 | function getZoomScale() { 334 | 335 | return Math.pow( 0.95, scope.zoomSpeed ); 336 | 337 | } 338 | 339 | function onMouseDown( event ) { 340 | 341 | if ( scope.enabled === false ) return; 342 | event.preventDefault(); 343 | 344 | if ( event.button === scope.mouseButtons.ORBIT ) { 345 | if ( scope.noRotate === true ) return; 346 | 347 | state = STATE.ROTATE; 348 | 349 | rotateStart.set( event.clientX, event.clientY ); 350 | 351 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 352 | if ( scope.noZoom === true ) return; 353 | 354 | state = STATE.DOLLY; 355 | 356 | dollyStart.set( event.clientX, event.clientY ); 357 | 358 | } else if ( event.button === scope.mouseButtons.PAN ) { 359 | if ( scope.noPan === true ) return; 360 | 361 | state = STATE.PAN; 362 | 363 | panStart.set( event.clientX, event.clientY ); 364 | 365 | } 366 | 367 | document.addEventListener( 'mousemove', onMouseMove, false ); 368 | document.addEventListener( 'mouseup', onMouseUp, false ); 369 | scope.dispatchEvent( startEvent ); 370 | 371 | } 372 | 373 | function onMouseMove( event ) { 374 | 375 | if ( scope.enabled === false ) return; 376 | 377 | event.preventDefault(); 378 | 379 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 380 | 381 | if ( state === STATE.ROTATE ) { 382 | 383 | if ( scope.noRotate === true ) return; 384 | 385 | rotateEnd.set( event.clientX, event.clientY ); 386 | rotateDelta.subVectors( rotateEnd, rotateStart ); 387 | 388 | // rotating across whole screen goes 360 degrees around 389 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 390 | 391 | // rotating up and down along whole screen attempts to go 360, but limited to 180 392 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 393 | 394 | rotateStart.copy( rotateEnd ); 395 | 396 | } else if ( state === STATE.DOLLY ) { 397 | 398 | if ( scope.noZoom === true ) return; 399 | 400 | dollyEnd.set( event.clientX, event.clientY ); 401 | dollyDelta.subVectors( dollyEnd, dollyStart ); 402 | 403 | if ( dollyDelta.y > 0 ) { 404 | 405 | scope.dollyIn(); 406 | 407 | } else { 408 | 409 | scope.dollyOut(); 410 | 411 | } 412 | 413 | dollyStart.copy( dollyEnd ); 414 | 415 | } else if ( state === STATE.PAN ) { 416 | 417 | if ( scope.noPan === true ) return; 418 | 419 | panEnd.set( event.clientX, event.clientY ); 420 | panDelta.subVectors( panEnd, panStart ); 421 | 422 | scope.pan( panDelta.x, panDelta.y ); 423 | 424 | panStart.copy( panEnd ); 425 | 426 | } 427 | 428 | scope.update(); 429 | 430 | } 431 | 432 | function onMouseUp( /* event */ ) { 433 | 434 | if ( scope.enabled === false ) return; 435 | 436 | document.removeEventListener( 'mousemove', onMouseMove, false ); 437 | document.removeEventListener( 'mouseup', onMouseUp, false ); 438 | scope.dispatchEvent( endEvent ); 439 | state = STATE.NONE; 440 | 441 | } 442 | 443 | function onMouseWheel( event ) { 444 | 445 | if ( scope.enabled === false || scope.noZoom === true ) return; 446 | 447 | event.preventDefault(); 448 | event.stopPropagation(); 449 | 450 | var delta = 0; 451 | 452 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 453 | 454 | delta = event.wheelDelta; 455 | 456 | } else if ( event.detail !== undefined ) { // Firefox 457 | 458 | delta = - event.detail; 459 | 460 | } 461 | 462 | if ( delta > 0 ) { 463 | 464 | scope.dollyOut(); 465 | 466 | } else { 467 | 468 | scope.dollyIn(); 469 | 470 | } 471 | 472 | scope.update(); 473 | scope.dispatchEvent( startEvent ); 474 | scope.dispatchEvent( endEvent ); 475 | 476 | } 477 | 478 | function onKeyDown( event ) { 479 | 480 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; 481 | 482 | switch ( event.keyCode ) { 483 | 484 | case scope.keys.UP: 485 | scope.pan( 0, scope.keyPanSpeed ); 486 | scope.update(); 487 | break; 488 | 489 | case scope.keys.BOTTOM: 490 | scope.pan( 0, - scope.keyPanSpeed ); 491 | scope.update(); 492 | break; 493 | 494 | case scope.keys.LEFT: 495 | scope.pan( scope.keyPanSpeed, 0 ); 496 | scope.update(); 497 | break; 498 | 499 | case scope.keys.RIGHT: 500 | scope.pan( - scope.keyPanSpeed, 0 ); 501 | scope.update(); 502 | break; 503 | 504 | } 505 | 506 | } 507 | 508 | function touchstart( event ) { 509 | 510 | if ( scope.enabled === false ) return; 511 | 512 | switch ( event.touches.length ) { 513 | 514 | case 1: // one-fingered touch: rotate 515 | 516 | if ( scope.noRotate === true ) return; 517 | 518 | state = STATE.TOUCH_ROTATE; 519 | 520 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 521 | break; 522 | 523 | case 2: // two-fingered touch: dolly 524 | 525 | if ( scope.noZoom === true ) return; 526 | 527 | state = STATE.TOUCH_DOLLY; 528 | 529 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 530 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 531 | var distance = Math.sqrt( dx * dx + dy * dy ); 532 | dollyStart.set( 0, distance ); 533 | break; 534 | 535 | case 3: // three-fingered touch: pan 536 | 537 | if ( scope.noPan === true ) return; 538 | 539 | state = STATE.TOUCH_PAN; 540 | 541 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 542 | break; 543 | 544 | default: 545 | 546 | state = STATE.NONE; 547 | 548 | } 549 | 550 | scope.dispatchEvent( startEvent ); 551 | 552 | } 553 | 554 | function touchmove( event ) { 555 | 556 | if ( scope.enabled === false ) return; 557 | 558 | event.preventDefault(); 559 | event.stopPropagation(); 560 | 561 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 562 | 563 | switch ( event.touches.length ) { 564 | 565 | case 1: // one-fingered touch: rotate 566 | 567 | if ( scope.noRotate === true ) return; 568 | if ( state !== STATE.TOUCH_ROTATE ) return; 569 | 570 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 571 | rotateDelta.subVectors( rotateEnd, rotateStart ); 572 | 573 | // rotating across whole screen goes 360 degrees around 574 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 575 | // rotating up and down along whole screen attempts to go 360, but limited to 180 576 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 577 | 578 | rotateStart.copy( rotateEnd ); 579 | 580 | scope.update(); 581 | break; 582 | 583 | case 2: // two-fingered touch: dolly 584 | 585 | if ( scope.noZoom === true ) return; 586 | if ( state !== STATE.TOUCH_DOLLY ) return; 587 | 588 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 589 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 590 | var distance = Math.sqrt( dx * dx + dy * dy ); 591 | 592 | dollyEnd.set( 0, distance ); 593 | dollyDelta.subVectors( dollyEnd, dollyStart ); 594 | 595 | if ( dollyDelta.y > 0 ) { 596 | 597 | scope.dollyOut(); 598 | 599 | } else { 600 | 601 | scope.dollyIn(); 602 | 603 | } 604 | 605 | dollyStart.copy( dollyEnd ); 606 | 607 | scope.update(); 608 | break; 609 | 610 | case 3: // three-fingered touch: pan 611 | 612 | if ( scope.noPan === true ) return; 613 | if ( state !== STATE.TOUCH_PAN ) return; 614 | 615 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 616 | panDelta.subVectors( panEnd, panStart ); 617 | 618 | scope.pan( panDelta.x, panDelta.y ); 619 | 620 | panStart.copy( panEnd ); 621 | 622 | scope.update(); 623 | break; 624 | 625 | default: 626 | 627 | state = STATE.NONE; 628 | 629 | } 630 | 631 | } 632 | 633 | function touchend( /* event */ ) { 634 | 635 | if ( scope.enabled === false ) return; 636 | 637 | scope.dispatchEvent( endEvent ); 638 | state = STATE.NONE; 639 | 640 | } 641 | 642 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 643 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 644 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 645 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 646 | 647 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 648 | this.domElement.addEventListener( 'touchend', touchend, false ); 649 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 650 | 651 | window.addEventListener( 'keydown', onKeyDown, false ); 652 | 653 | // force an update at start 654 | this.update(); 655 | 656 | }; 657 | 658 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 659 | -------------------------------------------------------------------------------- /vendor/three/TransformControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author arodic / https://github.com/arodic 3 | */ 4 | /*jshint sub:true*/ 5 | 6 | (function () { 7 | 8 | 'use strict'; 9 | 10 | var GizmoMaterial = function ( parameters ) { 11 | 12 | THREE.MeshBasicMaterial.call( this ); 13 | 14 | this.depthTest = false; 15 | this.depthWrite = false; 16 | this.side = THREE.FrontSide; 17 | this.transparent = true; 18 | 19 | this.setValues( parameters ); 20 | 21 | this.oldColor = this.color.clone(); 22 | this.oldOpacity = this.opacity; 23 | 24 | this.highlight = function( highlighted ) { 25 | 26 | if ( highlighted ) { 27 | 28 | this.color.setRGB( 1, 1, 0 ); 29 | this.opacity = 1; 30 | 31 | } else { 32 | 33 | this.color.copy( this.oldColor ); 34 | this.opacity = this.oldOpacity; 35 | 36 | } 37 | 38 | }; 39 | 40 | }; 41 | 42 | GizmoMaterial.prototype = Object.create( THREE.MeshBasicMaterial.prototype ); 43 | 44 | var GizmoLineMaterial = function ( parameters ) { 45 | 46 | THREE.LineBasicMaterial.call( this ); 47 | 48 | this.depthTest = false; 49 | this.depthWrite = false; 50 | this.transparent = true; 51 | this.linewidth = 1; 52 | 53 | this.setValues( parameters ); 54 | 55 | this.oldColor = this.color.clone(); 56 | this.oldOpacity = this.opacity; 57 | 58 | this.highlight = function( highlighted ) { 59 | 60 | if ( highlighted ) { 61 | 62 | this.color.setRGB( 1, 1, 0 ); 63 | this.opacity = 1; 64 | 65 | } else { 66 | 67 | this.color.copy( this.oldColor ); 68 | this.opacity = this.oldOpacity; 69 | 70 | } 71 | 72 | }; 73 | 74 | }; 75 | 76 | GizmoLineMaterial.prototype = Object.create( THREE.LineBasicMaterial.prototype ); 77 | 78 | THREE.TransformGizmo = function () { 79 | 80 | var scope = this; 81 | var showPickers = false; //debug 82 | var showActivePlane = false; //debug 83 | 84 | this.init = function () { 85 | 86 | THREE.Object3D.call( this ); 87 | 88 | this.handles = new THREE.Object3D(); 89 | this.pickers = new THREE.Object3D(); 90 | this.planes = new THREE.Object3D(); 91 | 92 | this.add(this.handles); 93 | this.add(this.pickers); 94 | this.add(this.planes); 95 | 96 | //// PLANES 97 | 98 | var planeGeometry = new THREE.PlaneGeometry( 50, 50, 2, 2 ); 99 | var planeMaterial = new THREE.MeshBasicMaterial( { wireframe: true } ); 100 | planeMaterial.side = THREE.DoubleSide; 101 | 102 | var planes = { 103 | "XY": new THREE.Mesh( planeGeometry, planeMaterial ), 104 | "YZ": new THREE.Mesh( planeGeometry, planeMaterial ), 105 | "XZ": new THREE.Mesh( planeGeometry, planeMaterial ), 106 | "XYZE": new THREE.Mesh( planeGeometry, planeMaterial ) 107 | }; 108 | 109 | this.activePlane = planes["XYZE"]; 110 | 111 | planes["YZ"].rotation.set( 0, Math.PI/2, 0 ); 112 | planes["XZ"].rotation.set( -Math.PI/2, 0, 0 ); 113 | 114 | for (var i in planes) { 115 | planes[i].name = i; 116 | this.planes.add(planes[i]); 117 | this.planes[i] = planes[i]; 118 | planes[i].visible = false; 119 | } 120 | 121 | //// HANDLES AND PICKERS 122 | 123 | var setupGizmos = function( gizmoMap, parent ) { 124 | 125 | for ( var name in gizmoMap ) { 126 | 127 | for ( i = gizmoMap[name].length; i--;) { 128 | 129 | var object = gizmoMap[name][i][0]; 130 | var position = gizmoMap[name][i][1]; 131 | var rotation = gizmoMap[name][i][2]; 132 | 133 | object.name = name; 134 | 135 | if ( position ) object.position.set( position[0], position[1], position[2] ); 136 | if ( rotation ) object.rotation.set( rotation[0], rotation[1], rotation[2] ); 137 | 138 | parent.add( object ); 139 | 140 | } 141 | 142 | } 143 | 144 | }; 145 | 146 | setupGizmos(this.handleGizmos, this.handles); 147 | setupGizmos(this.pickerGizmos, this.pickers); 148 | 149 | // reset Transformations 150 | 151 | this.traverse(function ( child ) { 152 | if (child instanceof THREE.Mesh) { 153 | child.updateMatrix(); 154 | 155 | var tempGeometry = new THREE.Geometry(); 156 | tempGeometry.merge( child.geometry, child.matrix ); 157 | 158 | child.geometry = tempGeometry; 159 | child.position.set( 0, 0, 0 ); 160 | child.rotation.set( 0, 0, 0 ); 161 | child.scale.set( 1, 1, 1 ); 162 | } 163 | }); 164 | 165 | }; 166 | 167 | this.hide = function () { 168 | this.traverse(function( child ) { 169 | child.visible = false; 170 | }); 171 | }; 172 | 173 | this.show = function () { 174 | this.traverse(function( child ) { 175 | child.visible = true; 176 | if (child.parent == scope.pickers ) child.visible = showPickers; 177 | if (child.parent == scope.planes ) child.visible = false; 178 | }); 179 | this.activePlane.visible = showActivePlane; 180 | }; 181 | 182 | this.highlight = function ( axis ) { 183 | this.traverse(function( child ) { 184 | if ( child.material && child.material.highlight ){ 185 | if ( child.name == axis ) { 186 | child.material.highlight( true ); 187 | } else { 188 | child.material.highlight( false ); 189 | } 190 | } 191 | }); 192 | }; 193 | 194 | }; 195 | 196 | THREE.TransformGizmo.prototype = Object.create( THREE.Object3D.prototype ); 197 | 198 | THREE.TransformGizmo.prototype.update = function ( rotation, eye ) { 199 | 200 | var vec1 = new THREE.Vector3( 0, 0, 0 ); 201 | var vec2 = new THREE.Vector3( 0, 1, 0 ); 202 | var lookAtMatrix = new THREE.Matrix4(); 203 | 204 | this.traverse(function(child) { 205 | if ( child.name.search("E") != -1 ) { 206 | child.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( eye, vec1, vec2 ) ); 207 | } else if ( child.name.search("X") != -1 || child.name.search("Y") != -1 || child.name.search("Z") != -1 ) { 208 | child.quaternion.setFromEuler( rotation ); 209 | } 210 | }); 211 | 212 | }; 213 | 214 | THREE.TransformGizmoTranslate = function () { 215 | 216 | THREE.TransformGizmo.call( this ); 217 | 218 | var arrowGeometry = new THREE.Geometry(); 219 | var mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0, 0.05, 0.2, 12, 1, false ) ); 220 | mesh.position.y = 0.5; 221 | mesh.updateMatrix(); 222 | 223 | arrowGeometry.merge( mesh.geometry, mesh.matrix ); 224 | 225 | var lineXGeometry = new THREE.Geometry(); 226 | lineXGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ) ); 227 | 228 | var lineYGeometry = new THREE.Geometry(); 229 | lineYGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) ); 230 | 231 | var lineZGeometry = new THREE.Geometry(); 232 | lineZGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 1 ) ); 233 | 234 | this.handleGizmos = { 235 | X: [ 236 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI/2 ] ], 237 | [ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ] 238 | ], 239 | Y: [ 240 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ], 241 | [ new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] 242 | ], 243 | Z: [ 244 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI/2, 0, 0 ] ], 245 | [ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ] 246 | ], 247 | XYZ: [ 248 | [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, 0, 0 ] ] 249 | ], 250 | XY: [ 251 | [ new THREE.Mesh( new THREE.PlaneGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ), [ 0.15, 0.15, 0 ] ] 252 | ], 253 | YZ: [ 254 | [ new THREE.Mesh( new THREE.PlaneGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0x00ffff, opacity: 0.25 } ) ), [ 0, 0.15, 0.15 ], [ 0, Math.PI/2, 0 ] ] 255 | ], 256 | XZ: [ 257 | [ new THREE.Mesh( new THREE.PlaneGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xff00ff, opacity: 0.25 } ) ), [ 0.15, 0, 0.15 ], [ -Math.PI/2, 0, 0 ] ] 258 | ] 259 | }; 260 | 261 | this.pickerGizmos = { 262 | X: [ 263 | [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI/2 ] ] 264 | ], 265 | Y: [ 266 | [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0.6, 0 ] ] 267 | ], 268 | Z: [ 269 | [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0.6 ], [ Math.PI/2, 0, 0 ] ] 270 | ], 271 | XYZ: [ 272 | [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ] 273 | ], 274 | XY: [ 275 | [ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ), [ 0.2, 0.2, 0 ] ] 276 | ], 277 | YZ: [ 278 | [ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0x00ffff, opacity: 0.25 } ) ), [ 0, 0.2, 0.2 ], [ 0, Math.PI/2, 0 ] ] 279 | ], 280 | XZ: [ 281 | [ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0xff00ff, opacity: 0.25 } ) ), [ 0.2, 0, 0.2 ], [ -Math.PI/2, 0, 0 ] ] 282 | ] 283 | }; 284 | 285 | this.setActivePlane = function ( axis, eye ) { 286 | 287 | var tempMatrix = new THREE.Matrix4(); 288 | eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) ); 289 | 290 | if ( axis == "X" ) { 291 | this.activePlane = this.planes[ "XY" ]; 292 | if ( Math.abs(eye.y) > Math.abs(eye.z) ) this.activePlane = this.planes[ "XZ" ]; 293 | } 294 | 295 | if ( axis == "Y" ){ 296 | this.activePlane = this.planes[ "XY" ]; 297 | if ( Math.abs(eye.x) > Math.abs(eye.z) ) this.activePlane = this.planes[ "YZ" ]; 298 | } 299 | 300 | if ( axis == "Z" ){ 301 | this.activePlane = this.planes[ "XZ" ]; 302 | if ( Math.abs(eye.x) > Math.abs(eye.y) ) this.activePlane = this.planes[ "YZ" ]; 303 | } 304 | 305 | if ( axis == "XYZ" ) this.activePlane = this.planes[ "XYZE" ]; 306 | 307 | if ( axis == "XY" ) this.activePlane = this.planes[ "XY" ]; 308 | 309 | if ( axis == "YZ" ) this.activePlane = this.planes[ "YZ" ]; 310 | 311 | if ( axis == "XZ" ) this.activePlane = this.planes[ "XZ" ]; 312 | 313 | this.hide(); 314 | this.show(); 315 | 316 | }; 317 | 318 | this.init(); 319 | 320 | }; 321 | 322 | THREE.TransformGizmoTranslate.prototype = Object.create( THREE.TransformGizmo.prototype ); 323 | 324 | THREE.TransformGizmoRotate = function () { 325 | 326 | THREE.TransformGizmo.call( this ); 327 | 328 | var CircleGeometry = function ( radius, facing, arc ) { 329 | 330 | var geometry = new THREE.Geometry(); 331 | arc = arc ? arc : 1; 332 | for ( var i = 0; i <= 64 * arc; ++i ) { 333 | if ( facing == 'x' ) geometry.vertices.push( new THREE.Vector3( 0, Math.cos( i / 32 * Math.PI ), Math.sin( i / 32 * Math.PI ) ).multiplyScalar(radius) ); 334 | if ( facing == 'y' ) geometry.vertices.push( new THREE.Vector3( Math.cos( i / 32 * Math.PI ), 0, Math.sin( i / 32 * Math.PI ) ).multiplyScalar(radius) ); 335 | if ( facing == 'z' ) geometry.vertices.push( new THREE.Vector3( Math.sin( i / 32 * Math.PI ), Math.cos( i / 32 * Math.PI ), 0 ).multiplyScalar(radius) ); 336 | } 337 | 338 | return geometry; 339 | }; 340 | 341 | this.handleGizmos = { 342 | X: [ 343 | [ new THREE.Line( new CircleGeometry(1,'x',0.5), new GizmoLineMaterial( { color: 0xff0000 } ) ) ] 344 | ], 345 | Y: [ 346 | [ new THREE.Line( new CircleGeometry(1,'y',0.5), new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] 347 | ], 348 | Z: [ 349 | [ new THREE.Line( new CircleGeometry(1,'z',0.5), new GizmoLineMaterial( { color: 0x0000ff } ) ) ] 350 | ], 351 | E: [ 352 | [ new THREE.Line( new CircleGeometry(1.25,'z',1), new GizmoLineMaterial( { color: 0xcccc00 } ) ) ] 353 | ], 354 | XYZE: [ 355 | [ new THREE.Line( new CircleGeometry(1,'z',1), new GizmoLineMaterial( { color: 0x787878 } ) ) ] 356 | ] 357 | }; 358 | 359 | this.pickerGizmos = { 360 | X: [ 361 | [ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, -Math.PI/2, -Math.PI/2 ] ] 362 | ], 363 | Y: [ 364 | [ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ Math.PI/2, 0, 0 ] ] 365 | ], 366 | Z: [ 367 | [ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, 0, -Math.PI/2 ] ] 368 | ], 369 | E: [ 370 | [ new THREE.Mesh( new THREE.TorusGeometry( 1.25, 0.12, 2, 24 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ) ] 371 | ], 372 | XYZE: [ 373 | [ new THREE.Mesh( new THREE.Geometry() ) ]// TODO 374 | ] 375 | }; 376 | 377 | this.setActivePlane = function ( axis ) { 378 | 379 | if ( axis == "E" ) this.activePlane = this.planes[ "XYZE" ]; 380 | 381 | if ( axis == "X" ) this.activePlane = this.planes[ "YZ" ]; 382 | 383 | if ( axis == "Y" ) this.activePlane = this.planes[ "XZ" ]; 384 | 385 | if ( axis == "Z" ) this.activePlane = this.planes[ "XY" ]; 386 | 387 | this.hide(); 388 | this.show(); 389 | 390 | }; 391 | 392 | this.update = function ( rotation, eye2 ) { 393 | 394 | THREE.TransformGizmo.prototype.update.apply( this, arguments ); 395 | 396 | var group = { 397 | handles: this["handles"], 398 | pickers: this["pickers"], 399 | }; 400 | 401 | var tempMatrix = new THREE.Matrix4(); 402 | var worldRotation = new THREE.Euler( 0, 0, 1 ); 403 | var tempQuaternion = new THREE.Quaternion(); 404 | var unitX = new THREE.Vector3( 1, 0, 0 ); 405 | var unitY = new THREE.Vector3( 0, 1, 0 ); 406 | var unitZ = new THREE.Vector3( 0, 0, 1 ); 407 | var quaternionX = new THREE.Quaternion(); 408 | var quaternionY = new THREE.Quaternion(); 409 | var quaternionZ = new THREE.Quaternion(); 410 | var eye = eye2.clone(); 411 | 412 | worldRotation.copy( this.planes["XY"].rotation ); 413 | tempQuaternion.setFromEuler( worldRotation ); 414 | 415 | tempMatrix.makeRotationFromQuaternion( tempQuaternion ).getInverse( tempMatrix ); 416 | eye.applyMatrix4( tempMatrix ); 417 | 418 | this.traverse(function(child) { 419 | 420 | tempQuaternion.setFromEuler( worldRotation ); 421 | 422 | if ( child.name == "X" ) { 423 | quaternionX.setFromAxisAngle( unitX, Math.atan2( -eye.y, eye.z ) ); 424 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); 425 | child.quaternion.copy( tempQuaternion ); 426 | } 427 | 428 | if ( child.name == "Y" ) { 429 | quaternionY.setFromAxisAngle( unitY, Math.atan2( eye.x, eye.z ) ); 430 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY ); 431 | child.quaternion.copy( tempQuaternion ); 432 | } 433 | 434 | if ( child.name == "Z" ) { 435 | quaternionZ.setFromAxisAngle( unitZ, Math.atan2( eye.y, eye.x ) ); 436 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ ); 437 | child.quaternion.copy( tempQuaternion ); 438 | } 439 | 440 | }); 441 | 442 | }; 443 | 444 | this.init(); 445 | 446 | }; 447 | 448 | THREE.TransformGizmoRotate.prototype = Object.create( THREE.TransformGizmo.prototype ); 449 | 450 | THREE.TransformGizmoScale = function () { 451 | 452 | THREE.TransformGizmo.call( this ); 453 | 454 | var arrowGeometry = new THREE.Geometry(); 455 | var mesh = new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ) ); 456 | mesh.position.y = 0.5; 457 | mesh.updateMatrix(); 458 | 459 | arrowGeometry.merge( mesh.geometry, mesh.matrix ); 460 | 461 | var lineXGeometry = new THREE.Geometry(); 462 | lineXGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ) ); 463 | 464 | var lineYGeometry = new THREE.Geometry(); 465 | lineYGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) ); 466 | 467 | var lineZGeometry = new THREE.Geometry(); 468 | lineZGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 1 ) ); 469 | 470 | this.handleGizmos = { 471 | X: [ 472 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI/2 ] ], 473 | [ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ] 474 | ], 475 | Y: [ 476 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ], 477 | [ new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] 478 | ], 479 | Z: [ 480 | [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI/2, 0, 0 ] ], 481 | [ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ] 482 | ], 483 | XYZ: [ 484 | [ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ] 485 | ] 486 | }; 487 | 488 | this.pickerGizmos = { 489 | X: [ 490 | [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI/2 ] ] 491 | ], 492 | Y: [ 493 | [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0.6, 0 ] ] 494 | ], 495 | Z: [ 496 | [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0.6 ], [ Math.PI/2, 0, 0 ] ] 497 | ], 498 | XYZ: [ 499 | [ new THREE.Mesh( new THREE.BoxGeometry( 0.4, 0.4, 0.4 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ] 500 | ] 501 | }; 502 | 503 | this.setActivePlane = function ( axis, eye ) { 504 | 505 | var tempMatrix = new THREE.Matrix4(); 506 | eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) ); 507 | 508 | if ( axis == "X" ) { 509 | this.activePlane = this.planes[ "XY" ]; 510 | if ( Math.abs(eye.y) > Math.abs(eye.z) ) this.activePlane = this.planes[ "XZ" ]; 511 | } 512 | 513 | if ( axis == "Y" ){ 514 | this.activePlane = this.planes[ "XY" ]; 515 | if ( Math.abs(eye.x) > Math.abs(eye.z) ) this.activePlane = this.planes[ "YZ" ]; 516 | } 517 | 518 | if ( axis == "Z" ){ 519 | this.activePlane = this.planes[ "XZ" ]; 520 | if ( Math.abs(eye.x) > Math.abs(eye.y) ) this.activePlane = this.planes[ "YZ" ]; 521 | } 522 | 523 | if ( axis == "XYZ" ) this.activePlane = this.planes[ "XYZE" ]; 524 | 525 | this.hide(); 526 | this.show(); 527 | 528 | }; 529 | 530 | this.init(); 531 | 532 | }; 533 | 534 | THREE.TransformGizmoScale.prototype = Object.create( THREE.TransformGizmo.prototype ); 535 | 536 | THREE.TransformControls = function ( camera, domElement ) { 537 | 538 | // TODO: Make non-uniform scale and rotate play nice in hierarchies 539 | // TODO: ADD RXYZ contol 540 | 541 | THREE.Object3D.call( this ); 542 | 543 | domElement = ( domElement !== undefined ) ? domElement : document; 544 | 545 | this.gizmo = {}; 546 | this.gizmo["translate"] = new THREE.TransformGizmoTranslate(); 547 | this.gizmo["rotate"] = new THREE.TransformGizmoRotate(); 548 | this.gizmo["scale"] = new THREE.TransformGizmoScale(); 549 | 550 | this.add(this.gizmo["translate"]); 551 | this.add(this.gizmo["rotate"]); 552 | this.add(this.gizmo["scale"]); 553 | 554 | this.gizmo["translate"].hide(); 555 | this.gizmo["rotate"].hide(); 556 | this.gizmo["scale"].hide(); 557 | 558 | this.object = undefined; 559 | this.snap = null; 560 | this.space = "world"; 561 | this.size = 1; 562 | this.axis = null; 563 | 564 | var scope = this; 565 | 566 | var _dragging = false; 567 | var _mode = "translate"; 568 | var _plane = "XY"; 569 | 570 | var changeEvent = { type: "change" }; 571 | var mouseDownEvent = { type: "mouseDown" }; 572 | var mouseUpEvent = { type: "mouseUp", mode: _mode }; 573 | var objectChangeEvent = { type: "objectChange" }; 574 | 575 | var ray = new THREE.Raycaster(); 576 | var pointerVector = new THREE.Vector3(); 577 | 578 | var point = new THREE.Vector3(); 579 | var offset = new THREE.Vector3(); 580 | 581 | var rotation = new THREE.Vector3(); 582 | var offsetRotation = new THREE.Vector3(); 583 | var scale = 1; 584 | 585 | var lookAtMatrix = new THREE.Matrix4(); 586 | var eye = new THREE.Vector3(); 587 | 588 | var tempMatrix = new THREE.Matrix4(); 589 | var tempVector = new THREE.Vector3(); 590 | var tempQuaternion = new THREE.Quaternion(); 591 | var unitX = new THREE.Vector3( 1, 0, 0 ); 592 | var unitY = new THREE.Vector3( 0, 1, 0 ); 593 | var unitZ = new THREE.Vector3( 0, 0, 1 ); 594 | 595 | var quaternionXYZ = new THREE.Quaternion(); 596 | var quaternionX = new THREE.Quaternion(); 597 | var quaternionY = new THREE.Quaternion(); 598 | var quaternionZ = new THREE.Quaternion(); 599 | var quaternionE = new THREE.Quaternion(); 600 | 601 | var oldPosition = new THREE.Vector3(); 602 | var oldScale = new THREE.Vector3(); 603 | var oldRotationMatrix = new THREE.Matrix4(); 604 | 605 | var parentRotationMatrix = new THREE.Matrix4(); 606 | var parentScale = new THREE.Vector3(); 607 | 608 | var worldPosition = new THREE.Vector3(); 609 | var worldRotation = new THREE.Euler(); 610 | var worldRotationMatrix = new THREE.Matrix4(); 611 | var camPosition = new THREE.Vector3(); 612 | var camRotation = new THREE.Euler(); 613 | 614 | domElement.addEventListener( "mousedown", onPointerDown, false ); 615 | domElement.addEventListener( "touchstart", onPointerDown, false ); 616 | 617 | domElement.addEventListener( "mousemove", onPointerHover, false ); 618 | domElement.addEventListener( "touchmove", onPointerHover, false ); 619 | 620 | domElement.addEventListener( "mousemove", onPointerMove, false ); 621 | domElement.addEventListener( "touchmove", onPointerMove, false ); 622 | 623 | domElement.addEventListener( "mouseup", onPointerUp, false ); 624 | domElement.addEventListener( "mouseout", onPointerUp, false ); 625 | domElement.addEventListener( "touchend", onPointerUp, false ); 626 | domElement.addEventListener( "touchcancel", onPointerUp, false ); 627 | domElement.addEventListener( "touchleave", onPointerUp, false ); 628 | 629 | this.attach = function ( object ) { 630 | 631 | scope.object = object; 632 | 633 | this.gizmo["translate"].hide(); 634 | this.gizmo["rotate"].hide(); 635 | this.gizmo["scale"].hide(); 636 | this.gizmo[_mode].show(); 637 | 638 | scope.update(); 639 | 640 | }; 641 | 642 | this.detach = function ( object ) { 643 | 644 | scope.object = undefined; 645 | this.axis = null; 646 | 647 | this.gizmo["translate"].hide(); 648 | this.gizmo["rotate"].hide(); 649 | this.gizmo["scale"].hide(); 650 | 651 | }; 652 | 653 | this.setMode = function ( mode ) { 654 | 655 | _mode = mode ? mode : _mode; 656 | 657 | if ( _mode == "scale" ) scope.space = "local"; 658 | 659 | this.gizmo["translate"].hide(); 660 | this.gizmo["rotate"].hide(); 661 | this.gizmo["scale"].hide(); 662 | this.gizmo[_mode].show(); 663 | 664 | this.update(); 665 | scope.dispatchEvent( changeEvent ); 666 | 667 | }; 668 | 669 | this.setSnap = function ( snap ) { 670 | 671 | scope.snap = snap; 672 | 673 | }; 674 | 675 | this.setSize = function ( size ) { 676 | 677 | scope.size = size; 678 | this.update(); 679 | scope.dispatchEvent( changeEvent ); 680 | 681 | }; 682 | 683 | this.setSpace = function ( space ) { 684 | 685 | scope.space = space; 686 | this.update(); 687 | scope.dispatchEvent( changeEvent ); 688 | 689 | }; 690 | 691 | this.update = function () { 692 | 693 | if ( scope.object === undefined ) return; 694 | 695 | scope.object.updateMatrixWorld(); 696 | worldPosition.setFromMatrixPosition( scope.object.matrixWorld ); 697 | worldRotation.setFromRotationMatrix( tempMatrix.extractRotation( scope.object.matrixWorld ) ); 698 | 699 | camera.updateMatrixWorld(); 700 | camPosition.setFromMatrixPosition( camera.matrixWorld ); 701 | camRotation.setFromRotationMatrix( tempMatrix.extractRotation( camera.matrixWorld ) ); 702 | 703 | scale = worldPosition.distanceTo( camPosition ) / 6 * scope.size; 704 | this.position.copy( worldPosition ); 705 | this.scale.set( scale, scale, scale ); 706 | 707 | eye.copy( camPosition ).sub( worldPosition ).normalize(); 708 | 709 | if ( scope.space == "local" ) 710 | this.gizmo[_mode].update( worldRotation, eye ); 711 | 712 | else if ( scope.space == "world" ) 713 | this.gizmo[_mode].update( new THREE.Euler(), eye ); 714 | 715 | this.gizmo[_mode].highlight( scope.axis ); 716 | 717 | }; 718 | 719 | function onPointerHover( event ) { 720 | 721 | if ( scope.object === undefined || _dragging === true ) return; 722 | 723 | event.preventDefault(); 724 | 725 | var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; 726 | 727 | var intersect = intersectObjects( pointer, scope.gizmo[_mode].pickers.children ); 728 | 729 | var axis = null; 730 | 731 | if ( intersect ) { 732 | 733 | axis = intersect.object.name; 734 | 735 | } 736 | 737 | if ( scope.axis !== axis ) { 738 | 739 | scope.axis = axis; 740 | scope.update(); 741 | scope.dispatchEvent( changeEvent ); 742 | 743 | } 744 | 745 | } 746 | 747 | function onPointerDown( event ) { 748 | 749 | if ( scope.object === undefined || _dragging === true ) return; 750 | 751 | event.preventDefault(); 752 | event.stopPropagation(); 753 | 754 | var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; 755 | 756 | if ( pointer.button === 0 || pointer.button === undefined ) { 757 | 758 | var intersect = intersectObjects( pointer, scope.gizmo[_mode].pickers.children ); 759 | 760 | if ( intersect ) { 761 | 762 | scope.dispatchEvent( mouseDownEvent ); 763 | 764 | scope.axis = intersect.object.name; 765 | 766 | scope.update(); 767 | 768 | eye.copy( camPosition ).sub( worldPosition ).normalize(); 769 | 770 | scope.gizmo[_mode].setActivePlane( scope.axis, eye ); 771 | 772 | var planeIntersect = intersectObjects( pointer, [scope.gizmo[_mode].activePlane] ); 773 | 774 | oldPosition.copy( scope.object.position ); 775 | oldScale.copy( scope.object.scale ); 776 | 777 | oldRotationMatrix.extractRotation( scope.object.matrix ); 778 | worldRotationMatrix.extractRotation( scope.object.matrixWorld ); 779 | 780 | parentRotationMatrix.extractRotation( scope.object.parent.matrixWorld ); 781 | parentScale.setFromMatrixScale( tempMatrix.getInverse( scope.object.parent.matrixWorld ) ); 782 | 783 | offset.copy( planeIntersect.point ); 784 | 785 | } 786 | 787 | } 788 | 789 | _dragging = true; 790 | 791 | } 792 | 793 | function onPointerMove( event ) { 794 | 795 | if ( scope.object === undefined || scope.axis === null || _dragging === false ) return; 796 | 797 | event.preventDefault(); 798 | event.stopPropagation(); 799 | 800 | var pointer = event.changedTouches? event.changedTouches[0] : event; 801 | 802 | var planeIntersect = intersectObjects( pointer, [scope.gizmo[_mode].activePlane] ); 803 | 804 | point.copy( planeIntersect.point ); 805 | 806 | if ( _mode == "translate" ) { 807 | 808 | point.sub( offset ); 809 | point.multiply(parentScale); 810 | 811 | if ( scope.space == "local" ) { 812 | 813 | point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 814 | 815 | if ( scope.axis.search("X") == -1 ) point.x = 0; 816 | if ( scope.axis.search("Y") == -1 ) point.y = 0; 817 | if ( scope.axis.search("Z") == -1 ) point.z = 0; 818 | 819 | point.applyMatrix4( oldRotationMatrix ); 820 | 821 | scope.object.position.copy( oldPosition ); 822 | scope.object.position.add( point ); 823 | 824 | } 825 | 826 | if ( scope.space == "world" || scope.axis.search("XYZ") != -1 ) { 827 | 828 | if ( scope.axis.search("X") == -1 ) point.x = 0; 829 | if ( scope.axis.search("Y") == -1 ) point.y = 0; 830 | if ( scope.axis.search("Z") == -1 ) point.z = 0; 831 | 832 | point.applyMatrix4( tempMatrix.getInverse( parentRotationMatrix ) ); 833 | 834 | scope.object.position.copy( oldPosition ); 835 | scope.object.position.add( point ); 836 | 837 | } 838 | 839 | if ( scope.snap !== null ) { 840 | 841 | if ( scope.axis.search("X") != -1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.snap ) * scope.snap; 842 | if ( scope.axis.search("Y") != -1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.snap ) * scope.snap; 843 | if ( scope.axis.search("Z") != -1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.snap ) * scope.snap; 844 | 845 | } 846 | 847 | } else if ( _mode == "scale" ) { 848 | 849 | point.sub( offset ); 850 | point.multiply(parentScale); 851 | 852 | if ( scope.space == "local" ) { 853 | 854 | if ( scope.axis == "XYZ") { 855 | 856 | scale = 1 + ( ( point.y ) / 50 ); 857 | 858 | scope.object.scale.x = oldScale.x * scale; 859 | scope.object.scale.y = oldScale.y * scale; 860 | scope.object.scale.z = oldScale.z * scale; 861 | 862 | } else { 863 | 864 | point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 865 | 866 | if ( scope.axis == "X" ) scope.object.scale.x = oldScale.x * ( 1 + point.x / 50 ); 867 | if ( scope.axis == "Y" ) scope.object.scale.y = oldScale.y * ( 1 + point.y / 50 ); 868 | if ( scope.axis == "Z" ) scope.object.scale.z = oldScale.z * ( 1 + point.z / 50 ); 869 | 870 | } 871 | 872 | } 873 | 874 | } else if ( _mode == "rotate" ) { 875 | 876 | point.sub( worldPosition ); 877 | point.multiply(parentScale); 878 | tempVector.copy(offset).sub( worldPosition ); 879 | tempVector.multiply(parentScale); 880 | 881 | if ( scope.axis == "E" ) { 882 | 883 | point.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) ); 884 | tempVector.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) ); 885 | 886 | rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); 887 | offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); 888 | 889 | tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); 890 | 891 | quaternionE.setFromAxisAngle( eye, rotation.z - offsetRotation.z ); 892 | quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); 893 | 894 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionE ); 895 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); 896 | 897 | scope.object.quaternion.copy( tempQuaternion ); 898 | 899 | } else if ( scope.axis == "XYZE" ) { 900 | 901 | quaternionE.setFromEuler( point.clone().cross(tempVector).normalize() ); // rotation axis 902 | 903 | tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); 904 | quaternionX.setFromAxisAngle( quaternionE, - point.clone().angleTo(tempVector) ); 905 | quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); 906 | 907 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); 908 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); 909 | 910 | scope.object.quaternion.copy( tempQuaternion ); 911 | 912 | } else if ( scope.space == "local" ) { 913 | 914 | point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 915 | 916 | tempVector.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); 917 | 918 | rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); 919 | offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); 920 | 921 | quaternionXYZ.setFromRotationMatrix( oldRotationMatrix ); 922 | quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x ); 923 | quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y ); 924 | quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z ); 925 | 926 | if ( scope.axis == "X" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionX ); 927 | if ( scope.axis == "Y" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionY ); 928 | if ( scope.axis == "Z" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionZ ); 929 | 930 | scope.object.quaternion.copy( quaternionXYZ ); 931 | 932 | } else if ( scope.space == "world" ) { 933 | 934 | rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); 935 | offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); 936 | 937 | tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); 938 | 939 | quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x ); 940 | quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y ); 941 | quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z ); 942 | quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); 943 | 944 | if ( scope.axis == "X" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); 945 | if ( scope.axis == "Y" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY ); 946 | if ( scope.axis == "Z" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ ); 947 | 948 | tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); 949 | 950 | scope.object.quaternion.copy( tempQuaternion ); 951 | 952 | } 953 | 954 | } 955 | 956 | scope.update(); 957 | scope.dispatchEvent( changeEvent ); 958 | scope.dispatchEvent( objectChangeEvent ); 959 | 960 | } 961 | 962 | function onPointerUp( event ) { 963 | 964 | if ( _dragging && ( scope.axis !== null ) ) { 965 | mouseUpEvent.mode = _mode; 966 | scope.dispatchEvent( mouseUpEvent ) 967 | } 968 | _dragging = false; 969 | onPointerHover( event ); 970 | 971 | } 972 | 973 | function intersectObjects( pointer, objects ) { 974 | var coords = THREE.CodeEditor.raycasting.getRelativeMouseXYFromEvent(pointer); 975 | 976 | pointerVector.copy(coords); 977 | pointerVector.unproject( camera ); 978 | 979 | ray.set( camPosition, pointerVector.sub( camPosition ).normalize() ); 980 | 981 | var intersections = ray.intersectObjects( objects, true ); 982 | return intersections[0] ? intersections[0] : false; 983 | 984 | } 985 | 986 | }; 987 | 988 | THREE.TransformControls.prototype = Object.create( THREE.Object3D.prototype ); 989 | 990 | }()); 991 | -------------------------------------------------------------------------------- /vendor/three/VRControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | */ 5 | 6 | THREE.VRControls = function ( object, callback ) { 7 | 8 | var vrInput; 9 | 10 | var onVRDevices = function ( devices ) { 11 | 12 | for ( var i = 0; i < devices.length; i ++ ) { 13 | 14 | var device = devices[ i ]; 15 | 16 | if ( device instanceof PositionSensorVRDevice ) { 17 | 18 | vrInput = devices[ i ]; 19 | return; // We keep the first we encounter 20 | 21 | } 22 | 23 | } 24 | 25 | if ( callback !== undefined ) { 26 | 27 | callback( 'HMD not available' ); 28 | 29 | } 30 | 31 | }; 32 | 33 | if ( navigator.getVRDevices !== undefined ) { 34 | 35 | navigator.getVRDevices().then( onVRDevices ); 36 | 37 | } else if ( callback !== undefined ) { 38 | 39 | callback( 'Your browser is not VR Ready' ); 40 | 41 | } 42 | 43 | this.update = function () { 44 | 45 | if ( vrInput === undefined ) return; 46 | 47 | var state = vrInput.getState(); 48 | 49 | if ( state.orientation !== null ) { 50 | 51 | object.quaternion.copy( state.orientation ); 52 | 53 | } 54 | 55 | if ( state.position !== null ) { 56 | 57 | object.position.copy( state.position ); 58 | 59 | } 60 | 61 | }; 62 | 63 | this.zeroSensor = function () { 64 | 65 | if ( vrInput === undefined ) return; 66 | 67 | vrInput.zeroSensor(); 68 | 69 | }; 70 | 71 | }; 72 | -------------------------------------------------------------------------------- /vendor/three/VREffect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * 4 | * It handles stereo rendering 5 | * If mozGetVRDevices and getVRDevices APIs are not available it gracefuly falls back to a 6 | * regular renderer 7 | * 8 | * The HMD supported is the Oculus DK1 and The Web API doesn't currently allow 9 | * to query for the display resolution (only the chrome API allows it). 10 | * The dimensions of the screen are temporarly hardcoded (1280 x 800). 11 | * 12 | * For VR mode to work it has to be used with the Oculus enabled builds of Firefox or Chrome: 13 | * 14 | * Firefox: 15 | * 16 | * OSX: http://people.mozilla.com/~vladimir/vr/firefox-33.0a1.en-US.mac.dmg 17 | * WIN: http://people.mozilla.com/~vladimir/vr/firefox-33.0a1.en-US.win64-x86_64.zip 18 | * 19 | * Chrome builds: 20 | * 21 | * https://drive.google.com/folderview?id=0BzudLt22BqGRbW9WTHMtOWMzNjQ&usp=sharing#list 22 | * 23 | */ 24 | THREE.VREffect = function ( renderer, done ) { 25 | 26 | var cameraLeft = new THREE.PerspectiveCamera(); 27 | var cameraRight = new THREE.PerspectiveCamera(); 28 | 29 | this._renderer = renderer; 30 | 31 | this._init = function() { 32 | var self = this; 33 | if ( !navigator.mozGetVRDevices && !navigator.getVRDevices ) { 34 | if ( done ) { 35 | done("Your browser is not VR Ready"); 36 | } 37 | return; 38 | } 39 | if ( navigator.getVRDevices ) { 40 | navigator.getVRDevices().then( gotVRDevices ); 41 | } else { 42 | navigator.mozGetVRDevices( gotVRDevices ); 43 | } 44 | function gotVRDevices( devices ) { 45 | var vrHMD; 46 | var error; 47 | for ( var i = 0; i < devices.length; ++i ) { 48 | if ( devices[i] instanceof HMDVRDevice ) { 49 | vrHMD = devices[i]; 50 | self._vrHMD = vrHMD; 51 | self.leftEyeTranslation = vrHMD.getEyeTranslation( "left" ); 52 | self.rightEyeTranslation = vrHMD.getEyeTranslation( "right" ); 53 | self.leftEyeFOV = vrHMD.getRecommendedEyeFieldOfView( "left" ); 54 | self.rightEyeFOV = vrHMD.getRecommendedEyeFieldOfView( "right" ); 55 | break; // We keep the first we encounter 56 | } 57 | } 58 | if ( done ) { 59 | if ( !vrHMD ) { 60 | error = 'HMD not available'; 61 | } 62 | done( error ); 63 | } 64 | } 65 | }; 66 | 67 | this._init(); 68 | 69 | this.render = function ( scene, camera ) { 70 | var renderer = this._renderer; 71 | var vrHMD = this._vrHMD; 72 | // VR render mode if HMD is available 73 | if ( vrHMD ) { 74 | this.renderStereo.apply( this, arguments ); 75 | return; 76 | } 77 | // Regular render mode if not HMD 78 | renderer.render.apply( this._renderer, arguments ); 79 | }; 80 | 81 | this.renderStereo = function( scene, camera, renderTarget, forceClear ) { 82 | 83 | var leftEyeTranslation = this.leftEyeTranslation; 84 | var rightEyeTranslation = this.rightEyeTranslation; 85 | var renderer = this._renderer; 86 | var rendererWidth = renderer.domElement.width / renderer.devicePixelRatio; 87 | var rendererHeight = renderer.domElement.height / renderer.devicePixelRatio; 88 | var eyeDivisionLine = rendererWidth / 2; 89 | 90 | renderer.enableScissorTest( true ); 91 | renderer.clear(); 92 | 93 | if ( camera.parent === undefined ) { 94 | camera.updateMatrixWorld(); 95 | } 96 | 97 | cameraLeft.projectionMatrix = this.FovToProjection( this.leftEyeFOV, true, camera.near, camera.far ); 98 | cameraRight.projectionMatrix = this.FovToProjection( this.rightEyeFOV, true, camera.near, camera.far ); 99 | 100 | camera.matrixWorld.decompose( cameraLeft.position, cameraLeft.quaternion, cameraLeft.scale ); 101 | camera.matrixWorld.decompose( cameraRight.position, cameraRight.quaternion, cameraRight.scale ); 102 | 103 | cameraLeft.translateX( leftEyeTranslation.x ); 104 | cameraRight.translateX( rightEyeTranslation.x ); 105 | 106 | // render left eye 107 | renderer.setViewport( 0, 0, eyeDivisionLine, rendererHeight ); 108 | renderer.setScissor( 0, 0, eyeDivisionLine, rendererHeight ); 109 | renderer.render( scene, cameraLeft ); 110 | 111 | // render right eye 112 | renderer.setViewport( eyeDivisionLine, 0, eyeDivisionLine, rendererHeight ); 113 | renderer.setScissor( eyeDivisionLine, 0, eyeDivisionLine, rendererHeight ); 114 | renderer.render( scene, cameraRight ); 115 | 116 | renderer.enableScissorTest( false ); 117 | 118 | }; 119 | 120 | this.setSize = function( width, height ) { 121 | renderer.setSize( width, height ); 122 | }; 123 | 124 | this.setFullScreen = function( enable ) { 125 | var renderer = this._renderer; 126 | var vrHMD = this._vrHMD; 127 | var canvasOriginalSize = this._canvasOriginalSize; 128 | if (!vrHMD) { 129 | return; 130 | } 131 | // If state doesn't change we do nothing 132 | if ( enable === this._fullScreen ) { 133 | return; 134 | } 135 | this._fullScreen = !!enable; 136 | 137 | // VR Mode disabled 138 | if ( !enable ) { 139 | // Restores canvas original size 140 | renderer.setSize( canvasOriginalSize.width, canvasOriginalSize.height ); 141 | return; 142 | } 143 | // VR Mode enabled 144 | this._canvasOriginalSize = { 145 | width: renderer.domElement.width, 146 | height: renderer.domElement.height 147 | }; 148 | // Hardcoded Rift display size 149 | renderer.setSize( 1280, 800, false ); 150 | this.startFullscreen(); 151 | }; 152 | 153 | this.startFullscreen = function() { 154 | var self = this; 155 | var renderer = this._renderer; 156 | var vrHMD = this._vrHMD; 157 | var canvas = renderer.domElement; 158 | var fullScreenChange = 159 | canvas.mozRequestFullScreen? 'mozfullscreenchange' : 'webkitfullscreenchange'; 160 | 161 | document.addEventListener( fullScreenChange, onFullScreenChanged, false ); 162 | function onFullScreenChanged() { 163 | if ( !document.mozFullScreenElement && !document.webkitFullScreenElement ) { 164 | self.setFullScreen( false ); 165 | } 166 | } 167 | if ( canvas.mozRequestFullScreen ) { 168 | canvas.mozRequestFullScreen( { vrDisplay: vrHMD } ); 169 | } else { 170 | canvas.webkitRequestFullscreen( { vrDisplay: vrHMD } ); 171 | } 172 | }; 173 | 174 | this.FovToNDCScaleOffset = function( fov ) { 175 | var pxscale = 2.0 / (fov.leftTan + fov.rightTan); 176 | var pxoffset = (fov.leftTan - fov.rightTan) * pxscale * 0.5; 177 | var pyscale = 2.0 / (fov.upTan + fov.downTan); 178 | var pyoffset = (fov.upTan - fov.downTan) * pyscale * 0.5; 179 | return { scale: [pxscale, pyscale], offset: [pxoffset, pyoffset] }; 180 | }; 181 | 182 | this.FovPortToProjection = function( fov, rightHanded /* = true */, zNear /* = 0.01 */, zFar /* = 10000.0 */ ) 183 | { 184 | rightHanded = rightHanded === undefined ? true : rightHanded; 185 | zNear = zNear === undefined ? 0.01 : zNear; 186 | zFar = zFar === undefined ? 10000.0 : zFar; 187 | 188 | var handednessScale = rightHanded ? -1.0 : 1.0; 189 | 190 | // start with an identity matrix 191 | var mobj = new THREE.Matrix4(); 192 | var m = mobj.elements; 193 | 194 | // and with scale/offset info for normalized device coords 195 | var scaleAndOffset = this.FovToNDCScaleOffset(fov); 196 | 197 | // X result, map clip edges to [-w,+w] 198 | m[0*4+0] = scaleAndOffset.scale[0]; 199 | m[0*4+1] = 0.0; 200 | m[0*4+2] = scaleAndOffset.offset[0] * handednessScale; 201 | m[0*4+3] = 0.0; 202 | 203 | // Y result, map clip edges to [-w,+w] 204 | // Y offset is negated because this proj matrix transforms from world coords with Y=up, 205 | // but the NDC scaling has Y=down (thanks D3D?) 206 | m[1*4+0] = 0.0; 207 | m[1*4+1] = scaleAndOffset.scale[1]; 208 | m[1*4+2] = -scaleAndOffset.offset[1] * handednessScale; 209 | m[1*4+3] = 0.0; 210 | 211 | // Z result (up to the app) 212 | m[2*4+0] = 0.0; 213 | m[2*4+1] = 0.0; 214 | m[2*4+2] = zFar / (zNear - zFar) * -handednessScale; 215 | m[2*4+3] = (zFar * zNear) / (zNear - zFar); 216 | 217 | // W result (= Z in) 218 | m[3*4+0] = 0.0; 219 | m[3*4+1] = 0.0; 220 | m[3*4+2] = handednessScale; 221 | m[3*4+3] = 0.0; 222 | 223 | mobj.transpose(); 224 | 225 | return mobj; 226 | }; 227 | 228 | this.FovToProjection = function( fov, rightHanded /* = true */, zNear /* = 0.01 */, zFar /* = 10000.0 */ ) 229 | { 230 | var fovPort = { 231 | upTan: Math.tan(fov.upDegrees * Math.PI / 180.0), 232 | downTan: Math.tan(fov.downDegrees * Math.PI / 180.0), 233 | leftTan: Math.tan(fov.leftDegrees * Math.PI / 180.0), 234 | rightTan: Math.tan(fov.rightDegrees * Math.PI / 180.0) 235 | }; 236 | return this.FovPortToProjection(fovPort, rightHanded, zNear, zFar); 237 | }; 238 | 239 | }; 240 | --------------------------------------------------------------------------------