├── .gitignore ├── Cakefile ├── README.md ├── lib ├── blockSelection.coffee ├── camera.coffee ├── collision.coffee ├── coreExtensions.coffee ├── instructions.coffee ├── methodTracer.coffee └── minecraft.coffee ├── public ├── index.html ├── instructions │ ├── drag.png │ ├── leftclick.png │ ├── pause.png │ ├── rightclick.png │ ├── save.png │ ├── scroll.png │ ├── space.png │ └── wasd.png ├── jquery │ └── jquery-1.9.1.js ├── lib │ ├── Detector.js │ ├── ImprovedNoise.js │ ├── RequestAnimationFrame.js │ ├── THREEx.WindowResize.js │ ├── Three.js │ ├── jquery.hotkeys.js │ ├── jquery.mousewheel.js │ ├── pointer.js │ └── rbcoffee.js ├── spec │ └── coffee │ │ └── applicationSpec.js └── textures │ ├── bedrock.png │ ├── bluewool.png │ ├── bluewoolicon.png │ ├── brick.png │ ├── brickicon.png │ ├── cobblestone.png │ ├── cobblestoneicon.png │ ├── diamond.png │ ├── diamondicon.png │ ├── dirt.png │ ├── glowstone.png │ ├── glowstoneicon.png │ ├── grass.png │ ├── grass_dirt.png │ ├── netherrack.png │ ├── netherrackicon.png │ ├── obsidian.png │ ├── obsidianicon.png │ ├── plank.png │ ├── plankicon.png │ ├── redwool.png │ ├── redwoolicon.png │ ├── whitewool.png │ └── whitewoolicon.png └── spec ├── coffee └── applicationSpec.coffee ├── jasmine ├── LICENSE ├── README.md ├── lib │ └── jasmine │ │ ├── TrivialReporter.js │ │ ├── consolex.js │ │ ├── index.js │ │ ├── jasmine-0.10.2.js │ │ └── jasmine.css └── specs.js └── web_runner.html /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/ 2 | /.bundle/ 3 | pkg 4 | db 5 | .hg 6 | 7 | ## EMACS 8 | *~ 9 | \#* 10 | .\#* 11 | 12 | ## Build 13 | *.gem 14 | public/*.js 15 | public/*.map 16 | spec/javascripts/*.js 17 | spec/javascripts/*.map 18 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | {spawn} = require 'child_process' 2 | puts = console.log 3 | 4 | system = (name, args, callback) -> 5 | print = (buffer) -> process.stdout.write buffer.toString() 6 | proc = spawn name, args 7 | proc.stdout.on 'data', print 8 | proc.stderr.on 'data', print 9 | proc.on 'exit', (status) -> callback?() 10 | 11 | compileall = (from, to, watch = false, callback = null) -> 12 | args = ['--map', '-o', to, '-c', from] 13 | args.unshift '-w' if watch 14 | system 'coffee', args, callback 15 | 16 | task 'c', 'Compile and watch', -> 17 | compileall 'lib/', 'public/', true 18 | compileall 'spec/coffee', 'spec/javascripts', true 19 | 20 | task 'compile', 'Compile', -> 21 | puts "Compiling..." 22 | compileall 'lib/', 'public/' 23 | 24 | task 'server', 'Serve the current filesystem. Needed for loading textures from fs. 25 | Require python installed.', -> 26 | system 'python', '-m SimpleHTTPServer'.split(' ') 27 | 28 | task 'spec', "runs unit tests", -> 29 | puts "Make sure to be running server on port 8000" 30 | compileall 'lib/', 'public/', false, -> 31 | compileall 'spec/coffee', 'spec/javascripts', false, -> 32 | system "open", ["http://localhost:8000/spec/web_runner.html"] 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WebGLCraft 2 | ============== 3 | 4 | WebGL implementation of [Minecraft](http://www.minecraft.net/) written in [Coffeescript](http://jashkenas.github.com/coffee-script/). 5 | 6 | Demo [here](http://danielribeiro.github.io/WebGLCraft/). 7 | 8 | [![](http://metaphysicaldeveloper.files.wordpress.com/2011/12/screen-shot-2011-12-17-at-6-44-36-pm.png)](http://danielribeiro.github.io/WebGLCraft/) 9 | 10 | You can read more about the development of this project [here](http://metaphysicaldeveloper.wordpress.com/2011/12/20/implementing-minecraft-in-webgl/) 11 | 12 | 13 | Compiling 14 | ---- 15 | 16 | It requires Coffeescript 1.1.3+ (version 1.6.2+ for [source maps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/)), and node.js 0.6+ 17 | 18 | To compile, run: 19 | 20 | cake c 21 | 22 | The command above will also watch for any changes. If you just wanna compile the files, run: 23 | 24 | cake compile 25 | 26 | If you wanna see the game locally, you need python, and you run 27 | 28 | cake server 29 | 30 | which simply runs 31 | 32 | 33 | python -m SimpleHTTPServer 34 | 35 | enabling you to open the game on [http://localhost:8000/public/](http://localhost:8000/public/) 36 | 37 | 38 | To run the tests, simply run: 39 | 40 | 41 | cake spec 42 | 43 | 44 | The tests are powered by [Jasmine](https://jasmine.github.io/), and can also be seen 45 | on the browser (useful for debugging) by opening http://localhost:8000/spec/web_runner.html 46 | 47 | Coffeescript Source Maps 48 | ----- 49 | 50 | Make sure you have the latest coffeescript running (see [this](https://github.com/jashkenas/coffee-script/issues/2835) issue). Your source map file should look like this: 51 | 52 | ```javascript 53 | { 54 | "version": 3, 55 | "file": "collision.js", 56 | "sourceRoot": "..", 57 | "sources": [ 58 | "lib/collision.coffee" 59 | ], 60 | "names": [], 61 | "mappings": ";AAAA;CAAA,CAAA,CAEI,CAFH,UAAD;CAEI,CAAuB,CAAA,CAAvB,KAAwB,YAAxB;CACI,CAAe,EAAA,CAAM,CAArB;CAAA,GAAA,WAAO;QAAP;CACA,CAAmB,CAAK,CAAL,EAAnB;CAAA,CAAO,EAAM,WAAN;QADP;CAEA,CAAO,EAAM,SAAN;CAHX,IAAuB;CAAvB,CAOmB,CAAA,CAAnB,CAAmB,IAAC,QAApB;CACI,SAAA,0BAAA;CAAA,EAAO,CAAP,EAAA,eAAA;CACA;CAAA,UAAA,gCAAA;yBAAA;CACI,CAAkC,CAAvB,CAAA,CAAU,GAArB;AAEoB,CAApB,GAAA,IAAA;CAAA,IAAA,YAAO;UAHX;CAAA,MADA;CAKA,GAAA,SAAO;CAbX,IAOmB;CATvB,GAAA;CAAA" 62 | } 63 | ``` 64 | 65 | 66 | Meta 67 | ---- 68 | 69 | Created by [Daniel Ribeiro](http://metaphysicaldeveloper.wordpress.com/about-me). Not affiliated with Mojang. Minecraft is a trademark of [Mojang](http://mojang.com/). 70 | 71 | Released under the MIT License: http://www.opensource.org/licenses/mit-license.php 72 | 73 | http://github.com/danielribeiro/WebGLCraft 74 | -------------------------------------------------------------------------------- /lib/blockSelection.coffee: -------------------------------------------------------------------------------- 1 | @Blocks = ["cobblestone", "plank", "brick", "diamond", 2 | "glowstone", "obsidian", "whitewool", "bluewool", "redwool", "netherrack"] 3 | 4 | class @BlockSelection 5 | constructor: (@game) -> @current = "cobblestone" 6 | 7 | blockImg: (name) -> "" 8 | 9 | mousedown: (e) -> 10 | return false if e.target == @ 11 | @select e.target.id 12 | return false 13 | 14 | mousewheel: (delta) -> 15 | dif = (if delta >= 0 then 1 else -1) 16 | index = (Blocks.indexOf(@current) - dif).mod(Blocks.length) 17 | @select Blocks[index] 18 | 19 | ligthUp: (target) -> @_setOpacity target, 0.8 20 | lightOff: (target) -> @_setOpacity target, 1 21 | 22 | select: (name) -> 23 | return if @current is name 24 | @game.selectCubeBlock name 25 | @ligthUp name 26 | @lightOff @current 27 | @current = name 28 | 29 | _setOpacity: (target, val) -> $("#" + target).css(opacity: val) 30 | 31 | insert: -> 32 | blockList = (@blockImg(b) for b in Blocks) 33 | domElement = $("#minecraft-blocks") 34 | domElement.append blockList.join('') 35 | @ligthUp @current 36 | domElement.mousedown (e) => @mousedown e 37 | $(document).mousewheel (e, delta) => @mousewheel delta 38 | domElement.show() 39 | -------------------------------------------------------------------------------- /lib/camera.coffee: -------------------------------------------------------------------------------- 1 | @MouseEvent = 2 | isLeftButton: (event) -> event.which == 1 3 | isRightButton: (event) -> event.which == 3 4 | 5 | isLeftButtonDown: (event) -> event.button == 0 and @isLeftButton event 6 | 7 | class @Controls 8 | constructor: (object, domElement) -> 9 | @object = object 10 | @target = new THREE.Vector3 0, 0, 0 11 | @domElement = domElement or document 12 | @lookSpeed = 0.20 13 | @mouseX = 0 14 | @mouseY = 0 15 | @lat = -66.59 16 | @lon = -31.8 17 | @deltaX = 0 18 | @deltaY = 0 19 | @mouseDragOn = false 20 | @anchorx = null 21 | @anchory = null 22 | @mouseLocked = false 23 | @defineBindings() 24 | 25 | enableMouseLocked: -> @mouseLocked = true 26 | disableMouseLocked: -> @mouseLocked = false 27 | 28 | defineBindings: -> 29 | $(document).mousemove (e) => @onMouseMove e 30 | $(@domElement).mousedown (e) => @onMouseDown e 31 | $(@domElement).mouseup (e) => @onMouseUp e 32 | $(@domElement).mouseenter (e) => @onMouserEnter e 33 | 34 | 35 | showCrosshair: -> document.getElementById('cursor').style.display = 'block' 36 | hideCrosshair: -> document.getElementById('cursor').style.display = 'none' 37 | 38 | onMouserEnter: (event) -> 39 | @onMouseUp(event) unless MouseEvent.isLeftButtonDown event 40 | 41 | onMouseDown: (event) -> 42 | return unless MouseEvent.isLeftButton event 43 | @domElement.focus() if @mouseLocked and @domElement isnt document 44 | @anchorx = event.pageX 45 | @anchory = event.pageY 46 | @setMouse @anchorx, @anchory 47 | @mouseDragOn = true 48 | return false 49 | 50 | onMouseUp: (event) -> 51 | @mouseDragOn = false 52 | return false 53 | 54 | setMouse: (x, y) -> 55 | @mouseX = x 56 | @mouseY = y 57 | @setDelta x - @anchorx, y - @anchory 58 | 59 | setDelta: (x, y) -> 60 | @deltaX = x 61 | @deltaY = y 62 | 63 | setDirection: (dir) -> {@lat,@lon} = dir 64 | 65 | getDirection: -> {@lat,@lon} 66 | 67 | onMouseMove: (event) -> 68 | if @mouseDragOn 69 | console.log event 70 | @setMouse event.pageX, event.pageY 71 | else if @mouseLocked 72 | e = event.originalEvent 73 | x = e.movementX or e.mozMovementX or e.webkitMovementX 74 | y = e.movementY or e.mozMovementY or e.webkitMovementY 75 | @setDelta x, y 76 | return 77 | 78 | halfCircle: Math.PI / 180 79 | 80 | viewDirection: -> @target.clone().sub(@object.position) 81 | 82 | move: (newPosition) -> 83 | @object.position = newPosition 84 | @updateLook() 85 | 86 | updateLook: -> 87 | {sin, cos} = Math 88 | phi = (90 - @lat) * @halfCircle 89 | theta = @lon * @halfCircle 90 | p = @object.position 91 | assoc @target, 92 | x: p.x + 100 * sin(phi) * cos(theta) 93 | y: p.y + 100 * cos(phi) 94 | z: p.z + 100 * sin(phi) * sin(theta) 95 | @object.lookAt @target 96 | return 97 | 98 | update: -> 99 | return unless @mouseDragOn or @mouseLocked 100 | return if @mouseDragOn and @mouseX is @anchorx and @mouseY is @anchory 101 | {max, min} = Math 102 | if @mouseLocked 103 | return if @deltaX is @previousDeltaX and @deltaY is @previousDeltaY 104 | @previousDeltaX = @deltaX 105 | @previousDeltaY = @deltaY 106 | @anchorx = window.innerWidth/2 107 | @anchory = window.innerHeight/2 108 | else if @mouseDragOn 109 | return if @mouseX is @anchorx and @mouseY is @anchory 110 | @anchorx = @mouseX 111 | @anchory = @mouseY 112 | 113 | @lon += @deltaX * @lookSpeed 114 | @lat -= @deltaY * @lookSpeed 115 | @lat = max(-85, min(85, @lat)) 116 | @updateLook() 117 | return -------------------------------------------------------------------------------- /lib/collision.coffee: -------------------------------------------------------------------------------- 1 | @CollisionUtils = 2 | # The two intervals are [s1, f1] and [s2, f2] 3 | testIntervalCollision: (s1, f1, s2, f2) -> 4 | return true if s1 == s2 5 | return f1 >= s2 if s1 < s2 6 | return f2 >= s1 7 | 8 | #Cubes are objects with vmax, vmin (the vertices with greatest/smallest values) 9 | #properties. Assumes unrotated cubes. 10 | testCubeCollision: (cube1, cube2) -> 11 | fcol = @testIntervalCollision 12 | for axis in ['x', 'y', 'z'] 13 | collides = fcol cube1.vmin[axis], cube1.vmax[axis] 14 | , cube2.vmin[axis], cube2.vmax[axis] 15 | return false unless collides 16 | return true 17 | -------------------------------------------------------------------------------- /lib/coreExtensions.coffee: -------------------------------------------------------------------------------- 1 | patch Number, 2 | mod: (arg) -> 3 | return @ % arg if @ >= 0 4 | return (@ + arg) % arg 5 | 6 | div: (arg) -> return Math.floor(@ / arg) 7 | 8 | 9 | times: (fn) -> 10 | i = 0 11 | while i < @ 12 | fn(i++) 13 | 14 | toRadians: -> (@ * Math.PI) / 180 15 | 16 | toDegrees: -> (@ * 180) / Math.PI 17 | 18 | 19 | @assoc = (o, i) -> 20 | (o[k] = v) for k, v of i 21 | return o 22 | -------------------------------------------------------------------------------- /lib/instructions.coffee: -------------------------------------------------------------------------------- 1 | class @Instructions 2 | constructor: (@callback) -> 3 | @domElement = $('#instructions') 4 | 5 | instructions: 6 | leftclick: "Remove block" 7 | rightclick: "Add block" 8 | drag: "Drag with the left mouse clicked to move the camera" 9 | save: "Save map" 10 | pause: "Pause/Unpause" 11 | space: "Jump" 12 | wasd: "WASD keys to move" 13 | scroll: "Scroll to change selected block" 14 | 15 | intructionsBody: -> 16 | @domElement.append "
17 |

