├── .gitignore ├── LICENSE ├── README.md └── camera.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules/* 16 | *.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Mikola Lysenko 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 3d-camera 2 | ========= 3 | An easy-to-use 3D camera for real-time interactive graphical applications. 4 | 5 | # Example 6 | 7 | ```javascript 8 | var createCamera = require('3d-camera') 9 | 10 | ``` 11 | 12 | # Install 13 | 14 | ``` 15 | npm i 3d-camera 16 | ``` 17 | 18 | # API 19 | 20 | ## Constructor 21 | 22 | #### `var camera = require('3d-camera')([options])` 23 | Creates a new 3D camera object. `options` is an optional JSON object which determines the initial values for the following properties: 24 | 25 | * `options.width` 26 | * `options.height` 27 | * `options.zNear` 28 | * `options.zFar` 29 | * `options.delay` 30 | * `options.fovX` 31 | * `options.fovY` 32 | * ... TODO 33 | 34 | **Returns** A new 3D camera object 35 | 36 | ## Basic interface 37 | 38 | Updates to the camera state are synchronized at calls to `tick()`. This is necessary to ensure that camera state is smoothly interpolated between successive updates. 39 | 40 | #### `camera.tick()` 41 | **This must be called at the start of each frame**. It applies any pending updates to the camera and performs interpolation between previous camera motions. 42 | 43 | **Returns** `true` if the state of the camera changed since the last call to `tick()`, `false` if the camera is at a steady state. 44 | 45 | #### `camera.width` 46 | This is the width of the view port which is being rendered. 47 | 48 | #### `camera.height` 49 | This is the height of the view port which is being rendered. 50 | 51 | #### `camera.shape` 52 | As an alternative to directly accessing the width/height properties, it is also possible to access the shape of the camera viewport directly as a vector. For some applications this may be more convenient. 53 | 54 | #### `camera.delay` 55 | The amount of delay to apply to the camera when interpolating between previous states. This delay is applied in millisecond and is based on the wall clock time. No delay means that camera updates are not smoothed. 56 | 57 | ## Coordinate transformations 58 | 59 | The main function of the camera is to keep track of coordinate transformations. By convention, in computer graphics we define 4 coordinate systems: 60 | 61 | * **Data coordinates**: Which are the coordinates where the geometry to be rendered is defined 62 | * **World coordinates**: Which is a common coordinate system in which all objects are rendered 63 | * **Camera coordinates**: A coordinate system in the world with the camera at the center 64 | * **Clip coordinates**: The normalized device coordinates 65 | 66 | These coordinate transformations are generated by 3 different matrices which transform each coordinate system to the next: 67 | 68 | * **Model matrix**: *Data* -> *World* 69 | * **View matrix**: *World* -> *Camera* 70 | * **Projection matrix**: *Camera* -> *Clip* 71 | 72 | Coordinate transformation updates are synchronized on calls to `tick()`, so any changes to these matrices have to wait until the next call to `tick()` before they become visible. 73 | 74 | ### Conventions on coordinate systems 75 | 76 | In WebGL and in this module, we use the following conventions for the coordinate axes for camera/clip coordinates: 77 | 78 | * **x-axis**: negative = left, positive = right 79 | * **y-axis**: negative = down, positive = up 80 | * **z-axis**: negative = backward (away from screen), positive = forward (toward screen) 81 | 82 | It is good practice to use `y` for "up" in world coordinates, though this is an application specific consideration. 83 | 84 | ### Compound transformations 85 | 86 | For convenience, the camera object provides a number of methods for getting transformations between the standard coordinate systems: 87 | 88 | ```javascript 89 | camera.data.toClip // a 4x4 homogeneous transformation matrix mapping 90 | // data coordinates to clip coordinates 91 | 92 | camera.clip.toWorld // a transformation mapping clip coordinates to world 93 | ``` 94 | 95 | The general pattern of these transformations is: 96 | 97 | ```javascript 98 | camera[SOURCE]['to' + TARGET] // transform from SOURCE to TARGET coords 99 | ``` 100 | 101 | Possible coordinate systems include `data`, `world`, `camera` and `clip`. 102 | 103 | The origin of the camera coordinates in any system can be found by accessing the `.origin` property of the camera, for example: 104 | 105 | ```javascript 106 | camera.world.origin // the origin of the camera in world coordinates 107 | ``` 108 | 109 | The state of these transformations is computed from the constituent `model`, `view` and `projection` matrices of the camera, which are controlled by the following interfaces. 110 | 111 | ### Projection matrix 112 | The projection matrix determines the relation between camera coordinates and clip coordinates. 113 | 114 | ```javascript 115 | var projection = camera.projection 116 | ``` 117 | 118 | #### `projection.matrix` 119 | The current projection matrix. You can assign to this property to change it dynamically. 120 | 121 | #### `projection.mode` 122 | The interaction mode for the projection matrix. Currently supported modes are: 123 | 124 | * `"perspective"` makes the camera behave as a perspective transformation, [based on this module](https://github.com/mikolalysenko/perspective-camera-controller) 125 | * `"ortho"` makes the camera behave as an orthogonal camera 126 | * `"matrix"` allows for direct matrix manipulation of the camera controller 127 | 128 | #### `projection.fovX` 129 | The horizontal field of view angle in radians 130 | 131 | #### `projection.fovY` 132 | The vertical field of view angle in radians 133 | 134 | #### `projection.zNear` 135 | The distance of the near clip plane 136 | 137 | #### `projection.zFar` 138 | The distance of the far clip plane 139 | 140 | ### View matrix 141 | 142 | ```javascript 143 | var view = camera.view 144 | ``` 145 | 146 | #### `view.matrix` 147 | The contents of the view matrix. Assigning to this value directly changes the view matrix. 148 | 149 | #### `view.mode` 150 | The interaction mode for the view matrix. Currently supported modes are: 151 | 152 | * `"turntable"` a turntable based interaction, [based on this module](https://github.com/mikolalysenko/turntable-camera-controller). 153 | * `"orbit"` a freely orbiting trackball camera controller, [based on this module](https://github.com/mikolalysenko/orbit-camera-controller) 154 | * `"fps"` a first person style camera (like Quake/Doom) 155 | * `"matrix"` unconstrained matrix based camera interactions 156 | 157 | #### `view.up` 158 | The up vector for the view coordinate system in 159 | 160 | #### `view.eye` 161 | The position of the camera in world coordinates (similar `camera.world.origin`)` 162 | 163 | #### `view.center` 164 | For `turntable` and `orbit` mode, this is the center of the camera coordinate system. 165 | 166 | #### `view.distance` 167 | The distance from the camera to the center 168 | 169 | #### `view.lookAt(eye, center, up)` 170 | Sets the orientation of the camera 171 | 172 | * `eye` is the eye vector of the viewer (or `view.eye` if unspecified) 173 | * `center` is the target of the camera (or `view.center` if unspecified) 174 | * `up` is the up vector for the camera (or `view.up` if unspecified) 175 | 176 | #### `view.rotate(dx, dy, dz)` 177 | Apply an incremental rotation in screen coordinates. 178 | 179 | * `dx` is the amount to rotate horizontally/yaw (in radians) 180 | * `dy` is the amount to rotate vertically/pitch (in radians) 181 | * `dz` is the amount to roll (in radians) 182 | 183 | #### `view.zoom(dr)` 184 | Modify the distance to the camera by a factor of `dr` 185 | 186 | * `dr` is a scale factor which is `>0` determining the zoom amount 187 | 188 | #### `view.pan(dx, dy)` 189 | Pan the camera within the viewing plane 190 | 191 | * `dx` is the amount to pan horizontally (in world units) 192 | * `dy` is the amount to pan vertically (in world units) 193 | 194 | #### `view.translate(dx, dy, dz)` 195 | Translates the camera in absolute (world) coordinates 196 | 197 | * `dx,dy,dz` are the component-wise transformations in world units 198 | 199 | #### `view.move(dx, dy, dz)` 200 | Translates the camera in relative (camera) coordinates 201 | 202 | * `dx,dy,dz` are the component-wise component transformations in camera units 203 | 204 | ### Model matrix 205 | 206 | ```javascript 207 | var model = camera.model 208 | ``` 209 | 210 | #### `model.matrix` 211 | The model matrix of the camera. 212 | 213 | #### `model.mode` 214 | Interaction mode for the model matrix. Currently supported modes are: 215 | 216 | * `"matrix"` direct matrix based access for the model matrix 217 | 218 | # License 219 | (c) 2015 Mikola Lysenko. MIT License -------------------------------------------------------------------------------- /camera.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var createCoordRelations = require('3d-camera-core') 4 | 5 | var IDENTITY = [1,0,0,0, 6 | 0,1,0,0, 7 | 0,0,1,0, 8 | 0,0,0,1] 9 | function MatrixController() { 10 | this.matrix = IDENTITY.slice() 11 | this.isDirty = true 12 | } 13 | var cproto = MatrixController.prototype 14 | cproto.get = function(m) { 15 | var n = this.matrix 16 | for(var i=0; i<16; ++i) { 17 | m[i] = n[i] 18 | } 19 | this.isDirty = false 20 | return m 21 | } 22 | cproto.dirty = function() { 23 | return this.isDirty 24 | } 25 | 26 | function createCamera(options) { 27 | options = options || {} 28 | 29 | //Model matrix 30 | var modelController = new MatrixController() 31 | var model = {} 32 | Object.defineProperties(model, { 33 | mode: { 34 | get: function() { 35 | return 'matrix' 36 | }, 37 | set: function(m) { 38 | return 'matrix' 39 | }, 40 | enumerable: true 41 | }, 42 | matrix: { 43 | get: function() { 44 | return modelController.matrix 45 | }, 46 | set: function(m) { 47 | var mm = modelController.matrix 48 | for(var i=0; i<16; ++i) { 49 | mm[i] = m[i] 50 | } 51 | return modelMatrix 52 | }, 53 | enumerable: true 54 | } 55 | }) 56 | 57 | //View matrix 58 | var viewController = new MatrixController() 59 | var view = { 60 | lookAt: function(eye, center, up) { 61 | }, 62 | rotate: function(dx, dy, dz) { 63 | }, 64 | zoom: function(dr) { 65 | }, 66 | pan: function(dx, dy, dz) { 67 | }, 68 | translate: function(dx, dy, dz) { 69 | }, 70 | zoom: function(dx, dy, dz) { 71 | } 72 | } 73 | Object.defineProperties(view, { 74 | mode: { 75 | }, 76 | matrix: { 77 | }, 78 | eye: { 79 | }, 80 | up: { 81 | }, 82 | center: { 83 | }, 84 | distance: { 85 | } 86 | }) 87 | 88 | //Projection matrix 89 | var projectionController = new MatrixController() 90 | var projection = {} 91 | Object.defineProperties(projection, { 92 | mode: { 93 | }, 94 | matrix: { 95 | }, 96 | fovX: { 97 | }, 98 | fovY: { 99 | }, 100 | zNear: { 101 | }, 102 | zFar: { 103 | } 104 | }) 105 | 106 | 107 | //Core camera object 108 | var coords = createCoordRelations({ 109 | model: modelController, 110 | view: viewController, 111 | projection: projectionController 112 | }) 113 | var camera = { 114 | 115 | model: model, 116 | view: view, 117 | projection: projection, 118 | 119 | data: coords.data, 120 | world: coords.world, 121 | camera: coords.camera, 122 | clip: coords.clip, 123 | 124 | tick: function() { 125 | } 126 | } 127 | Object.defineProperties(camera, { 128 | width: { 129 | get: function() { 130 | }, 131 | set: function(w) { 132 | }, 133 | enumerable: true 134 | }, 135 | height: { 136 | get: function() { 137 | }, 138 | set: function(h) { 139 | }, 140 | enumerable: true 141 | }, 142 | shape: { 143 | get: function() { 144 | }, 145 | set: function(s) { 146 | }, 147 | enumerable: true 148 | }, 149 | delay: { 150 | get: function() { 151 | }, 152 | set: function(d) { 153 | }, 154 | enumerable: true 155 | } 156 | }) 157 | 158 | return camera 159 | } --------------------------------------------------------------------------------