├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── test.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 | # camera-2d 2 | 3 | > A simple 2D camera suited for games and visualizations. 4 | 5 | 6 | # Install 7 | 8 | ```bash 9 | $ npm install camera-2d 10 | ``` 11 | 12 | 13 | # Usage 14 | 15 | ```js 16 | var camera = createCamera({ 17 | left: 0, 18 | right: 640, 19 | top: 0, 20 | bottom: 480 21 | }) 22 | 23 | camera.setTranslation(64, 64) 24 | camera.setRotation(Math.PI / 4) 25 | camera.setScale(0.5) 26 | 27 | var matrix = camera.getMatrix() 28 | console.dir(matrix) 29 | ``` 30 | 31 | outputs a 3x3 matrix compatible with 32 | [gl-mat3](https://www.npmjs.com/package/gl-mat3): 33 | ``` 34 | Float32Array { 35 | '0': 0.0011048543965443969, 36 | '1': -0.001473139156587422, 37 | '2': 0, 38 | '3': -0.0011048543965443969, 39 | '4': -0.001473139156587422, 40 | '5': 0, 41 | '6': 0, 42 | '7': 0.18856181204319, 43 | '8': 1 } 44 | ``` 45 | 46 | # API 47 | 48 | ## var camera = require('camera-2d')(options) 49 | 50 | Creates a new camera object. The `options` object contains the following 51 | optional parameters: 52 | 53 | - `left` - the coordinate of the left side of the screen. 54 | - `right` - the coordinate of the right side of the screen. 55 | - `top` - the coordinate of the top of the screen. 56 | - `bottom` - the coordinate of the bottom of the screen. 57 | - `x` - the X coordinate to centre the camera on. 58 | - `y` - the Y coordinate to centre the camera on. 59 | - `angle` - the rotation to apply around the screen centre. 60 | - `scale` - the scaling factor to apply. 61 | 62 | 63 | ## Properties 64 | 65 | ### camera.width, camera.height 66 | 67 | Width and height of the camera viewport, in units. 68 | 69 | ### camera.x, camera.y 70 | 71 | The camera's current X/Y offset from the origin. 72 | 73 | ### camera.rotation 74 | 75 | The rotation of the camera viewport, in radians. 76 | 77 | ### camera.scale 78 | 79 | The scaling factor of the camera. 80 | 81 | 82 | ## Methods 83 | 84 | ### camera.getMatrix([out]) 85 | 86 | Retrieves the current state of the camera matrix. 87 | 88 | `out` is a length 9 array which receives the result of the computation if not 89 | otherwise specified. 90 | 91 | **Returns** The resulting camera matrix 92 | 93 | ### camera.setTranslation(x, y) 94 | 95 | Sets the X/Y offset from the origin for the camera to be centred on. 96 | 97 | ### camera.setRotation(angle) 98 | 99 | Sets the rotation around the translation coordinates, in radians. 100 | 101 | ### camera.setScale(scale) 102 | 103 | Sets the scaling factor of the camera at the translation coordinates. 104 | 105 | 106 | ## Additional Credit 107 | 108 | Heavily inspired by Mikola Lysenko's 109 | [camera-2d](https://github.com/mikolalysenko/camera-2d) module. 110 | 111 | I really liked the idea of a lightweight 2D camera that spits out a 3x3 112 | transformation matrix, but wanted an API that made both absolute and relative 113 | operations easy. 114 | 115 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = createCamera2D 4 | 5 | var mat3 = require('gl-mat3') 6 | var vec2 = require('gl-vec2') 7 | 8 | function Camera2D(angle, scale, tx, ty, left, right, top, bottom) { 9 | this.angle = angle 10 | this.scale = scale 11 | this.x = tx 12 | this.y = ty 13 | this.left = left 14 | this.right = right 15 | this.top = top 16 | this.bottom = bottom 17 | 18 | this.width = Math.abs(this.left - this.right) 19 | this.height = Math.abs(this.top - this.bottom) 20 | 21 | this.projection= ortho2D(mat3.create(), this.left, this.right, this.bottom, this.top) 22 | this.matrix = mat3.create() 23 | this.scratchVector = vec2.create() 24 | } 25 | 26 | var proto = Camera2D.prototype 27 | 28 | proto.getMatrix = function(out) { 29 | 30 | // Set to basic projection 31 | mat3.copy(this.matrix, this.projection) 32 | 33 | // Rotate 34 | mat3.rotate(this.matrix, this.matrix, this.angle) 35 | 36 | // Scale 37 | vec2.set(this.scratchVector, this.scale, this.scale) 38 | mat3.scale(this.matrix, this.matrix, this.scratchVector) 39 | 40 | // Translate to destination 41 | vec2.set(this.scratchVector, this.x, this.y) 42 | mat3.translate(this.matrix, this.matrix, this.scratchVector) 43 | 44 | out = out || mat3.create() 45 | mat3.copy(out, this.matrix) 46 | 47 | return out 48 | } 49 | 50 | proto.setRotation = function (angle) { 51 | this.angle = angle 52 | } 53 | 54 | proto.setScale = function (scale) { 55 | this.scale = scale 56 | } 57 | 58 | proto.setTranslation = function(x, y) { 59 | this.x = -x 60 | this.y = -y 61 | } 62 | 63 | function createCamera2D(options) { 64 | options = options || {} 65 | return new Camera2D( 66 | options.angle || 0.0, 67 | options.scale || 1.0, 68 | options.x || 0.0, 69 | options.y || 0.0, 70 | options.left || 0.0, 71 | options.right || 1.0, 72 | options.top || 0.0, 73 | options.bottom || 1.0 74 | ) 75 | } 76 | 77 | function ortho2D(out, left, right, bottom, top) { 78 | var lr = 1 / (left - right) 79 | var bt = 1 / (bottom - top) 80 | 81 | out[0] = -2 * lr 82 | out[1] = 0 83 | out[2] = 0 84 | 85 | out[3] = 0 86 | out[4] = -2 * bt 87 | out[5] = 0 88 | 89 | out[6] = 0 90 | out[7] = 0 91 | out[8] = 1 92 | 93 | return out 94 | } 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "camera-2d", 3 | "version": "1.0.1", 4 | "description": "2D camera controller for games and visualizations", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "gl-mat3": "^1.0.0", 11 | "gl-vec2": "^1.0.0" 12 | }, 13 | "devDependencies": { 14 | "tape": "^4.0.0" 15 | }, 16 | "scripts": { 17 | "test": "tape test/*.js" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/mikolalysenko/camera-2d.git" 22 | }, 23 | "keywords": [ 24 | "2d", 25 | "camera", 26 | "game", 27 | "matrix", 28 | "pan", 29 | "rotation", 30 | "scale", 31 | "translate", 32 | "webgl", 33 | "zoom" 34 | ], 35 | "author": [ 36 | "Mikola Lysenko", 37 | "Stephen Whitmore" 38 | ], 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/mikolalysenko/camera-2d/issues" 42 | }, 43 | "homepage": "https://github.com/mikolalysenko/camera-2d" 44 | } 45 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var createCamera = require('../index') 2 | var mat3 = require('gl-mat3') 3 | var test = require('tape') 4 | 5 | test('identity', function (t) { 6 | t.plan(1) 7 | 8 | var camera = createCamera() 9 | var matrix = camera.getMatrix() 10 | 11 | var expected = [ 12 | 2, 0, 0, 13 | 0,-2, 0, 14 | 0, 0, 1 15 | ] 16 | 17 | matrixEquals(t, matrix, expected) 18 | }) 19 | 20 | test('projection', function (t) { 21 | t.plan(1) 22 | 23 | var camera = createCamera({ 24 | left: 0, 25 | right: 100, 26 | top: 0, 27 | bottom: 100 28 | }) 29 | var matrix = camera.getMatrix() 30 | 31 | var expected = [ 32 | 0.019999999552965164, 0, 0, 33 | 0, -0.019999999552965164, 0, 34 | 0, 0, 1 35 | ] 36 | 37 | matrixEquals(t, matrix, expected) 38 | }) 39 | 40 | test('translate', function (t) { 41 | t.plan(1) 42 | 43 | var camera = createCamera() 44 | 45 | camera.setTranslation(10, 5) 46 | 47 | var matrix = camera.getMatrix() 48 | 49 | var expected = [ 50 | 2, 0, 0, 51 | 0,-2, 0, 52 | -20,10, 1 53 | ] 54 | 55 | matrixEquals(t, matrix, expected) 56 | }) 57 | 58 | test('scale', function (t) { 59 | t.plan(1) 60 | 61 | var camera = createCamera() 62 | 63 | camera.setScale(4) 64 | 65 | var matrix = camera.getMatrix() 66 | 67 | var expected = [ 68 | 8, 0, 0, 69 | 0,-8, 0, 70 | 0, 0, 1 71 | ] 72 | 73 | matrixEquals(t, matrix, expected) 74 | }) 75 | 76 | test('rotate', function (t) { 77 | t.plan(1) 78 | 79 | var camera = createCamera() 80 | 81 | camera.setRotation(Math.PI) 82 | 83 | var matrix = camera.getMatrix() 84 | 85 | var expected = [ 86 | -2, -2.4492937, 0, 87 | -2.4492937, 2, 0, 88 | 0, 0, 1 89 | ] 90 | 91 | matrixEquals(t, matrix, expected) 92 | }) 93 | 94 | 95 | function matrixEquals(t, actual, expected) { 96 | for (var i=0; i < 9; i++) { 97 | if (floatEquals(actual, expected)) { 98 | t.fail('matrix['+i+'] differs (actual='+actual[i]+' expected='+expected[i]+')') 99 | } 100 | } 101 | t.ok(true) 102 | } 103 | 104 | function floatEquals(actual, expected) { 105 | var EPSILON = 0.0001 106 | return Math.abs(actual - expected) < EPSILON 107 | } 108 | --------------------------------------------------------------------------------