├── .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://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 |
19 |
"
20 | $("#instructionsContent").mousedown =>
21 | @domElement.hide()
22 | @callback()
23 | return
24 |
25 | ribbon: ->
26 | '
27 |
'
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 = ""
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 |