Click to start

18 | #{@lines()}
19 |
" 20 | $("#instructionsContent").mousedown => 21 | @domElement.hide() 22 | @callback() 23 | return 24 | 25 | ribbon: -> 26 | ' 27 | Fork me on GitHub' 30 | 31 | insert: -> 32 | @setBoder() 33 | @intructionsBody() 34 | minecraft = "Minecraft" 35 | legal = "
Not affiliated with Mojang. #{minecraft} is a trademark of Mojang
" 36 | hnimage = '' 37 | hnlink = "
Comment on #{hnimage} Hacker News
" 38 | @domElement.append legal + hnlink + @ribbon() 39 | @domElement.show() 40 | 41 | lines: -> 42 | ret = (@line(inst) for inst of @instructions) 43 | ret.join(' ') 44 | 45 | line: (name) -> 46 | inst = @instructions[name] 47 | "#{@img(name)} 48 | #{inst}" 49 | 50 | setBoder: -> 51 | for prefix in ['-webkit-', '-moz-', '-o-', '-ms-', ''] 52 | @domElement.css prefix + 'border-radius', '10px' 53 | return 54 | 55 | img: (name) -> "" -------------------------------------------------------------------------------- /lib/methodTracer.coffee: -------------------------------------------------------------------------------- 1 | class @MethodTracer 2 | constructor: -> 3 | @tracer = {} 4 | 5 | trace: (clasname) -> 6 | clas = eval(clasname) 7 | for name, f of clas.prototype when typeof f is 'function' 8 | uniqueId = "#{clasname}##{name}" 9 | tracer = @tracer 10 | tracer[uniqueId] = false 11 | clas.prototype[name] = (args...) -> 12 | tracer[uniqueId] = true 13 | f(args...) 14 | return @ 15 | 16 | traceClasses: (classNames) -> 17 | for clas in classNames.split(' ') 18 | @trace clas 19 | return @ 20 | 21 | traceModule: (module, moduleName) -> 22 | for name, f of module when typeof f is 'function' 23 | uniqueId = "Module #{moduleName}##{name}" 24 | tracer = @tracer 25 | tracer[uniqueId] = false 26 | module[name] = @wrapfn(module, uniqueId, f) 27 | return @ 28 | 29 | wrapfn: (module, uniqueId, f) -> 30 | self = @ 31 | return (args...) -> 32 | self.tracer[uniqueId] = true 33 | f.apply(module, args) 34 | 35 | printUnused: -> 36 | for id, used of @tracer when not used 37 | puts id 38 | return @ 39 | -------------------------------------------------------------------------------- /lib/minecraft.coffee: -------------------------------------------------------------------------------- 1 | # Imports 2 | {Object3D, Matrix4, Scene, Mesh, WebGLRenderer, PerspectiveCamera} = THREE 3 | {CubeGeometry, PlaneGeometry, MeshLambertMaterial, MeshNormalMaterial} = THREE 4 | {AmbientLight, DirectionalLight, PointLight, Raycaster, Vector3, Vector2} = THREE 5 | {MeshLambertMaterial, MeshNormalMaterial, Projector} = THREE 6 | {Texture, UVMapping, RepeatWrapping, RepeatWrapping, NearestFilter} = THREE 7 | {LinearMipMapLinearFilter, ClampToEdgeWrapping, Clock} = THREE 8 | 9 | vec = (x, y, z) -> new Vector3 x, y, z 10 | 11 | CubeSize = 50 12 | 13 | class Player 14 | width: CubeSize * 0.3 15 | depth: CubeSize * 0.3 16 | height: CubeSize * 1.63 17 | 18 | constructor: -> 19 | @halfHeight = @height / 2 20 | @halfWidth = @width / 2 21 | @halfDepth = @depth / 2 22 | @pos = vec() 23 | @eyesDelta = @halfHeight * 0.9 24 | 25 | eyesPosition: -> 26 | ret = @pos.clone() 27 | ret.y += @eyesDelta 28 | return ret 29 | 30 | 31 | position: (axis) -> 32 | return @pos unless axis? 33 | return @pos[axis] 34 | 35 | incPosition: (axis, val) -> 36 | @pos[axis] += val 37 | return 38 | 39 | setPosition: (axis, val) -> 40 | @pos[axis] = val 41 | return 42 | 43 | 44 | collidesWithGround: -> @position('y') < @halfHeight 45 | 46 | vertex: (vertexX, vertexY, vertexZ) -> 47 | vertex = @position().clone() 48 | vertex.x += vertexX * @halfWidth 49 | vertex.y += vertexY * @halfHeight 50 | vertex.z += vertexZ * @halfDepth 51 | return vertex 52 | 53 | boundingBox: -> 54 | vmin = @vertex(-1, -1, -1) 55 | vmax = @vertex 1, 1, 1 56 | return {vmin: vmin, vmax: vmax} 57 | 58 | 59 | class Grid 60 | constructor: (@size = 5) -> 61 | @matrix = [] 62 | @size.times (i) => 63 | @matrix[i] = [] 64 | @size.times (j) => 65 | @matrix[i][j] = [] 66 | @map = JSON.parse(JSON.stringify(@matrix)) #deep copy 67 | 68 | insideGrid: (x, y, z) -> 0 <= x < @size and 0 <= y < @size and 0 <= z < @size 69 | 70 | get: (x, y, z) -> @matrix[x][y][z] 71 | 72 | put: (x, y, z, val) -> 73 | @matrix[x][y][z] = val 74 | return @map[x][y][z] = null unless val 75 | @map[x][y][z] = val.material.materials[0].map.image.src.match(/\/([a-zA-Z0-9_]*)\..*$/)[1] # hack to take cubeName 76 | 77 | gridCoords: (x, y, z) -> 78 | x = Math.floor(x / CubeSize) 79 | y = Math.floor(y / CubeSize) 80 | z = Math.floor(z / CubeSize) 81 | return [x, y, z] 82 | 83 | 84 | class CollisionHelper 85 | constructor: (@player, @grid)-> return 86 | rad: CubeSize 87 | halfRad: CubeSize / 2 88 | 89 | collides: -> 90 | return true if @player.collidesWithGround() 91 | return true if @beyondBounds() 92 | playerBox = @player.boundingBox() 93 | for cube in @possibleCubes() 94 | return true if @_collideWithCube playerBox, cube 95 | return false 96 | 97 | beyondBounds: -> 98 | p = @player.position() 99 | [x, y, z] = @grid.gridCoords p.x, p.y, p.z 100 | return true unless @grid.insideGrid x, 0, z 101 | 102 | 103 | _addToPosition: (position, value) -> 104 | pos = position.clone() 105 | pos.x += value 106 | pos.y += value 107 | pos.z += value 108 | return pos 109 | 110 | collideWithCube: (cube) -> @_collideWithCube @player.boundingBox(), cube 111 | 112 | _collideWithCube: (playerBox, cube) -> 113 | vmin = @_addToPosition cube.position, -@halfRad 114 | vmax = @_addToPosition cube.position, @halfRad 115 | cubeBox = {vmin, vmax} 116 | return CollisionUtils.testCubeCollision playerBox, cubeBox 117 | 118 | possibleCubes: -> 119 | cubes = [] 120 | grid = @grid 121 | @withRange (x, y, z) -> 122 | cube = grid.get x, y, z 123 | cubes.push cube if cube? 124 | return cubes 125 | 126 | withRange: (func) -> 127 | {vmin, vmax} = @player.boundingBox() 128 | minx = @toGrid(vmin.x) 129 | miny = @toGrid(vmin.y) 130 | minz = @toGrid(vmin.z) 131 | 132 | maxx = @toGrid(vmax.x + @rad) 133 | maxy = @toGrid(vmax.y + @rad) 134 | maxz = @toGrid(vmax.z + @rad) 135 | x = minx 136 | while x <= maxx 137 | y = miny 138 | while y <= maxy 139 | z = minz 140 | while z <= maxz 141 | func x, y, z 142 | z++ 143 | y++ 144 | x++ 145 | return 146 | 147 | toGrid: (val) -> 148 | ret = Math.floor(val / @rad) 149 | return 0 if ret < 0 150 | return @grid.size - 1 if ret > @grid.size - 1 151 | return ret 152 | 153 | 154 | TextureHelper = 155 | loadTexture: (path) -> 156 | image = new Image() 157 | image.src = path 158 | texture = new Texture(image, new UVMapping(), ClampToEdgeWrapping, ClampToEdgeWrapping, NearestFilter, LinearMipMapLinearFilter) 159 | image.onload = -> texture.needsUpdate = true 160 | new THREE.MeshLambertMaterial(map: texture, ambient: 0xbbbbbb) 161 | 162 | 163 | tileTexture: (path, repeatx, repeaty) -> 164 | image = new Image() 165 | image.src = path 166 | texture = new Texture(image, new UVMapping(), RepeatWrapping, 167 | RepeatWrapping, NearestFilter, LinearMipMapLinearFilter) 168 | texture.repeat.x = repeatx 169 | texture.repeat.y = repeaty 170 | image.onload = -> texture.needsUpdate = true 171 | new THREE.MeshLambertMaterial(map: texture, ambient: 0xbbbbbb) 172 | 173 | 174 | 175 | class Floor 176 | constructor: (width, height) -> 177 | repeatX = width / CubeSize 178 | repeatY = height / CubeSize 179 | material = TextureHelper.tileTexture("./textures/bedrock.png", repeatX, repeatY) 180 | planeGeo = new PlaneGeometry(width, height, 1, 1) 181 | plane = new Mesh(planeGeo, material) 182 | plane.position.y = -1 183 | plane.rotation.x = -Math.PI / 2 184 | plane.name = 'floor' 185 | @plane = plane 186 | 187 | addToScene: (scene) -> scene.add @plane 188 | 189 | 190 | class Game 191 | constructor: (@populateWorldFunction) -> 192 | @rad = CubeSize 193 | @currentMeshSpec = @createGrassGeometry() 194 | @cubeBlocks = @createBlocksGeometry() 195 | @selectCubeBlock 'cobblestone' 196 | @move = {x: 0, z: 0, y: 0} 197 | @keysDown = {} 198 | @grid = new Grid(100) 199 | @onGround = true 200 | @pause = off 201 | @fullscreen = off 202 | @renderer = @createRenderer() 203 | @rendererPosition = $("#minecraft-container canvas").offset() 204 | @camera = @createCamera() 205 | THREEx.WindowResize @renderer, @camera 206 | @canvas = @renderer.domElement 207 | @controls = new Controls @camera, @canvas 208 | @player = new Player() 209 | @scene = new Scene() 210 | new Floor(50000, 50000).addToScene @scene 211 | @scene.add @camera 212 | @addLights @scene 213 | @projector = new Projector() 214 | @castRay = null 215 | @moved = false 216 | @toDelete = null 217 | @collisionHelper = new CollisionHelper(@player, @grid) 218 | @clock = new Clock() 219 | @populateWorld() 220 | @defineControls() 221 | 222 | 223 | width: -> window.innerWidth 224 | height: -> window.innerHeight 225 | 226 | createBlocksGeometry: -> 227 | cubeBlocks = {} 228 | for b in Blocks 229 | geo = new THREE.CubeGeometry @rad, @rad, @rad, 1, 1, 1 230 | t = @texture(b) 231 | cubeBlocks[b] = @meshSpec geo, [t, t, t, t, t, t] 232 | return cubeBlocks 233 | 234 | createGrassGeometry: -> 235 | [grass_dirt, grass, dirt] = @textures "grass_dirt", "grass", "dirt" 236 | materials = [grass_dirt, #right 237 | grass_dirt, # left 238 | grass, # top 239 | dirt, # bottom 240 | grass_dirt, # back 241 | grass_dirt] #front 242 | @meshSpec new THREE.CubeGeometry( @rad, @rad, @rad, 1, 1, 1), materials 243 | 244 | texture: (name) -> TextureHelper.loadTexture "./textures/#{name}.png" 245 | 246 | textures: (names...) -> return (@texture name for name in names) 247 | 248 | gridCoords: (x, y, z) -> @grid.gridCoords x, y, z 249 | 250 | meshSpec: (geometry, material) -> {geometry, material} 251 | 252 | 253 | intoGrid: (x, y, z, val) -> 254 | args = @gridCoords(x, y, z).concat(val) 255 | return @grid.put args... 256 | 257 | 258 | generateHeight: -> 259 | size = 11 260 | data = [] 261 | size.times (i) -> 262 | data[i] = [] 263 | size.times (j) -> 264 | data[i][j] = 0 265 | perlin = new ImprovedNoise() 266 | quality = 0.05 267 | z = Math.random() * 100 268 | 4.times (j) -> 269 | size.times (x) -> 270 | size.times (y) -> 271 | noise = perlin.noise(x / quality, y / quality, z) 272 | data[x][y] += noise * quality 273 | quality *= 4 274 | data 275 | 276 | haveSave: -> !!localStorage["map"] and !!localStorage["position"] and !! localStorage["direction"] 277 | 278 | loadWorld: -> 279 | map = JSON.parse localStorage["map"] 280 | position = JSON.parse localStorage["position"] 281 | direction = JSON.parse localStorage["direction"] 282 | 283 | @player.pos.set position... 284 | @controls.setDirection direction 285 | 286 | for mapYZ,x in map 287 | for mapZ,y in mapYZ 288 | for cubeName,z in mapZ 289 | @cubeAt x,y,z, @cubeBlocks[cubeName] if cubeName 290 | 291 | populateWorld: -> 292 | return @loadWorld() if @haveSave() 293 | middle = @grid.size / 2 294 | data = @generateHeight() 295 | playerHeight = null 296 | for i in [-5..5] 297 | for j in [-5..5] 298 | height =(Math.abs Math.floor(data[i + 5][j + 5])) + 1 299 | playerHeight = (height + 1) * CubeSize if i == 0 and j == 0 300 | height.times (k) => @cubeAt middle + i , k, middle + j 301 | middlePos = middle * CubeSize 302 | @player.pos.set middlePos, playerHeight, middlePos 303 | 304 | 305 | populateWorld2: -> 306 | middle = @grid.size / 2 307 | ret = if @populateWorldFunction? 308 | setblockFunc = (x, y, z, blockName) => 309 | @cubeAt x, y, z, @cubeBlocks[blockName] 310 | @populateWorldFunction setblockFunc, middle 311 | else 312 | [middle, 3, middle] 313 | pos = (i * CubeSize for i in ret) 314 | @player.pos.set pos... 315 | 316 | cubeAt: (x, y, z, meshSpec, validatingFunction) -> 317 | meshSpec or=@currentMeshSpec 318 | raise "bad material" unless meshSpec.geometry? 319 | raise "really bad material" unless meshSpec.material? 320 | mesh = new Mesh(meshSpec.geometry, new THREE.MeshFaceMaterial(meshSpec.material)) 321 | mesh.geometry.dynamic = false 322 | halfcube = CubeSize / 2 323 | mesh.position.set CubeSize * x, y * CubeSize + halfcube, CubeSize * z 324 | mesh.name = "block" 325 | if validatingFunction? 326 | return unless validatingFunction(mesh) 327 | @grid.put x, y, z, mesh 328 | @scene.add mesh 329 | mesh.updateMatrix() 330 | mesh.matrixAutoUpdate = false 331 | return 332 | 333 | createCamera: -> 334 | camera = new PerspectiveCamera(45, @width() / @height(), 1, 10000) 335 | camera.lookAt vec 0, 0, 0 336 | camera 337 | 338 | createRenderer: -> 339 | renderer = new WebGLRenderer(antialias: true) 340 | renderer.setSize @width(), @height() 341 | renderer.setClearColorHex(0xBFD1E5, 1.0) 342 | renderer.clear() 343 | $('#minecraft-container').append(renderer.domElement) 344 | renderer 345 | 346 | addLights: (scene) -> 347 | ambientLight = new AmbientLight(0xaaaaaa) 348 | scene.add ambientLight 349 | directionalLight = new DirectionalLight(0xffffff, 1) 350 | directionalLight.position.set 1, 1, 0.5 351 | directionalLight.position.normalize() 352 | scene.add directionalLight 353 | 354 | defineControls: -> 355 | bindit = (key) => 356 | $(document).bind 'keydown', key, => 357 | @keysDown[key] = true 358 | return false 359 | $(document).bind 'keyup', key, => 360 | @keysDown[key] = false 361 | return false 362 | for key in "wasd".split('').concat('space', 'up', 'down', 'left', 'right') 363 | bindit key 364 | $(document).bind 'keydown', 'p', => @togglePause() 365 | $(document).bind 'keydown', 'k', => @save() 366 | for target in [document, @canvas] 367 | $(target).mousedown (e) => @onMouseDown e 368 | $(target).mouseup (e) => @onMouseUp e 369 | $(target).mousemove (e) => @onMouseMove e 370 | 371 | save: -> 372 | localStorage["map"] = JSON.stringify @grid.map 373 | localStorage["position"] = JSON.stringify [ @player.position("x"),@player.position("y"),@player.position("z")] 374 | localStorage["direction"] = JSON.stringify @controls.getDirection() 375 | 376 | togglePause: -> 377 | @pause = !@pause 378 | @clock.start() if @pause is off 379 | return 380 | 381 | relativePosition: (x, y) -> 382 | [x - @rendererPosition.left, y - @rendererPosition.top] 383 | 384 | onMouseUp: (e) -> 385 | if not @moved and MouseEvent.isLeftButton e 386 | @toDelete = @_targetPosition(e) 387 | @moved = false 388 | 389 | onMouseMove: (event) -> @moved = true 390 | 391 | onMouseDown: (e) -> 392 | @moved = false 393 | return unless MouseEvent.isRightButton e 394 | @castRay = @_targetPosition(e) 395 | 396 | _targetPosition: (e) -> 397 | return @relativePosition(@width() / 2, @height() / 2) if @fullscreen 398 | @relativePosition(e.pageX, e.pageY) 399 | 400 | deleteBlock: -> 401 | return unless @toDelete? 402 | [x, y] = @toDelete 403 | x = (x / @width()) * 2 - 1 404 | y = (-y / @height()) * 2 + 1 405 | vector = vec x, y, 1 406 | @projector.unprojectVector vector, @camera 407 | todir = vector.sub(@camera.position).normalize() 408 | @deleteBlockInGrid new Raycaster @camera.position, todir 409 | @toDelete = null 410 | return 411 | 412 | findBlock: (ray) -> 413 | for o in ray.intersectObjects(@scene.children) 414 | return o unless o.object.name is 'floor' 415 | return null 416 | 417 | 418 | deleteBlockInGrid: (ray) -> 419 | target = @findBlock ray 420 | return unless target? 421 | return unless @withinHandDistance target.object.position 422 | mesh = target.object 423 | @scene.remove mesh 424 | {x, y, z} = mesh.position 425 | @intoGrid x, y, z, null 426 | return 427 | 428 | 429 | placeBlock: -> 430 | return unless @castRay? 431 | [x, y] = @castRay 432 | x = (x / @width()) * 2 - 1 433 | y = (-y / @height()) * 2 + 1 434 | vector = vec x, y, 1 435 | @projector.unprojectVector vector, @camera 436 | todir = vector.sub(@camera.position).normalize() 437 | @placeBlockInGrid new Raycaster @camera.position, todir 438 | @castRay = null 439 | return 440 | 441 | getAdjacentCubePosition: (target) -> 442 | normal = target.face.normal.clone() 443 | p = target.object.position.clone().add normal.multiplyScalar(CubeSize) 444 | return p 445 | 446 | addHalfCube: (p) -> 447 | p.y += CubeSize / 2 448 | p.z += CubeSize / 2 449 | p.x += CubeSize / 2 450 | return p 451 | 452 | getCubeOnFloorPosition: (raycast) -> 453 | ray = raycast.ray 454 | return null if ray.direction.y >= 0 455 | ret = vec() 456 | o = ray.origin 457 | v = ray.direction 458 | t = (-o.y) / v.y 459 | ret.y = 0 460 | ret.x = o.x + t * v.x 461 | ret.z = o.z + t * v.z 462 | return @addHalfCube ret 463 | 464 | selectCubeBlock: (name) -> 465 | @currentCube = @cubeBlocks[name] 466 | 467 | getNewCubePosition: (ray) -> 468 | target = @findBlock ray 469 | return @getCubeOnFloorPosition ray unless target? 470 | return @getAdjacentCubePosition target 471 | 472 | createCubeAt: (x, y, z) -> 473 | @cubeAt x, y, z, @currentCube, (cube) => not @collisionHelper.collideWithCube cube 474 | 475 | handLength: 7 476 | 477 | withinHandDistance: (pos) -> 478 | dist = pos.distanceTo @player.position() 479 | return dist <= CubeSize * @handLength 480 | 481 | placeBlockInGrid: (ray) -> 482 | p = @getNewCubePosition ray 483 | return unless p? 484 | gridPos = @gridCoords p.x, p.y, p.z 485 | [x, y, z] = gridPos 486 | return unless @withinHandDistance p 487 | return unless @grid.insideGrid x, y, z 488 | return if @grid.get(x, y, z)? 489 | @createCubeAt x, y, z 490 | return 491 | 492 | 493 | collides: -> @collisionHelper.collides() 494 | 495 | start: -> 496 | animate = => 497 | @tick() unless @pause 498 | requestAnimationFrame animate, @renderer.domElement 499 | animate() 500 | # PointerLock.init onEnable: @enablePointLock, onDisable: @disablePointLock 501 | # PointerLock.fullScreenLock($("#app canvas").get(0)) 502 | 503 | enablePointLock: => 504 | $("#cursor").show() 505 | # @controls.enableMouseLocked() 506 | @fullscreen = on 507 | 508 | disablePointLock: => 509 | $("#cursor").hide() 510 | # @controls.disableMouseLocked() 511 | @fullscreen = off 512 | 513 | 514 | axes: ['x', 'y', 'z'] 515 | iterationCount: 10 516 | 517 | moveCube: (speedRatio) -> 518 | @defineMove() 519 | iterationCount = Math.round(@iterationCount * speedRatio) 520 | while iterationCount-- > 0 521 | @applyGravity() 522 | for axis in @axes when @move[axis] isnt 0 523 | originalpos = @player.position(axis) 524 | @player.incPosition axis, @move[axis] 525 | if @collides() 526 | @player.setPosition axis, originalpos 527 | @onGround = true if axis is 'y' and @move.y < 0 528 | else if axis is 'y' and @move.y <= 0 529 | @onGround = false 530 | return 531 | 532 | 533 | playerKeys: 534 | w: 'z+' 535 | up: 'z+' 536 | s: 'z-' 537 | down: 'z-' 538 | a: 'x+' 539 | left: 'x+' 540 | d: 'x-' 541 | right: 'x-' 542 | 543 | shouldJump: -> @keysDown.space and @onGround 544 | 545 | defineMove: -> 546 | baseVel = .4 547 | jumpSpeed = .8 548 | @move.x = 0 549 | @move.z = 0 550 | for key, action of @playerKeys 551 | [axis, operation] = action 552 | vel = if operation is '-' then -baseVel else baseVel 553 | @move[axis] += vel if @keysDown[key] 554 | if @shouldJump() 555 | @onGround = false 556 | @move.y = jumpSpeed 557 | @garanteeXYNorm() 558 | @projectMoveOnCamera() 559 | return 560 | 561 | garanteeXYNorm: -> 562 | if @move.x != 0 and @move.z != 0 563 | ratio = Math.cos(Math.PI / 4) 564 | @move.x *= ratio 565 | @move.z *= ratio 566 | return 567 | 568 | projectMoveOnCamera: -> 569 | {x, z} = @controls.viewDirection() 570 | frontDir = new Vector2(x, z).normalize() 571 | rightDir = new Vector2(frontDir.y, -frontDir.x) 572 | frontDir.multiplyScalar @move.z 573 | rightDir.multiplyScalar @move.x 574 | @move.x = frontDir.x + rightDir.x 575 | @move.z = frontDir.y + rightDir.y 576 | 577 | 578 | applyGravity: -> @move.y -= .005 unless @move.y < -1 579 | 580 | setCameraEyes: -> 581 | pos = @player.eyesPosition() 582 | @controls.move pos 583 | eyesDelta = @controls.viewDirection().normalize().multiplyScalar(20) 584 | eyesDelta.y = 0 585 | pos.sub eyesDelta 586 | return 587 | 588 | idealSpeed: 1 / 60 589 | 590 | tick: -> 591 | speedRatio = @clock.getDelta() / @idealSpeed 592 | @placeBlock() 593 | @deleteBlock() 594 | @moveCube speedRatio 595 | @renderer.clear() 596 | @controls.update() 597 | @setCameraEyes() 598 | @renderer.render @scene, @camera 599 | return 600 | 601 | @Minecraft = 602 | start: -> 603 | $("#blocks").hide() 604 | $('#instructions').hide() 605 | $(document).bind "contextmenu", -> false 606 | return Detector.addGetWebGLMessage() unless Detector.webgl 607 | startGame = -> 608 | game = new Game() 609 | new BlockSelection(game).insert() 610 | 611 | $("#minecraft-blocks").show() 612 | window.game = game 613 | game.start() 614 | new Instructions(startGame).insert() 615 | 616 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MineCraftWebGL 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 122 | 123 | 124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /public/instructions/drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/instructions/drag.png -------------------------------------------------------------------------------- /public/instructions/leftclick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/instructions/leftclick.png -------------------------------------------------------------------------------- /public/instructions/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/instructions/pause.png -------------------------------------------------------------------------------- /public/instructions/rightclick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/instructions/rightclick.png -------------------------------------------------------------------------------- /public/instructions/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/instructions/save.png -------------------------------------------------------------------------------- /public/instructions/scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/instructions/scroll.png -------------------------------------------------------------------------------- /public/instructions/space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/instructions/space.png -------------------------------------------------------------------------------- /public/instructions/wasd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/instructions/wasd.png -------------------------------------------------------------------------------- /public/lib/Detector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | */ 5 | 6 | Detector = { 7 | 8 | canvas : !! window.CanvasRenderingContext2D, 9 | webgl : ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(), 10 | workers : !! window.Worker, 11 | fileapi : window.File && window.FileReader && window.FileList && window.Blob, 12 | 13 | getWebGLErrorMessage : function () { 14 | 15 | var domElement = document.createElement( 'div' ); 16 | 17 | domElement.style.fontFamily = 'monospace'; 18 | domElement.style.fontSize = '13px'; 19 | domElement.style.textAlign = 'center'; 20 | domElement.style.background = '#eee'; 21 | domElement.style.color = '#000'; 22 | domElement.style.padding = '1em'; 23 | domElement.style.width = '475px'; 24 | domElement.style.margin = '5em auto 0'; 25 | 26 | if ( ! this.webgl ) { 27 | 28 | domElement.innerHTML = window.WebGLRenderingContext ? [ 29 | 'Your graphics card does not seem to support WebGL.
', 30 | 'Find out how to get it here.' 31 | ].join( '\n' ) : [ 32 | 'Your browser does not seem to support WebGL.
', 33 | 'Find out how to get it here.' 34 | ].join( '\n' ); 35 | 36 | } 37 | 38 | return domElement; 39 | 40 | }, 41 | 42 | addGetWebGLMessage : function ( parameters ) { 43 | 44 | var parent, id, domElement; 45 | 46 | parameters = parameters || {}; 47 | 48 | parent = parameters.parent !== undefined ? parameters.parent : document.body; 49 | id = parameters.id !== undefined ? parameters.id : 'oldie'; 50 | 51 | domElement = Detector.getWebGLErrorMessage(); 52 | domElement.id = id; 53 | 54 | parent.appendChild( domElement ); 55 | 56 | } 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /public/lib/ImprovedNoise.js: -------------------------------------------------------------------------------- 1 | // http://mrl.nyu.edu/~perlin/noise/ 2 | 3 | var ImprovedNoise = function () { 4 | 5 | var p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10, 6 | 23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87, 7 | 174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211, 8 | 133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208, 9 | 89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5, 10 | 202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119, 11 | 248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232, 12 | 178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249, 13 | 14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205, 14 | 93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180]; 15 | 16 | for (var i=0; i < 256 ; i++) { 17 | 18 | p[256+i] = p[i]; 19 | 20 | } 21 | 22 | function fade(t) { 23 | 24 | return t * t * t * (t * (t * 6 - 15) + 10); 25 | 26 | } 27 | 28 | function lerp(t, a, b) { 29 | 30 | return a + t * (b - a); 31 | 32 | } 33 | 34 | function grad(hash, x, y, z) { 35 | 36 | var h = hash & 15; 37 | var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; 38 | return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); 39 | 40 | } 41 | 42 | return { 43 | 44 | noise: function (x, y, z) { 45 | 46 | var floorX = ~~x, floorY = ~~y, floorZ = ~~z; 47 | 48 | var X = floorX & 255, Y = floorY & 255, Z = floorZ & 255; 49 | 50 | x -= floorX; 51 | y -= floorY; 52 | z -= floorZ; 53 | 54 | var xMinus1 = x -1, yMinus1 = y - 1, zMinus1 = z - 1; 55 | 56 | var u = fade(x), v = fade(y), w = fade(z); 57 | 58 | var A = p[X]+Y, AA = p[A]+Z, AB = p[A+1]+Z, B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z; 59 | 60 | return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), 61 | grad(p[BA], xMinus1, y, z)), 62 | lerp(u, grad(p[AB], x, yMinus1, z), 63 | grad(p[BB], xMinus1, yMinus1, z))), 64 | lerp(v, lerp(u, grad(p[AA+1], x, y, zMinus1), 65 | grad(p[BA+1], xMinus1, y, z-1)), 66 | lerp(u, grad(p[AB+1], x, yMinus1, zMinus1), 67 | grad(p[BB+1], xMinus1, yMinus1, zMinus1)))); 68 | 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /public/lib/RequestAnimationFrame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides requestAnimationFrame in a cross browser way. 3 | * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 4 | */ 5 | 6 | if ( !window.requestAnimationFrame ) { 7 | 8 | window.requestAnimationFrame = ( function() { 9 | 10 | return window.webkitRequestAnimationFrame || 11 | window.mozRequestAnimationFrame || 12 | window.oRequestAnimationFrame || 13 | window.msRequestAnimationFrame || 14 | function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { 15 | 16 | window.setTimeout( callback, 1000 / 60 ); 17 | 18 | }; 19 | 20 | } )(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /public/lib/THREEx.WindowResize.js: -------------------------------------------------------------------------------- 1 | // This THREEx helper makes it easy to handle window resize. 2 | // It will update renderer and camera when window is resized. 3 | // 4 | // # Usage 5 | // 6 | // **Step 1**: Start updating renderer and camera 7 | // 8 | // ```var windowResize = THREEx.WindowResize(aRenderer, aCamera)``` 9 | // 10 | // **Step 2**: Start updating renderer and camera 11 | // 12 | // ```windowResize.stop()``` 13 | // # Code 14 | // Source: https://github.com/jeromeetienne/threex/blob/master/THREEx.WindowResize.js 15 | 16 | // 17 | 18 | /** @namespace */ 19 | var THREEx = THREEx || {}; 20 | 21 | /** 22 | * Update renderer and camera when the window is resized 23 | * 24 | * @param {Object} renderer the renderer to update 25 | * @param {Object} Camera the camera to update 26 | */ 27 | THREEx.WindowResize = function(renderer, camera){ 28 | var callback = function(){ 29 | // notify the renderer of the size change 30 | renderer.setSize( window.innerWidth, window.innerHeight ); 31 | // update the camera 32 | camera.aspect = window.innerWidth / window.innerHeight; 33 | camera.updateProjectionMatrix(); 34 | } 35 | // bind the resize event 36 | window.addEventListener('resize', callback, false); 37 | // return .stop() the function to stop watching window resize 38 | return { 39 | /** 40 | * Stop watching window resize 41 | */ 42 | stop : function(){ 43 | window.removeEventListener('resize', callback); 44 | } 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /public/lib/jquery.hotkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Hotkeys Plugin 3 | * Copyright 2010, John Resig 4 | * Dual licensed under the MIT or GPL Version 2 licenses. 5 | * 6 | * Based upon the plugin by Tzury Bar Yochay: 7 | * https://github.com/tzuryby/jquery.hotkeys 8 | * 9 | * Original idea by: 10 | * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ 11 | */ 12 | 13 | (function(jQuery){ 14 | 15 | jQuery.hotkeys = { 16 | version: "0.8", 17 | 18 | specialKeys: { 19 | 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20 | 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 21 | 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 22 | 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 23 | 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 24 | 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 25 | 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" 26 | }, 27 | 28 | shiftNums: { 29 | "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 30 | "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 31 | ".": ">", "/": "?", "\\": "|" 32 | } 33 | }; 34 | 35 | function keyHandler( handleObj ) { 36 | 37 | var stopEvent = function(evt) { 38 | evt.cancelBubble=true; 39 | evt.returnValue=false; 40 | evt.keyCode=0; 41 | return false; 42 | } 43 | 44 | // Only care when a possible input has been specified 45 | if ( typeof handleObj.data !== "string" ) { 46 | return; 47 | } 48 | 49 | var origHandler = handleObj.handler, 50 | keys = handleObj.data.toLowerCase().split(" "); 51 | 52 | handleObj.handler = function( event ) { 53 | // Don't fire in text-accepting inputs that we didn't directly bind to 54 | if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || 55 | event.target.type === "text") ) { 56 | return; 57 | } 58 | 59 | // Keypress represents characters, not special keys 60 | var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], 61 | character = String.fromCharCode( event.which ).toLowerCase(), 62 | key, modif = "", possible = {}; 63 | 64 | // check combinations (alt|ctrl|shift+anything) 65 | if ( event.altKey && special !== "alt" ) { 66 | modif += "alt+"; 67 | } 68 | 69 | if ( event.ctrlKey && special !== "ctrl" ) { 70 | modif += "ctrl+"; 71 | } 72 | 73 | // TODO: Need to make sure this works consistently across platforms 74 | if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { 75 | modif += "meta+"; 76 | } 77 | 78 | if ( event.shiftKey && special !== "shift" ) { 79 | modif += "shift+"; 80 | } 81 | 82 | if ( special ) { 83 | possible[ modif + special ] = true; 84 | 85 | } else { 86 | possible[ modif + character ] = true; 87 | possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; 88 | 89 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 90 | if ( modif === "shift+" ) { 91 | possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; 92 | } 93 | } 94 | 95 | for ( var i = 0, l = keys.length; i < l; i++ ) { 96 | if ( possible[ keys[i] ] ) { 97 | var ret = origHandler.apply( this, arguments ); 98 | stopEvent(event); 99 | return ret; 100 | } 101 | } 102 | stopEvent(event); 103 | 104 | }; 105 | } 106 | 107 | jQuery.each([ "keydown", "keyup", "keypress" ], function() { 108 | jQuery.event.special[ this ] = {add: keyHandler}; 109 | }); 110 | 111 | })( jQuery ); 112 | -------------------------------------------------------------------------------- /public/lib/jquery.mousewheel.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net) 2 | * Licensed under the MIT License (LICENSE.txt). 3 | * 4 | * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. 5 | * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. 6 | * Thanks to: Seamus Leahy for adding deltaX and deltaY 7 | * 8 | * Version: 3.0.6 9 | * 10 | * Requires: 1.2.2+ 11 | */ 12 | 13 | (function($) { 14 | 15 | var types = ['DOMMouseScroll', 'mousewheel']; 16 | 17 | if ($.event.fixHooks) { 18 | for ( var i=types.length; i; ) { 19 | $.event.fixHooks[ types[--i] ] = $.event.mouseHooks; 20 | } 21 | } 22 | 23 | $.event.special.mousewheel = { 24 | setup: function() { 25 | if ( this.addEventListener ) { 26 | for ( var i=types.length; i; ) { 27 | this.addEventListener( types[--i], handler, false ); 28 | } 29 | } else { 30 | this.onmousewheel = handler; 31 | } 32 | }, 33 | 34 | teardown: function() { 35 | if ( this.removeEventListener ) { 36 | for ( var i=types.length; i; ) { 37 | this.removeEventListener( types[--i], handler, false ); 38 | } 39 | } else { 40 | this.onmousewheel = null; 41 | } 42 | } 43 | }; 44 | 45 | $.fn.extend({ 46 | mousewheel: function(fn) { 47 | return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); 48 | }, 49 | 50 | unmousewheel: function(fn) { 51 | return this.unbind("mousewheel", fn); 52 | } 53 | }); 54 | 55 | 56 | function handler(event) { 57 | var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0; 58 | event = $.event.fix(orgEvent); 59 | event.type = "mousewheel"; 60 | 61 | // Old school scrollwheel delta 62 | if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; } 63 | if ( orgEvent.detail ) { delta = -orgEvent.detail/3; } 64 | 65 | // New school multidimensional scroll (touchpads) deltas 66 | deltaY = delta; 67 | 68 | // Gecko 69 | if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { 70 | deltaY = 0; 71 | deltaX = -1*delta; 72 | } 73 | 74 | // Webkit 75 | if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; } 76 | if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; } 77 | 78 | // Add event and delta to the front of the arguments 79 | args.unshift(event, delta, deltaX, deltaY); 80 | 81 | return ($.event.dispatch || $.event.handle).apply(this, args); 82 | } 83 | 84 | })(jQuery); 85 | -------------------------------------------------------------------------------- /public/lib/pointer.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | (function() { 3 | this.PointerLock = { 4 | _initialized: false, 5 | init: function(callbackObj) { 6 | var onDisable, onEnable, self; 7 | if (callbackObj == null) { 8 | callbackObj = {}; 9 | } 10 | if (this._initialized) { 11 | return; 12 | } 13 | this._initialized = true; 14 | this.enabled = false; 15 | this.container = null; 16 | self = this; 17 | onEnable = callbackObj.onEnable, onDisable = callbackObj.onDisable; 18 | return ["", "moz", "webkit"].forEach(function(prefix) { 19 | var changeCallback; 20 | changeCallback = function(e) { 21 | self.enabled = !self.enabled; 22 | if (self.enabled) { 23 | return typeof onEnable === "function" ? onEnable() : void 0; 24 | } else { 25 | return typeof onDisable === "function" ? onDisable() : void 0; 26 | } 27 | }; 28 | return document.addEventListener(prefix + 'pointerlockchange', changeCallback, false); 29 | }); 30 | }, 31 | fullScreenLock: function(container) { 32 | var onFirefox, onScreenChange; 33 | if (this.enabled) { 34 | return; 35 | } 36 | container.fullScreenLock = container.requestPointerLock || container.mozRequestPointerLock || container.webkitRequestPointerLock; 37 | onFirefox = container.mozRequestFullScreen != null; 38 | if (onFirefox) { 39 | // if (this.container !== container) { 40 | // onScreenChange = function() { 41 | // if (document.mozFullScreenElement === container) { 42 | // return container.fullScreenLock(); 43 | // } 44 | // }; 45 | // document.addEventListener("mozfullscreenchange", onScreenChange, false); 46 | // } 47 | // container.mozRequestFullScreen(); 48 | } else { 49 | container.fullScreenLock(); 50 | container.webkitRequestFullScreen(); 51 | } 52 | return this.container = container; 53 | }, 54 | lock: function(container, callbackObj) { 55 | this.init(callbackObj); 56 | return this.fullScreenLock(container); 57 | } 58 | }; 59 | 60 | }).call(this); 61 | -------------------------------------------------------------------------------- /public/lib/rbcoffee.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var abstract_method, abstract_property, callIt, clone, define, eq, isAbstract, methods, methodsOfInstance, methodsOfInstanceWhile, methodsWhile, mixin, mixinWith, patch, puts, raise; 3 | var __slice = Array.prototype.slice, __hasProp = Object.prototype.hasOwnProperty; 4 | puts = function() { 5 | var _i, _len, _ref, arg, args; 6 | args = __slice.call(arguments, 0); 7 | if (!(this["console"])) { 8 | return null; 9 | } 10 | _ref = args; 11 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 12 | arg = _ref[_i]; 13 | console.error(arg); 14 | } 15 | return null; 16 | }; 17 | raise = function(message) { 18 | throw new Error(message); 19 | }; 20 | abstract_method = function() { 21 | return raise("Subclass responsability"); 22 | }; 23 | abstract_property = function() { 24 | return raise("Abstract property"); 25 | }; 26 | clone = function(obj) { 27 | var _ref, k, ret, v; 28 | if (!(typeof obj !== "undefined" && obj !== null)) { 29 | return obj; 30 | } 31 | ret = {}; 32 | _ref = obj; 33 | for (k in _ref) { 34 | if (!__hasProp.call(_ref, k)) continue; 35 | v = _ref[k]; 36 | ret[k] = v; 37 | } 38 | return ret; 39 | }; 40 | eq = function(x, y) { 41 | return x == y; 42 | }; 43 | define = function(clas, methodName, func) { 44 | return (clas.prototype[methodName] = func); 45 | }; 46 | patch = function(clas, mixed) { 47 | var _ref, method, name; 48 | _ref = mixed; 49 | for (name in _ref) { 50 | if (!__hasProp.call(_ref, name)) continue; 51 | method = _ref[name]; 52 | (define(clas, name, method)); 53 | } 54 | return null; 55 | }; 56 | isAbstract = function(m) { 57 | return m === abstract_method || m === abstract_property; 58 | }; 59 | mixinWith = function(clas, mixed) { 60 | var _ref, _ref2, m, name; 61 | _ref = mixed; 62 | for (name in _ref) { 63 | if (!__hasProp.call(_ref, name)) continue; 64 | m = _ref[name]; 65 | if (!(isAbstract(m) || ((typeof (_ref2 = clas.prototype[name]) !== "undefined" && _ref2 !== null) && !isAbstract(clas.prototype[name])))) { 66 | define(clas, name, m); 67 | } 68 | } 69 | return null; 70 | }; 71 | mixin = function(clas) { 72 | var _i, _len, _ref, trait, traits; 73 | traits = __slice.call(arguments, 1); 74 | _ref = traits; 75 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 76 | trait = _ref[_i]; 77 | mixinWith(clas, trait); 78 | } 79 | return null; 80 | }; 81 | methods = function(clas) { 82 | var _i, _ref, _result, c, ret; 83 | ret = (function() { 84 | _result = []; _ref = clas.prototype; 85 | for (c in _ref) { 86 | if (!__hasProp.call(_ref, c)) continue; 87 | _i = _ref[c]; 88 | _result.push(c); 89 | } 90 | return _result; 91 | })(); 92 | if (!(clas.__super__)) { 93 | return ret; 94 | } 95 | return ret.concat(methods(clas.__super__.constructor)); 96 | }; 97 | methodsWhile = function(clas, func) { 98 | var _i, _ref, _result, c, ret; 99 | if (!(func(clas))) { 100 | return []; 101 | } 102 | ret = (function() { 103 | _result = []; _ref = clas.prototype; 104 | for (c in _ref) { 105 | if (!__hasProp.call(_ref, c)) continue; 106 | _i = _ref[c]; 107 | _result.push(c); 108 | } 109 | return _result; 110 | })(); 111 | if (!(clas.__super__)) { 112 | return ret; 113 | } 114 | return ret.concat(methodsWhile(clas.__super__.constructor, func)); 115 | }; 116 | methodsOfInstance = function(instance) { 117 | return methods(instance.constructor); 118 | }; 119 | methodsOfInstanceWhile = function(instance, func) { 120 | return methodsWhile(instance.constructor, func); 121 | }; 122 | callIt = function(f) { 123 | return f(); 124 | }; 125 | window.abstract_method = abstract_method 126 | window.abstract_property = abstract_property 127 | window.callIt = callIt 128 | window.clone = clone 129 | window.define = define 130 | window.eq = eq 131 | window.isAbstract = isAbstract 132 | window.methods = methods 133 | window.methodsOfInstance = methodsOfInstance 134 | window.methodsOfInstanceWhile = methodsOfInstanceWhile 135 | window.methodsWhile = methodsWhile 136 | window.mixin = mixin 137 | window.mixinWith = mixinWith 138 | window.patch = patch 139 | window.puts = puts 140 | window.raise = raise 141 | }).call(this); 142 | -------------------------------------------------------------------------------- /public/spec/coffee/applicationSpec.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var cube, importAll, isFalse, isTrue, same, vec; 3 | var __hasProp = Object.prototype.hasOwnProperty; 4 | importAll = function(from) { 5 | var _i, _ref, _result, i; 6 | _result = []; _ref = from; 7 | for (i in _ref) { 8 | if (!__hasProp.call(_ref, i)) continue; 9 | _i = _ref[i]; 10 | _result.push(global[i] = from[i]); 11 | } 12 | return _result; 13 | }; 14 | same = function(thi, that) { 15 | return expect(thi).toEqual(that); 16 | }; 17 | isTrue = function(val) { 18 | return same(val, true); 19 | }; 20 | isFalse = function(val) { 21 | return same(val, false); 22 | }; 23 | require('specBrowserAdapter'); 24 | require('lib/rbcoffee'); 25 | require('collision'); 26 | cube = function(vmin, vmax) { 27 | return { 28 | vmin: vmin, 29 | vmax: vmax 30 | }; 31 | }; 32 | vec = function(x, y, z) { 33 | return { 34 | x: x, 35 | y: y, 36 | z: z 37 | }; 38 | }; 39 | describe("Intersection utils", function() { 40 | it("can decide interval collision", function() { 41 | var collides; 42 | collides = CollisionUtils.testIntervalCollision; 43 | isTrue(collides(0, 9, 6, 12)); 44 | isTrue(collides(1, 5, 2, 3)); 45 | isTrue(collides(1, 5, 1, 10)); 46 | isFalse(collides(1, 5, 6, 10)); 47 | return isFalse(collides(6, 10, 1, 5)); 48 | }); 49 | it("can decide when unrotated cubes don't collide ", function() { 50 | var collides, cube1, cube2; 51 | collides = CollisionUtils.testCubeCollision; 52 | cube1 = cube(vec(0, 0, 0), vec(50, 50, 50)); 53 | cube2 = cube(vec(60, 60, 60), vec(150, 150, 150)); 54 | return isFalse(collides(cube1, cube2)); 55 | }); 56 | return it("can decide when unrotated cubes do collide ", function() { 57 | var collides, cube1, cube2; 58 | collides = CollisionUtils.testCubeCollision; 59 | cube1 = cube(vec(822.5, 43, 22.5), vec(847.5, 123, 47.5)); 60 | cube2 = cube(vec(775, 50, 25), vec(825, 100, 75)); 61 | return isTrue(collides(cube1, cube2)); 62 | }); 63 | }); 64 | window.cube = cube 65 | window.importAll = importAll 66 | window.isFalse = isFalse 67 | window.isTrue = isTrue 68 | window.same = same 69 | window.vec = vec 70 | }).call(this); 71 | -------------------------------------------------------------------------------- /public/textures/bedrock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/bedrock.png -------------------------------------------------------------------------------- /public/textures/bluewool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/bluewool.png -------------------------------------------------------------------------------- /public/textures/bluewoolicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/bluewoolicon.png -------------------------------------------------------------------------------- /public/textures/brick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/brick.png -------------------------------------------------------------------------------- /public/textures/brickicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/brickicon.png -------------------------------------------------------------------------------- /public/textures/cobblestone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/cobblestone.png -------------------------------------------------------------------------------- /public/textures/cobblestoneicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/cobblestoneicon.png -------------------------------------------------------------------------------- /public/textures/diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/diamond.png -------------------------------------------------------------------------------- /public/textures/diamondicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/diamondicon.png -------------------------------------------------------------------------------- /public/textures/dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/dirt.png -------------------------------------------------------------------------------- /public/textures/glowstone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/glowstone.png -------------------------------------------------------------------------------- /public/textures/glowstoneicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/glowstoneicon.png -------------------------------------------------------------------------------- /public/textures/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/grass.png -------------------------------------------------------------------------------- /public/textures/grass_dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/grass_dirt.png -------------------------------------------------------------------------------- /public/textures/netherrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/netherrack.png -------------------------------------------------------------------------------- /public/textures/netherrackicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/netherrackicon.png -------------------------------------------------------------------------------- /public/textures/obsidian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/obsidian.png -------------------------------------------------------------------------------- /public/textures/obsidianicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/obsidianicon.png -------------------------------------------------------------------------------- /public/textures/plank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/plank.png -------------------------------------------------------------------------------- /public/textures/plankicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/plankicon.png -------------------------------------------------------------------------------- /public/textures/redwool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/redwool.png -------------------------------------------------------------------------------- /public/textures/redwoolicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/redwoolicon.png -------------------------------------------------------------------------------- /public/textures/whitewool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/whitewool.png -------------------------------------------------------------------------------- /public/textures/whitewoolicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielribeiro/WebGLCraft/c74a2fbdbe445a48abf259a7839459a968a0f0af/public/textures/whitewoolicon.png -------------------------------------------------------------------------------- /spec/coffee/applicationSpec.coffee: -------------------------------------------------------------------------------- 1 | importAll = (from) -> 2 | for i of from 3 | global[i] = from[i] 4 | 5 | same = (thi, that) -> expect(thi).toEqual(that) 6 | isTrue = (val) -> same val, true 7 | isFalse = (val) -> same val, false 8 | 9 | cube = (vmin, vmax) -> {vmin, vmax} 10 | vec = (x, y, z) -> {x, y, z} 11 | 12 | 13 | describe "Intersection utils", -> 14 | it "can decide interval collision", -> 15 | collides = (args...) -> CollisionUtils.testIntervalCollision(args...) 16 | isTrue collides(0, 9, 6, 12) 17 | isTrue collides(1, 5, 2, 3) 18 | isTrue collides(1, 5, 1, 10) 19 | isFalse collides(1, 5, 6, 10) 20 | isFalse collides(6, 10, 1, 5) 21 | 22 | 23 | it "can decide when unrotated cubes don't collide ", -> 24 | cube1 = cube(vec(0, 0, 0), vec(50, 50, 50)) 25 | cube2 = cube(vec(60, 60, 60), vec(150, 150, 150)) 26 | isFalse CollisionUtils.testCubeCollision(cube1, cube2) 27 | 28 | 29 | it "can decide when unrotated cubes do collide ", -> 30 | cube1 = cube(vec(822.5, 43, 22.5), vec(847.5, 123, 47.5)) 31 | cube2 = cube(vec(775, 50, 25), vec(825, 100, 75)) 32 | isTrue CollisionUtils.testCubeCollision(cube1, cube2) 33 | 34 | -------------------------------------------------------------------------------- /spec/jasmine/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /spec/jasmine/README.md: -------------------------------------------------------------------------------- 1 | jasmine-node 2 | ====== 3 | 4 | This node.js module makes the wonderful Pivotal Lab's jasmine (http://github.com/pivotal/jasmine) spec framework available in node.js. 5 | 6 | Checkout specs.js in the rood directory and spec/SampleSpecs.js to see how to use it. 7 | -------------------------------------------------------------------------------- /spec/jasmine/lib/jasmine/TrivialReporter.js: -------------------------------------------------------------------------------- 1 | jasmine.TrivialReporter = function(doc) { 2 | this.document = doc || document; 3 | this.suiteDivs = {}; 4 | }; 5 | 6 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 7 | var el = document.createElement(type); 8 | 9 | for (var i = 2; i < arguments.length; i++) { 10 | var child = arguments[i]; 11 | 12 | if (typeof child === 'string') { 13 | el.appendChild(document.createTextNode(child)); 14 | } else { 15 | if (child) { el.appendChild(child); } 16 | } 17 | } 18 | 19 | for (var attr in attrs) { 20 | el[attr] = attrs[attr]; 21 | } 22 | 23 | return el; 24 | }; 25 | 26 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 27 | var suites = runner.suites(); 28 | 29 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 30 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 31 | this.runnerMessageSpan = this.createDom('span', {}, "Running...")); 32 | this.document.body.appendChild(this.runnerDiv); 33 | 34 | for (var i = 0; i < suites.length; i++) { 35 | var suite = suites[i]; 36 | var suiteDiv = this.createDom('div', { className: 'suite' }, 37 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 38 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 39 | this.suiteDivs[suite.getFullName()] = suiteDiv; 40 | var parentDiv = this.document.body; 41 | if (suite.parentSuite) { 42 | parentDiv = this.suiteDivs[suite.parentSuite.getFullName()]; 43 | } 44 | parentDiv.appendChild(suiteDiv); 45 | } 46 | 47 | this.startedAt = new Date(); 48 | }; 49 | 50 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 51 | var results = runner.results(); 52 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 53 | this.runnerDiv.setAttribute("class", className); 54 | //do it twice for IE 55 | this.runnerDiv.setAttribute("className", className); 56 | var specs = runner.specs(); 57 | var specCount = 0; 58 | for (var i = 0; i < specs.length; i++) { 59 | if (this.specFilter(specs[i])) { 60 | specCount++; 61 | } 62 | } 63 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 64 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 65 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 66 | }; 67 | 68 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 69 | var results = suite.results(); 70 | var status = results.passed() ? 'passed' : 'failed'; 71 | if (results.totalCount == 0) { // todo: change this to check results.skipped 72 | status = 'skipped'; 73 | } 74 | this.suiteDivs[suite.getFullName()].className += " " + status; 75 | }; 76 | 77 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 78 | var results = spec.results(); 79 | var status = results.passed() ? 'passed' : 'failed'; 80 | if (results.skipped) { 81 | status = 'skipped'; 82 | } 83 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 84 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 85 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, spec.getFullName())); 86 | 87 | 88 | var resultItems = results.getItems(); 89 | for (var i = 0; i < resultItems.length; i++) { 90 | var result = resultItems[i]; 91 | if (result.passed && !result.passed()) { 92 | specDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 93 | specDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 94 | } 95 | } 96 | this.suiteDivs[spec.suite.getFullName()].appendChild(specDiv); 97 | }; 98 | 99 | jasmine.TrivialReporter.prototype.log = function() { 100 | console.log.apply(console, arguments); 101 | }; 102 | 103 | jasmine.TrivialReporter.prototype.getLocation = function() { 104 | return this.document.location; 105 | }; 106 | 107 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 108 | var paramMap = {}; 109 | var params = this.getLocation().search.substring(1).split('&'); 110 | for (var i = 0; i < params.length; i++) { 111 | var p = params[i].split('='); 112 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 113 | } 114 | 115 | if (!paramMap["spec"]) return true; 116 | return spec.getFullName().indexOf(paramMap["spec"]) == 0; 117 | }; -------------------------------------------------------------------------------- /spec/jasmine/lib/jasmine/consolex.js: -------------------------------------------------------------------------------- 1 | /** Console X 2 | * http://github.com/deadlyicon/consolex.js 3 | * 4 | * By Jared Grippe 5 | * 6 | * Copyright (c) 2009 Jared Grippe 7 | * Licensed under the MIT license. 8 | * 9 | * consolex avoids ever having to see javascript bugs in browsers that do not implement the entire 10 | * firebug console suit 11 | * 12 | */ 13 | (function(window) { 14 | window.console || (window.console = {}); 15 | 16 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", 17 | "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; 18 | 19 | function emptyFunction(){} 20 | 21 | for (var i = 0; i < names.length; ++i){ 22 | window.console[names[i]] || (window.console[names[i]] = emptyFunction); 23 | if (typeof window.console[names[i]] !== 'function') 24 | window.console[names[i]] = (function(method) { 25 | return function(){ return Function.prototype.apply.apply(method, [console,arguments]); }; 26 | })(window.console[names[i]]); 27 | } 28 | })(this); -------------------------------------------------------------------------------- /spec/jasmine/lib/jasmine/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var sys = require('util'); 3 | 4 | var filename = __dirname + '/jasmine-0.10.2.js'; 5 | global.window = { 6 | setTimeout: setTimeout, 7 | clearTimeout: clearTimeout, 8 | setInterval: setInterval, 9 | clearInterval: clearInterval 10 | }; 11 | var src = fs.readFileSync(filename); 12 | var jasmine = require('vm').runInThisContext(src + '\njasmine;', filename); 13 | delete global.window; 14 | 15 | function noop(){} 16 | 17 | jasmine.executeSpecsInFolder = function(folder, done, isVerbose, showColors){ 18 | var log = []; 19 | var columnCounter = 0; 20 | var start = 0; 21 | var elapsed = 0; 22 | var verbose = isVerbose || false; 23 | var colors = showColors || false; 24 | var specs = jasmine.getAllSpecFiles(folder); 25 | 26 | var ansi = { 27 | green: '\033[32m', 28 | red: '\033[31m', 29 | yellow: '\033[33m', 30 | none: '\033[0m' 31 | }; 32 | 33 | for (var i = 0, len = specs.length; i < len; ++i){ 34 | var filename = specs[i]; 35 | require(filename.replace(/\.js$/, "")); 36 | } 37 | 38 | var jasmineEnv = jasmine.getEnv(); 39 | jasmineEnv.reporter = { 40 | log: function(str){ 41 | }, 42 | 43 | reportRunnerStarting: function(runner) { 44 | sys.puts('Started'); 45 | start = Number(new Date); 46 | }, 47 | 48 | reportSuiteResults: function(suite) { 49 | var specResults = suite.results(); 50 | var path = []; 51 | while(suite) { 52 | path.unshift(suite.description); 53 | suite = suite.parentSuite; 54 | } 55 | var description = path.join(' '); 56 | 57 | if (verbose) 58 | log.push('Spec ' + description); 59 | 60 | specResults.items_.forEach(function(spec){ 61 | if (spec.failedCount > 0 && spec.description) { 62 | if (!verbose) 63 | log.push(description); 64 | log.push(' it ' + spec.description); 65 | spec.items_.forEach(function(result){ 66 | log.push(' ' + result.trace.stack + '\n'); 67 | }); 68 | } 69 | }); 70 | }, 71 | 72 | reportSpecResults: function(spec) { 73 | var result = spec.results(); 74 | var msg = ''; 75 | if (result.passed()) 76 | { 77 | msg = (colors) ? (ansi.green + '.' + ansi.none) : '.'; 78 | // } else if (result.skipped) { TODO: Research why "result.skipped" returns false when "xit" is called on a spec? 79 | // msg = (colors) ? (ansi.yellow + '*' + ansi.none) : '*'; 80 | } else { 81 | msg = (colors) ? (ansi.red + 'F' + ansi.none) : 'F'; 82 | } 83 | sys.print(msg); 84 | if (columnCounter++ < 50) return; 85 | columnCounter = 0; 86 | sys.print('\n'); 87 | }, 88 | 89 | 90 | reportRunnerResults: function(runner) { 91 | elapsed = (Number(new Date) - start) / 1000; 92 | sys.puts('\n'); 93 | log.forEach(function(log){ 94 | sys.puts(log); 95 | }); 96 | sys.puts('Finished in ' + elapsed + ' seconds'); 97 | 98 | var summary = jasmine.printRunnerResults(runner); 99 | if(colors) 100 | { 101 | if(runner.results().failedCount === 0 ) 102 | sys.puts(ansi.green + summary + ansi.none); 103 | else 104 | sys.puts(ansi.red + summary + ansi.none); 105 | } else { 106 | sys.puts(summary); 107 | } 108 | (done||noop)(runner, log); 109 | } 110 | }; 111 | jasmineEnv.execute(); 112 | }; 113 | 114 | jasmine.getAllSpecFiles = function(dir){ 115 | var files = fs.readdirSync(dir); 116 | var specs = []; 117 | 118 | for (var i = 0, len = files.length; i < len; ++i){ 119 | var filename = dir + '/' + files[i]; 120 | if (fs.statSync(filename).isFile() && filename.match(/\.js$/)){ 121 | specs.push(filename); 122 | }else if (fs.statSync(filename).isDirectory()){ 123 | var subfiles = this.getAllSpecFiles(filename); 124 | subfiles.forEach(function(result){ 125 | specs.push(result); 126 | }); 127 | } 128 | } 129 | return specs; 130 | }; 131 | 132 | jasmine.printRunnerResults = function(runner){ 133 | var results = runner.results(); 134 | var suites = runner.suites(); 135 | var msg = ''; 136 | msg += suites.length + ' test' + ((suites.length === 1) ? '' : 's') + ', '; 137 | msg += results.totalCount + ' assertion' + ((results.totalCount === 1) ? '' : 's') + ', '; 138 | msg += results.failedCount + ' failure' + ((results.failedCount === 1) ? '' : 's') + '\n'; 139 | return msg; 140 | }; 141 | 142 | function now(){ 143 | return new Date().getTime(); 144 | } 145 | 146 | jasmine.asyncSpecWait = function(){ 147 | var wait = jasmine.asyncSpecWait; 148 | wait.start = now(); 149 | wait.done = false; 150 | (function innerWait(){ 151 | waits(10); 152 | runs(function() { 153 | if (wait.start + wait.timeout < now()) { 154 | expect('timeout waiting for spec').toBeNull(); 155 | } else if (wait.done) { 156 | wait.done = false; 157 | } else { 158 | innerWait(); 159 | } 160 | }); 161 | })(); 162 | }; 163 | jasmine.asyncSpecWait.timeout = 4 * 1000; 164 | jasmine.asyncSpecDone = function(){ 165 | jasmine.asyncSpecWait.done = true; 166 | }; 167 | 168 | for ( var key in jasmine) { 169 | exports[key] = jasmine[key]; 170 | } 171 | -------------------------------------------------------------------------------- /spec/jasmine/lib/jasmine/jasmine-0.10.2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. 3 | * 4 | * @namespace 5 | */ 6 | var jasmine = {}; 7 | 8 | /** 9 | * @private 10 | */ 11 | jasmine.unimplementedMethod_ = function() { 12 | throw new Error("unimplemented method"); 13 | }; 14 | 15 | /** 16 | * Use jasmine.undefined instead of undefined, since undefined 0; 146 | }; 147 | 148 | /** 149 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 150 | * 151 | * @example 152 | * // don't care about which function is passed in, as long as it's a function 153 | * expect(mySpy).wasCalledWith(jasmine.any(Function)); 154 | * 155 | * @param {Class} clazz 156 | * @returns matchable object of the type clazz 157 | */ 158 | jasmine.any = function(clazz) { 159 | return new jasmine.Matchers.Any(clazz); 160 | }; 161 | 162 | /** 163 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 164 | * 165 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 166 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 167 | * 168 | * A Spy has the following mehtod: wasCalled, callCount, mostRecentCall, and argsForCall (see docs) 169 | * Spies are torn down at the end of every spec. 170 | * 171 | * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 172 | * 173 | * @example 174 | * // a stub 175 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 176 | * 177 | * // spy example 178 | * var foo = { 179 | * not: function(bool) { return !bool; } 180 | * } 181 | * 182 | * // actual foo.not will not be called, execution stops 183 | * spyOn(foo, 'not'); 184 | 185 | // foo.not spied upon, execution will continue to implementation 186 | * spyOn(foo, 'not').andCallThrough(); 187 | * 188 | * // fake example 189 | * var foo = { 190 | * not: function(bool) { return !bool; } 191 | * } 192 | * 193 | * // foo.not(val) will return val 194 | * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 195 | * 196 | * // mock example 197 | * foo.not(7 == 7); 198 | * expect(foo.not).wasCalled(); 199 | * expect(foo.not).wasCalledWith(true); 200 | * 201 | * @constructor 202 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 203 | * @param {String} name 204 | */ 205 | jasmine.Spy = function(name) { 206 | /** 207 | * The name of the spy, if provided. 208 | */ 209 | this.identity = name || 'unknown'; 210 | /** 211 | * Is this Object a spy? 212 | */ 213 | this.isSpy = true; 214 | /** 215 | * The actual function this spy stubs. 216 | */ 217 | this.plan = function() { 218 | }; 219 | /** 220 | * Tracking of the most recent call to the spy. 221 | * @example 222 | * var mySpy = jasmine.createSpy('foo'); 223 | * mySpy(1, 2); 224 | * mySpy.mostRecentCall.args = [1, 2]; 225 | */ 226 | this.mostRecentCall = {}; 227 | 228 | /** 229 | * Holds arguments for each call to the spy, indexed by call count 230 | * @example 231 | * var mySpy = jasmine.createSpy('foo'); 232 | * mySpy(1, 2); 233 | * mySpy(7, 8); 234 | * mySpy.mostRecentCall.args = [7, 8]; 235 | * mySpy.argsForCall[0] = [1, 2]; 236 | * mySpy.argsForCall[1] = [7, 8]; 237 | */ 238 | this.argsForCall = []; 239 | this.calls = []; 240 | }; 241 | 242 | /** 243 | * Tells a spy to call through to the actual implemenatation. 244 | * 245 | * @example 246 | * var foo = { 247 | * bar: function() { // do some stuff } 248 | * } 249 | * 250 | * // defining a spy on an existing property: foo.bar 251 | * spyOn(foo, 'bar').andCallThrough(); 252 | */ 253 | jasmine.Spy.prototype.andCallThrough = function() { 254 | this.plan = this.originalValue; 255 | return this; 256 | }; 257 | 258 | /** 259 | * For setting the return value of a spy. 260 | * 261 | * @example 262 | * // defining a spy from scratch: foo() returns 'baz' 263 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 264 | * 265 | * // defining a spy on an existing property: foo.bar() returns 'baz' 266 | * spyOn(foo, 'bar').andReturn('baz'); 267 | * 268 | * @param {Object} value 269 | */ 270 | jasmine.Spy.prototype.andReturn = function(value) { 271 | this.plan = function() { 272 | return value; 273 | }; 274 | return this; 275 | }; 276 | 277 | /** 278 | * For throwing an exception when a spy is called. 279 | * 280 | * @example 281 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 282 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 283 | * 284 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 285 | * spyOn(foo, 'bar').andThrow('baz'); 286 | * 287 | * @param {String} exceptionMsg 288 | */ 289 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 290 | this.plan = function() { 291 | throw exceptionMsg; 292 | }; 293 | return this; 294 | }; 295 | 296 | /** 297 | * Calls an alternate implementation when a spy is called. 298 | * 299 | * @example 300 | * var baz = function() { 301 | * // do some stuff, return something 302 | * } 303 | * // defining a spy from scratch: foo() calls the function baz 304 | * var foo = jasmine.createSpy('spy on foo').andCall(baz); 305 | * 306 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function 307 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 308 | * 309 | * @param {Function} fakeFunc 310 | */ 311 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 312 | this.plan = fakeFunc; 313 | return this; 314 | }; 315 | 316 | /** 317 | * Resets all of a spy's the tracking variables so that it can be used again. 318 | * 319 | * @example 320 | * spyOn(foo, 'bar'); 321 | * 322 | * foo.bar(); 323 | * 324 | * expect(foo.bar.callCount).toEqual(1); 325 | * 326 | * foo.bar.reset(); 327 | * 328 | * expect(foo.bar.callCount).toEqual(0); 329 | */ 330 | jasmine.Spy.prototype.reset = function() { 331 | this.wasCalled = false; 332 | this.callCount = 0; 333 | this.argsForCall = []; 334 | this.calls = []; 335 | this.mostRecentCall = {}; 336 | }; 337 | 338 | jasmine.createSpy = function(name) { 339 | 340 | var spyObj = function() { 341 | spyObj.wasCalled = true; 342 | spyObj.callCount++; 343 | var args = jasmine.util.argsToArray(arguments); 344 | spyObj.mostRecentCall.object = this; 345 | spyObj.mostRecentCall.args = args; 346 | spyObj.argsForCall.push(args); 347 | spyObj.calls.push({object: this, args: args}); 348 | return spyObj.plan.apply(this, arguments); 349 | }; 350 | 351 | var spy = new jasmine.Spy(name); 352 | 353 | for (var prop in spy) { 354 | spyObj[prop] = spy[prop]; 355 | } 356 | 357 | spyObj.reset(); 358 | 359 | return spyObj; 360 | }; 361 | 362 | /** 363 | * Determines whether an object is a spy. 364 | * 365 | * @param {jasmine.Spy|Object} putativeSpy 366 | * @returns {Boolean} 367 | */ 368 | jasmine.isSpy = function(putativeSpy) { 369 | return putativeSpy && putativeSpy.isSpy; 370 | }; 371 | 372 | /** 373 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 374 | * large in one call. 375 | * 376 | * @param {String} baseName name of spy class 377 | * @param {Array} methodNames array of names of methods to make spies 378 | */ 379 | jasmine.createSpyObj = function(baseName, methodNames) { 380 | if (!jasmine.isArray_(methodNames) || methodNames.length == 0) { 381 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 382 | } 383 | var obj = {}; 384 | for (var i = 0; i < methodNames.length; i++) { 385 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 386 | } 387 | return obj; 388 | }; 389 | 390 | jasmine.log = function(message) { 391 | jasmine.getEnv().currentSpec.log(message); 392 | }; 393 | 394 | /** 395 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 396 | * 397 | * @example 398 | * // spy example 399 | * var foo = { 400 | * not: function(bool) { return !bool; } 401 | * } 402 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 403 | * 404 | * @see jasmine.createSpy 405 | * @param obj 406 | * @param methodName 407 | * @returns a Jasmine spy that can be chained with all spy methods 408 | */ 409 | var spyOn = function(obj, methodName) { 410 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 411 | }; 412 | 413 | /** 414 | * Creates a Jasmine spec that will be added to the current suite. 415 | * 416 | * // TODO: pending tests 417 | * 418 | * @example 419 | * it('should be true', function() { 420 | * expect(true).toEqual(true); 421 | * }); 422 | * 423 | * @param {String} desc description of this specification 424 | * @param {Function} func defines the preconditions and expectations of the spec 425 | */ 426 | var it = function(desc, func) { 427 | return jasmine.getEnv().it(desc, func); 428 | }; 429 | 430 | /** 431 | * Creates a disabled Jasmine spec. 432 | * 433 | * A convenience method that allows existing specs to be disabled temporarily during development. 434 | * 435 | * @param {String} desc description of this specification 436 | * @param {Function} func defines the preconditions and expectations of the spec 437 | */ 438 | var xit = function(desc, func) { 439 | return jasmine.getEnv().xit(desc, func); 440 | }; 441 | 442 | /** 443 | * Starts a chain for a Jasmine expectation. 444 | * 445 | * It is passed an Object that is the actual value and should chain to one of the many 446 | * jasmine.Matchers functions. 447 | * 448 | * @param {Object} actual Actual value to test against and expected value 449 | */ 450 | var expect = function(actual) { 451 | return jasmine.getEnv().currentSpec.expect(actual); 452 | }; 453 | 454 | /** 455 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 456 | * 457 | * @param {Function} func Function that defines part of a jasmine spec. 458 | */ 459 | var runs = function(func) { 460 | jasmine.getEnv().currentSpec.runs(func); 461 | }; 462 | 463 | /** 464 | * Waits for a timeout before moving to the next runs()-defined block. 465 | * @param {Number} timeout 466 | */ 467 | var waits = function(timeout) { 468 | jasmine.getEnv().currentSpec.waits(timeout); 469 | }; 470 | 471 | /** 472 | * Waits for the latchFunction to return true before proceeding to the next runs()-defined block. 473 | * 474 | * @param {Number} timeout 475 | * @param {Function} latchFunction 476 | * @param {String} message 477 | */ 478 | var waitsFor = function(timeout, latchFunction, message) { 479 | jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message); 480 | }; 481 | 482 | /** 483 | * A function that is called before each spec in a suite. 484 | * 485 | * Used for spec setup, including validating assumptions. 486 | * 487 | * @param {Function} beforeEachFunction 488 | */ 489 | var beforeEach = function(beforeEachFunction) { 490 | jasmine.getEnv().beforeEach(beforeEachFunction); 491 | }; 492 | 493 | /** 494 | * A function that is called after each spec in a suite. 495 | * 496 | * Used for restoring any state that is hijacked during spec execution. 497 | * 498 | * @param {Function} afterEachFunction 499 | */ 500 | var afterEach = function(afterEachFunction) { 501 | jasmine.getEnv().afterEach(afterEachFunction); 502 | }; 503 | 504 | /** 505 | * Defines a suite of specifications. 506 | * 507 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 508 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 509 | * of setup in some tests. 510 | * 511 | * @example 512 | * // TODO: a simple suite 513 | * 514 | * // TODO: a simple suite with a nested describe block 515 | * 516 | * @param {String} description A string, usually the class under test. 517 | * @param {Function} specDefinitions function that defines several specs. 518 | */ 519 | var describe = function(description, specDefinitions) { 520 | return jasmine.getEnv().describe(description, specDefinitions); 521 | }; 522 | 523 | /** 524 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 525 | * 526 | * @param {String} description A string, usually the class under test. 527 | * @param {Function} specDefinitions function that defines several specs. 528 | */ 529 | var xdescribe = function(description, specDefinitions) { 530 | return jasmine.getEnv().xdescribe(description, specDefinitions); 531 | }; 532 | 533 | 534 | // Provide the XMLHttpRequest class for IE 5.x-6.x: 535 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 536 | try { 537 | return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 538 | } catch(e) { 539 | } 540 | try { 541 | return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 542 | } catch(e) { 543 | } 544 | try { 545 | return new ActiveXObject("Msxml2.XMLHTTP"); 546 | } catch(e) { 547 | } 548 | try { 549 | return new ActiveXObject("Microsoft.XMLHTTP"); 550 | } catch(e) { 551 | } 552 | throw new Error("This browser does not support XMLHttpRequest."); 553 | } : XMLHttpRequest; 554 | 555 | /** 556 | * Adds suite files to an HTML document so that they are executed, thus adding them to the current 557 | * Jasmine environment. 558 | * 559 | * @param {String} url path to the file to include 560 | * @param {Boolean} opt_global 561 | */ 562 | jasmine.include = function(url, opt_global) { 563 | if (opt_global) { 564 | document.write(' 8 | 9 | 10 | 11 | 12 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | --------------------------------------------------------------------------------