├── LICENSE.txt ├── README.md └── engineEditorCamera.js /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Hazani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EngineEditorCamera 2 | ## A game engine editor-type camera for easy traversal through [three.js](https://threejs.org) scenes 3 | 4 | [LIVE DEMO](https://nevr.to/02) 5 | 6 | Featuring: 7 | * smooth scene traversal - right click + W/A/S/D/Q/E to get around 8 | 9 | * right click + **shift** + WASDEQ to go faster 10 | 11 | * mouse scroll wheel up/down to increase/decrease camera movement speed 12 | 13 | * session persistence: location, rotation and movement speed persist over refreshes (extra convenient for hot reloading) 14 | 15 | 16 | ## Instructions: 17 | * add `engineEditorCamera.js` to a threeJS app 18 | * in your main app JS file (or wherever you want, long as you're fine refrencing your camera and renderer there): 19 | 20 | ```js 21 | // app.js 22 | 23 | import * as THREE from "three" 24 | import EngineEditorCamera from "PATH/TO/engineEditorCamera"] 25 | 26 | // (set up your renderer and camera, for example:) 27 | 28 | const camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 ); 29 | 30 | const renderer = new THREE.WebGLRenderer(); 31 | renderer.setSize( window.innerWidth, window.innerHeight ); 32 | document.body.appendChild( renderer.domElement ); 33 | 34 | // create a new EngineEditorCamera with camera and your renderer's DOM element, like so: 35 | const engineEditorCamera = new EngineEditorCamera(camera, renderer.domElement) 36 | 37 | 38 | // in your update loop, call the .update() method, for example: 39 | 40 | const animate = function () { 41 | requestAnimationFrame( animate ); 42 | engineEditorCamera.update(); 43 | renderer.render( scene, camera ); 44 | }; 45 | 46 | animate(); 47 | 48 | ``` 49 | 50 | And that's it. Happy scene traversal! 51 | 52 | -------------------------------------------------------------------------------- /engineEditorCamera.js: -------------------------------------------------------------------------------- 1 | // a game engine-like editor camera for ThreeJS 2 | 3 | import { Vector3, Euler } from "three" 4 | 5 | export default class EngineEditorCamera 6 | { 7 | constructor(camera, domElement) 8 | { 9 | this.camera = camera; 10 | this.domElement = domElement; 11 | this.PI_2 = Math.PI / 2; 12 | this.euler = new Euler(0, 0, 0, 'YXZ'); 13 | this.vec = new Vector3(); 14 | this.cameraForward = new Vector3(); 15 | this._rcPressed = false; 16 | this.pressedKeyMap = { 17 | 87: false, // w 18 | 65: false, // a 19 | 83: false, // s 20 | 68: false, // d 21 | 81: false, // q 22 | 69: false, // e 23 | 16: false // shift 24 | }; 25 | 26 | window.addEventListener('keydown', this.onKeyDown.bind(this), false); 27 | window.addEventListener('keyup', this.onKeyUp.bind(this), false); 28 | window.addEventListener('mousedown', this.onMouseDown.bind(this), false); 29 | window.addEventListener('mouseup', this.onMouseUp.bind(this), false); 30 | window.addEventListener('mousemove', this.onMouseMove.bind(this), false); 31 | window.addEventListener('wheel', this.onMouseWheel.bind(this), false); 32 | 33 | //disable rClick 34 | document.oncontextmenu = (e) => 35 | { 36 | if (e.preventDefault != undefined) e.preventDefault(); 37 | if (e.stopPropagation != undefined) e.stopPropagation(); 38 | } 39 | this.retrieveSessionData(); 40 | } 41 | 42 | setSessionData() 43 | { 44 | 45 | this.editorCamState = { 46 | camSpeed: this.CAM_SPEED, 47 | cameraPosition: this.camera.position, 48 | cameraQuaternion: this.camera.quaternion 49 | } 50 | window.localStorage.setItem('camState', JSON.stringify(this.editorCamState)); 51 | // console.log("saving editor camera session data"); 52 | } 53 | 54 | retrieveSessionData() 55 | { 56 | const camState = JSON.parse(window.localStorage.getItem('camState')); 57 | if (camState == null) 58 | { 59 | this.CAM_SPEED = 0.05; 60 | this.setSessionData(); 61 | return; 62 | } 63 | 64 | this.CAM_SPEED = "camSpeed" in camState ? camState["camSpeed"] : 0.05; 65 | this.camera.position.copy("cameraPosition" in camState ? camState["cameraPosition"] : new Vector3()); 66 | this.camera.applyQuaternion("cameraQuaternion" in camState ? camState["cameraQuaternion"] : new Vector3()); 67 | // console.log("loading editor camera session data"); 68 | } 69 | 70 | update() 71 | { 72 | this.shiftSpeedMulti = this.pressedKeyMap[16] ? 2 : 1; 73 | if (this.pressedKeyMap[87]) this.moveForward(this.CAM_SPEED * this.shiftSpeedMulti); 74 | if (this.pressedKeyMap[83]) this.moveForward(-this.CAM_SPEED * this.shiftSpeedMulti); 75 | if (this.pressedKeyMap[69]) this.moveUp(this.CAM_SPEED * this.shiftSpeedMulti); 76 | if (this.pressedKeyMap[81]) this.moveUp(-this.CAM_SPEED * this.shiftSpeedMulti); 77 | if (this.pressedKeyMap[68]) this.moveRight(this.CAM_SPEED * this.shiftSpeedMulti); 78 | if (this.pressedKeyMap[65]) this.moveRight(-this.CAM_SPEED * this.shiftSpeedMulti); 79 | } 80 | 81 | onKeyDown(event) 82 | { 83 | this.pressedKeyMap[event.keyCode] = (event.keyCode in this.pressedKeyMap); 84 | }; 85 | onKeyUp(event) { this.pressedKeyMap[event.keyCode] = !(event.keyCode in this.pressedKeyMap); }; 86 | 87 | onMouseDown(event) 88 | { 89 | if (event.button == 2) 90 | { 91 | this._rcPressed = true; 92 | this.domElement.requestPointerLock = this.domElement.requestPointerLock || 93 | domElement.mozRequestPointerLock; 94 | this.domElement.requestPointerLock() 95 | } 96 | } 97 | 98 | onMouseUp(event) 99 | { 100 | if (event.button == 2) 101 | { 102 | this._rcPressed = false; 103 | document.exitPointerLock(); 104 | this.setSessionData(); 105 | } 106 | } 107 | 108 | moveForward(distance) 109 | { 110 | this.camera.getWorldDirection(this.cameraForward); 111 | this.camera.position.addScaledVector(this.cameraForward, distance); 112 | } 113 | 114 | moveRight(distance) 115 | { 116 | this.vec.setFromMatrixColumn(this.camera.matrix, 0); 117 | this.camera.position.addScaledVector(this.vec, distance); 118 | } 119 | 120 | moveUp(distance) 121 | { 122 | this.camera.position.addScaledVector(this.camera.up, distance); 123 | } 124 | 125 | onMouseMove(event) 126 | { 127 | if (!this._rcPressed) return; 128 | const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; 129 | const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; 130 | this.euler.setFromQuaternion(this.camera.quaternion); 131 | this.euler.y -= movementX * 0.002; 132 | this.euler.x -= movementY * 0.002; 133 | this.euler.x = Math.max(- this.PI_2, Math.min(this.PI_2, this.euler.x)); 134 | this.camera.quaternion.setFromEuler(this.euler); 135 | } 136 | 137 | onMouseWheel(event) 138 | { 139 | if (this._rcPressed) this.CAM_SPEED = Math.max(0.01, this.CAM_SPEED -= event.deltaY * 0.0001); 140 | else this.moveForward(this.CAM_SPEED * -event.deltaY / 10); 141 | } 142 | } --------------------------------------------------------------------------------