├── LICENSE
├── README.md
├── js
├── blocks.js
├── glMatrix-1.2.min.js
├── helpers.js
├── network.js
├── physics.js
├── player.js
├── render.js
└── world.js
├── media
├── background.png
├── blockthumbs.png
├── player.png
└── terrain.png
├── multiplayer.html
├── server.js
├── singleplayer.html
└── style
├── main.css
├── minecraftia.ttf
└── minecraftia.txt
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Alexander Overvoorde
2 |
3 | This software is provided 'as-is', without any express or implied
4 | warranty. In no event will the authors be held liable for any damages
5 | arising from the use of this software.
6 |
7 | Permission is granted to anyone to use this software for any purpose,
8 | including commercial applications, and to alter it and redistribute it
9 | freely, subject to the following restrictions:
10 |
11 | 1. The origin of this software must not be misrepresented; you must not
12 | claim that you wrote the original software. If you use this software
13 | in a product, an acknowledgment in the product documentation would be
14 | appreciated but is not required.
15 |
16 | 2. Altered source versions must be plainly marked as such, and must not be
17 | misrepresented as being the original software.
18 |
19 | 3. This notice may not be removed or altered from any source
20 | distribution.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WebCraft (HTML5 Minecraft)
2 | ---------------------
3 | [](https://github.com/Overv/WebCraft/issues)
4 | [](https://github.com/Overv/WebCraft/network)
5 | [](https://github.com/Overv/WebCraft/stargazers)
6 | [](https://github.com/Overv/WebCraft/blob/master/LICENSE)
7 | [](https://codeclimate.com/github/WarenGonzaga/WebCraft/maintainability)
8 |
9 | **This project is no longer actively maintained!**
10 |
11 | This project is intended to become a Minecraft Classic clone using HTML 5 technologies, most notably WebGL and WebSockets. No third-party libraries are used, with the exception of glmatrix and socket.io. People who have written similar demos used libraries such as *three.js*, but it is
12 | both foolish and inefficient to use a 3D engine for rendering large amount of blocks.
13 |
14 | Screenshots
15 | ---------------------
16 |
17 | 
18 |
19 | Structure
20 | ---------------------
21 |
22 | + *js/* - Includes the different modules that make up the functionality of WebCraft.
23 | + *media/* - Contains the graphics resources.
24 | + *style/* - Contains stylesheets for the HTML front-ends.
25 | + *singleplayer.html* - The front-end for the singleplayer client.
26 | + *multiplayer.html* - The front-end for the multiplayer client.
27 | + *server.js* - The Node.js server code.
28 |
29 | Modules
30 | ---------------------
31 |
32 | The two front-ends invoke the available modules to deliver the components necessary for the gameplay and graphics of either the singleplayer or multiplayer experience. The available modules are listed below.
33 |
34 | **Blocks.js**
35 |
36 | This is the most *moddable* module, as it contains the structure with the available block materials and their respective properties. It also contains functions invoked by the render class for proper shading and lighting of blocks.
37 |
38 | **World.js**
39 |
40 | This is the base class, which all other modules depend on. Although it is a very important module, it is also the most passive module. It contains the block structure of the world and exposes functions for manipulating it.
41 |
42 | **Physics.js**
43 |
44 | This module has strong roots in the world class and simulates the flow of fluid blocks and the gravity of falling blocks at regular intervals. It has no specific parameters and is simply invoked in the game loop to update the world.
45 |
46 | **Render.js**
47 |
48 | This is the module that takes care of visualizing the block structure in the world class. When a world is assigned to it, it sets up a structure of chunks that are updated when a block changes. These chunks are mostly just fancy Vertex Buffer Objects. As this module takes care of the rendering, it also houses the code that deals with *picking* (getting a block from an x, y position on the screen).
49 |
50 | **Player.js**
51 |
52 | Finally there is also the module that handles everything related to the player of the game. Surprising, perhaps, is that it also deals with the physics and collision of the player. Less surprising is that it manages the material selector and input and responds to it in an update function, just like the physics module.
53 |
54 | **Network.js**
55 |
56 | This module makes it easy to synchronize a world between a server and connected clients. It comes with both a *Client* and *Server* class to facilitate all of your networking needs.
57 |
58 | Typical game set-up
59 | ---------------------
60 |
61 | First a new world is created and the block structure is initialised.
62 |
63 | var world = new World( 16, 16, 16 );
64 | world.createFlatWorld( 6 );
65 |
66 | The *6* in *createFlatWorld* here is the line between the ground and the first air layer.
67 |
68 | Now that we have a world, we can set up a renderer, which will subsequently divide the world into chunks for rendering.
69 |
70 | var render = new Renderer( "renderSurface" );
71 | render.setWorld( world, 8 );
72 | render.setPerspective( 60, 0.01, 200 );
73 |
74 | The *8* here determines the XYZ size of one chunk. In this case the entire world consists out of 8 chunks.
75 |
76 | To finish the code that deals with world management, we create the physics simulator.
77 |
78 | var physics = new Physics();
79 | physics.setWorld( world );
80 |
81 | And finally, we add a local player to the game:
82 |
83 | var player = new Player();
84 | player.setWorld( world );
85 | player.setInputCanvas( "renderSurface" );
86 | player.setMaterialSelector( "materialSelector" );
87 |
88 | That concludes the set-up code. The render loop can be constructed with a timer on a fixed framerate:
89 |
90 | setInterval( function()
91 | {
92 | var time = new Date().getTime() / 1000.0;
93 |
94 | // Simulate physics
95 | physics.simulate();
96 |
97 | // Update local player
98 | player.update();
99 |
100 | // Build a chunk
101 | render.buildChunks( 5 );
102 |
103 | // Draw world
104 | render.setCamera( player.getEyePos().toArray(), player.angles );
105 | render.draw();
106 |
107 | while ( new Date().getTime() / 1000 - time < 0.016 );
108 | }, 1 );
109 |
110 | To see how the material selector and canvas can be set-up, have a look at *singleplayer.html* and *style/main.css*. Note that the player and physics modules are entirely optional, so you could just as well use this code as a base for making a Minecraft map viewer on your website.
111 |
--------------------------------------------------------------------------------
/js/blocks.js:
--------------------------------------------------------------------------------
1 | // ==========================================
2 | // Block types
3 | //
4 | // This file contains all available block types and their properties.
5 | // ==========================================
6 |
7 | // Direction enumeration
8 | var DIRECTION = {};
9 | DIRECTION.UP = 1;
10 | DIRECTION.DOWN = 2;
11 | DIRECTION.LEFT = 3;
12 | DIRECTION.RIGHT = 4;
13 | DIRECTION.FORWARD = 5;
14 | DIRECTION.BACK = 6;
15 |
16 | BLOCK = {};
17 |
18 | // Air
19 | BLOCK.AIR = {
20 | id: 0,
21 | spawnable: false,
22 | transparent: true
23 | };
24 |
25 | // Bedrock
26 | BLOCK.BEDROCK = {
27 | id: 1,
28 | spawnable: false,
29 | transparent: false,
30 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 1/16, 1/16, 2/16, 2/16 ]; }
31 | };
32 |
33 | // Dirt
34 | BLOCK.DIRT = {
35 | id: 2,
36 | spawnable: true,
37 | transparent: false,
38 | selflit: false,
39 | gravity: false,
40 | fluid: false,
41 | texture: function( world, lightmap, lit, x, y, z, dir )
42 | {
43 | if ( dir == DIRECTION.UP && lit )
44 | return [ 14/16, 0/16, 15/16, 1/16 ];
45 | else if ( dir == DIRECTION.DOWN || !lit )
46 | return [ 2/16, 0/16, 3/16, 1/16 ];
47 | else
48 | return [ 3/16, 0/16, 4/16, 1/16 ];
49 | }
50 | };
51 |
52 | // Wood
53 | BLOCK.WOOD = {
54 | id: 3,
55 | spawnable: true,
56 | transparent: false,
57 | selflit: false,
58 | gravity: false,
59 | fluid: false,
60 | texture: function( world, lightmap, lit, x, y, z, dir )
61 | {
62 | if ( dir == DIRECTION.UP || dir == DIRECTION.DOWN )
63 | return [ 5/16, 1/16, 6/16, 2/16 ];
64 | else
65 | return [ 4/16, 1/16, 5/16, 2/16 ];
66 | }
67 | };
68 |
69 | // TNT
70 | BLOCK.TNT = {
71 | id: 4,
72 | spawnable: true,
73 | transparent: false,
74 | selflit: false,
75 | gravity: false,
76 | fluid: false,
77 | texture: function( world, lightmap, lit, x, y, z, dir )
78 | {
79 | if ( dir == DIRECTION.UP || dir == DIRECTION.DOWN )
80 | return [ 10/16, 0/16, 11/16, 1/16 ];
81 | else
82 | return [ 8/16, 0/16, 9/16, 1/16 ];
83 | }
84 | };
85 |
86 | // Bookcase
87 | BLOCK.BOOKCASE = {
88 | id: 5,
89 | spawnable: true,
90 | transparent: false,
91 | selflit: false,
92 | gravity: false,
93 | fluid: false,
94 | texture: function( world, lightmap, lit, x, y, z, dir )
95 | {
96 | if ( dir == DIRECTION.FORWARD || dir == DIRECTION.BACK )
97 | return [ 3/16, 2/16, 4/16, 3/16 ];
98 | else
99 | return [ 4/16, 0/16, 5/16, 1/16 ];
100 | }
101 | };
102 |
103 | // Lava
104 | BLOCK.LAVA = {
105 | id: 6,
106 | spawnable: false,
107 | transparent: true,
108 | selflit: true,
109 | gravity: true,
110 | fluid: true,
111 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 13/16, 14/16, 14/16, 15/16 ]; }
112 | };
113 |
114 | // Plank
115 | BLOCK.PLANK = {
116 | id: 7,
117 | spawnable: true,
118 | transparent: false,
119 | selflit: false,
120 | gravity: false,
121 | fluid: false,
122 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 4/16, 0/16, 5/16, 1/16 ]; }
123 | };
124 |
125 | // Cobblestone
126 | BLOCK.COBBLESTONE = {
127 | id: 8,
128 | spawnable: true,
129 | transparent: false,
130 | selflit: false,
131 | gravity: false,
132 | fluid: false,
133 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 0/16, 1/16, 1/16, 2/16 ]; }
134 | };
135 |
136 | // Concrete
137 | BLOCK.CONCRETE = {
138 | id: 9,
139 | spawnable: true,
140 | transparent: false,
141 | selflit: false,
142 | gravity: false,
143 | fluid: false,
144 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 1/16, 0/16, 2/16, 1/16 ]; }
145 | };
146 |
147 | // Brick
148 | BLOCK.BRICK = {
149 | id: 10,
150 | spawnable: true,
151 | transparent: false,
152 | selflit: false,
153 | gravity: false,
154 | fluid: false,
155 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 7/16, 0/16, 8/16, 1/16 ]; }
156 | };
157 |
158 | // Sand
159 | BLOCK.SAND = {
160 | id: 11,
161 | spawnable: true,
162 | transparent: false,
163 | selflit: false,
164 | gravity: true,
165 | fluid: false,
166 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 2/16, 1/16, 3/16, 2/16 ]; }
167 | };
168 |
169 | // Gravel
170 | BLOCK.GRAVEL = {
171 | id: 12,
172 | spawnable: true,
173 | transparent: false,
174 | selflit: false,
175 | gravity: true,
176 | fluid: false,
177 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 3/16, 1/16, 4/16, 2/16 ]; }
178 | };
179 |
180 | // Iron
181 | BLOCK.IRON = {
182 | id: 13,
183 | spawnable: true,
184 | transparent: false,
185 | selflit: false,
186 | gravity: false,
187 | fluid: false,
188 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 6/16, 1/16, 7/16, 2/16 ]; }
189 | };
190 |
191 | // Gold
192 | BLOCK.GOLD = {
193 | id: 14,
194 | spawnable: true,
195 | transparent: false,
196 | selflit: false,
197 | gravity: false,
198 | fluid: false,
199 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 7/16, 1/16, 8/16, 2/16 ]; }
200 | };
201 |
202 | // Diamond
203 | BLOCK.DIAMOND = {
204 | id: 15,
205 | spawnable: true,
206 | transparent: false,
207 | selflit: false,
208 | gravity: false,
209 | fluid: false,
210 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 8/16, 1/16, 9/16, 2/16 ]; }
211 | };
212 |
213 | // Obsidian
214 | BLOCK.OBSIDIAN = {
215 | id: 16,
216 | spawnable: true,
217 | transparent: false,
218 | selflit: false,
219 | gravity: false,
220 | fluid: false,
221 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 5/16, 2/16, 6/16, 3/16 ]; }
222 | };
223 |
224 | // Glass
225 | BLOCK.GLASS = {
226 | id: 17,
227 | spawnable: true,
228 | transparent: true,
229 | selflit: false,
230 | gravity: false,
231 | fluid: false,
232 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 1/16, 3/16, 2/16, 4/16 ]; }
233 | };
234 |
235 | // Sponge
236 | BLOCK.SPONGE = {
237 | id: 18,
238 | spawnable: true,
239 | transparent: false,
240 | selflit: false,
241 | gravity: false,
242 | fluid: false,
243 | texture: function( world, lightmap, lit, x, y, z, dir ) { return [ 0/16, 3/16, 1/16, 4/16 ]; }
244 | };
245 |
246 | // fromId( id )
247 | //
248 | // Returns a block structure for the given id.
249 |
250 | BLOCK.fromId = function( id )
251 | {
252 | for ( var mat in BLOCK )
253 | if ( typeof( BLOCK[mat] ) == "object" && BLOCK[mat].id == id )
254 | return BLOCK[mat];
255 | return null;
256 | }
257 |
258 | // pushVertices( vertices, world, lightmap, x, y, z )
259 | //
260 | // Pushes the vertices necessary for rendering a
261 | // specific block into the array.
262 |
263 | BLOCK.pushVertices = function( vertices, world, lightmap, x, y, z )
264 | {
265 | var blocks = world.blocks;
266 | var blockLit = z >= lightmap[x][y];
267 | var block = blocks[x][y][z];
268 | var bH = block.fluid && ( z == world.sz - 1 || !blocks[x][y][z+1].fluid ) ? 0.9 : 1.0;
269 |
270 | // Top
271 | if ( z == world.sz - 1 || world.blocks[x][y][z+1].transparent || block.fluid )
272 | {
273 | var c = block.texture( world, lightmap, blockLit, x, y, z, DIRECTION.UP );
274 |
275 | var lightMultiplier = z >= lightmap[x][y] ? 1.0 : 0.6;
276 | if ( block.selflit ) lightMultiplier = 1.0;
277 |
278 | pushQuad(
279 | vertices,
280 | [ x, y, z + bH, c[0], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
281 | [ x + 1.0, y, z + bH, c[2], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
282 | [ x + 1.0, y + 1.0, z + bH, c[2], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
283 | [ x, y + 1.0, z + bH, c[0], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ]
284 | );
285 | }
286 |
287 | // Bottom
288 | if ( z == 0 || world.blocks[x][y][z-1].transparent )
289 | {
290 | var c = block.texture( world, lightmap, blockLit, x, y, z, DIRECTION.DOWN );
291 |
292 | var lightMultiplier = block.selflit ? 1.0 : 0.6;
293 |
294 | pushQuad(
295 | vertices,
296 | [ x, y + 1.0, z, c[0], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
297 | [ x + 1.0, y + 1.0, z, c[2], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
298 | [ x + 1.0, y, z, c[2], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
299 | [ x, y, z, c[0], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ]
300 | );
301 | }
302 |
303 | // Front
304 | if ( y == 0 || world.blocks[x][y-1][z].transparent )
305 | {
306 | var c = block.texture( world, lightmap, blockLit, x, y, z, DIRECTION.FORWARD );
307 |
308 | var lightMultiplier = ( y == 0 || z >= lightmap[x][y-1] ) ? 1.0 : 0.6;
309 | if ( block.selflit ) lightMultiplier = 1.0;
310 |
311 | pushQuad(
312 | vertices,
313 | [ x, y, z, c[0], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
314 | [ x + 1.0, y, z, c[2], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
315 | [ x + 1.0, y, z + bH, c[2], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
316 | [ x, y, z + bH, c[0], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ]
317 | );
318 | }
319 |
320 | // Back
321 | if ( y == world.sy - 1 || world.blocks[x][y+1][z].transparent )
322 | {
323 | var c = block.texture( world, lightmap, blockLit, x, y, z, DIRECTION.BACK );
324 |
325 | var lightMultiplier = block.selflit ? 1.0 : 0.6;
326 |
327 | pushQuad(
328 | vertices,
329 | [ x, y + 1.0, z + bH, c[2], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
330 | [ x + 1.0, y + 1.0, z + bH, c[0], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
331 | [ x + 1.0, y + 1.0, z, c[0], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
332 | [ x, y + 1.0, z, c[2], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ]
333 | );
334 | }
335 |
336 | // Left
337 | if ( x == 0 || world.blocks[x-1][y][z].transparent )
338 | {
339 | var c = block.texture( world, lightmap, blockLit, x, y, z, DIRECTION.LEFT );
340 |
341 | var lightMultiplier = block.selflit ? 1.0 : 0.6;
342 |
343 | pushQuad(
344 | vertices,
345 | [ x, y, z + bH, c[2], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
346 | [ x, y + 1.0, z + bH, c[0], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
347 | [ x, y + 1.0, z, c[0], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
348 | [ x, y, z, c[2], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ]
349 | );
350 | }
351 |
352 | // Right
353 | if ( x == world.sx - 1 || world.blocks[x+1][y][z].transparent )
354 | {
355 | var c = block.texture( world, lightmap, blockLit, x, y, z, DIRECTION.RIGHT );
356 |
357 | var lightMultiplier = ( x == world.sx - 1 || z >= lightmap[x+1][y] ) ? 1.0 : 0.6;
358 | if ( block.selflit ) lightMultiplier = 1.0;
359 |
360 | pushQuad(
361 | vertices,
362 | [ x + 1.0, y, z, c[0], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
363 | [ x + 1.0, y + 1.0, z, c[2], c[3], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
364 | [ x + 1.0, y + 1.0, z + bH, c[2], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ],
365 | [ x + 1.0, y, z + bH, c[0], c[1], lightMultiplier, lightMultiplier, lightMultiplier, 1.0 ]
366 | );
367 | }
368 | }
369 |
370 | // pushPickingVertices( vertices, x, y, z )
371 | //
372 | // Pushes vertices with the data needed for picking.
373 |
374 | BLOCK.pushPickingVertices = function( vertices, x, y, z )
375 | {
376 | var color = { r: x/255, g: y/255, b: z/255 };
377 |
378 | // Top
379 | pushQuad(
380 | vertices,
381 | [ x, y, z + 1, 0, 0, color.r, color.g, color.b, 1/255 ],
382 | [ x + 1, y, z + 1, 1, 0, color.r, color.g, color.b, 1/255 ],
383 | [ x + 1, y + 1, z + 1, 1, 1, color.r, color.g, color.b, 1/255 ],
384 | [ x, y + 1, z + 1, 0, 0, color.r, color.g, color.b, 1/255 ]
385 | );
386 |
387 | // Bottom
388 | pushQuad(
389 | vertices,
390 | [ x, y + 1, z, 0, 0, color.r, color.g, color.b, 2/255 ],
391 | [ x + 1, y + 1, z, 1, 0, color.r, color.g, color.b, 2/255 ],
392 | [ x + 1, y, z, 1, 1, color.r, color.g, color.b, 2/255 ],
393 | [ x, y, z, 0, 0, color.r, color.g, color.b, 2/255 ]
394 | );
395 |
396 | // Front
397 | pushQuad(
398 | vertices,
399 | [ x, y, z, 0, 0, color.r, color.g, color.b, 3/255 ],
400 | [ x + 1, y, z, 1, 0, color.r, color.g, color.b, 3/255 ],
401 | [ x + 1, y, z + 1, 1, 1, color.r, color.g, color.b, 3/255 ],
402 | [ x, y, z + 1, 0, 0, color.r, color.g, color.b, 3/255 ]
403 | );
404 |
405 | // Back
406 | pushQuad(
407 | vertices,
408 | [ x, y + 1, z + 1, 0, 0, color.r, color.g, color.b, 4/255 ],
409 | [ x + 1, y + 1, z + 1, 1, 0, color.r, color.g, color.b, 4/255 ],
410 | [ x + 1, y + 1, z, 1, 1, color.r, color.g, color.b, 4/255 ],
411 | [ x, y + 1, z, 0, 0, color.r, color.g, color.b, 4/255 ]
412 | );
413 |
414 | // Left
415 | pushQuad(
416 | vertices,
417 | [ x, y, z + 1, 0, 0, color.r, color.g, color.b, 5/255 ],
418 | [ x, y + 1, z + 1, 1, 0, color.r, color.g, color.b, 5/255 ],
419 | [ x, y + 1, z, 1, 1, color.r, color.g, color.b, 5/255 ],
420 | [ x, y, z, 0, 0, color.r, color.g, color.b, 5/255 ]
421 | );
422 |
423 | // Right
424 | pushQuad(
425 | vertices,
426 | [ x + 1, y, z, 0, 0, color.r, color.g, color.b, 6/255 ],
427 | [ x + 1, y + 1, z, 1, 0, color.r, color.g, color.b, 6/255 ],
428 | [ x + 1, y + 1, z + 1, 1, 1, color.r, color.g, color.b, 6/255 ],
429 | [ x + 1, y, z + 1, 0, 0, color.r, color.g, color.b, 6/255 ]
430 | );
431 | }
432 |
433 | // Export to node.js
434 | if ( typeof( exports ) != "undefined" )
435 | {
436 | exports.BLOCK = BLOCK;
437 | }
--------------------------------------------------------------------------------
/js/glMatrix-1.2.min.js:
--------------------------------------------------------------------------------
1 | // gl-matrix 1.2 - https://github.com/toji/gl-matrix/blob/master/LICENSE.md
2 | (function(){var a="undefined"!=typeof exports?global:window;a.glMatrixArrayType=a.MatrixArray=null;a.vec3={};a.mat3={};a.mat4={};a.quat4={};a.setMatrixArrayType=function(a){return glMatrixArrayType=MatrixArray=a};a.determineMatrixArrayType=function(){return setMatrixArrayType("undefined"!==typeof Float32Array?Float32Array:Array)};determineMatrixArrayType()})();vec3.create=function(a){var b=new MatrixArray(3);a?(b[0]=a[0],b[1]=a[1],b[2]=a[2]):b[0]=b[1]=b[2]=0;return b};
3 | vec3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];return b};vec3.add=function(a,b,c){if(!c||a===c)return a[0]+=b[0],a[1]+=b[1],a[2]+=b[2],a;c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];return c};vec3.subtract=function(a,b,c){if(!c||a===c)return a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a;c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];return c};vec3.negate=function(a,b){b||(b=a);b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];return b};
4 | vec3.scale=function(a,b,c){if(!c||a===c)return a[0]*=b,a[1]*=b,a[2]*=b,a;c[0]=a[0]*b;c[1]=a[1]*b;c[2]=a[2]*b;return c};vec3.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=Math.sqrt(c*c+d*d+e*e);if(g){if(1===g)return b[0]=c,b[1]=d,b[2]=e,b}else return b[0]=0,b[1]=0,b[2]=0,b;g=1/g;b[0]=c*g;b[1]=d*g;b[2]=e*g;return b};vec3.cross=function(a,b,c){c||(c=a);var d=a[0],e=a[1],a=a[2],g=b[0],f=b[1],b=b[2];c[0]=e*b-a*f;c[1]=a*g-d*b;c[2]=d*f-e*g;return c};
5 | vec3.length=function(a){var b=a[0],c=a[1],a=a[2];return Math.sqrt(b*b+c*c+a*a)};vec3.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]};vec3.direction=function(a,b,c){c||(c=a);var d=a[0]-b[0],e=a[1]-b[1],a=a[2]-b[2],b=Math.sqrt(d*d+e*e+a*a);if(!b)return c[0]=0,c[1]=0,c[2]=0,c;b=1/b;c[0]=d*b;c[1]=e*b;c[2]=a*b;return c};vec3.lerp=function(a,b,c,d){d||(d=a);d[0]=a[0]+c*(b[0]-a[0]);d[1]=a[1]+c*(b[1]-a[1]);d[2]=a[2]+c*(b[2]-a[2]);return d};
6 | vec3.dist=function(a,b){var c=b[0]-a[0],d=b[1]-a[1],e=b[2]-a[2];return Math.sqrt(c*c+d*d+e*e)};vec3.unproject=function(a,b,c,d,e){e||(e=a);var g=mat4.create(),f=new MatrixArray(4);f[0]=2*(a[0]-d[0])/d[2]-1;f[1]=2*(a[1]-d[1])/d[3]-1;f[2]=2*a[2]-1;f[3]=1;mat4.multiply(c,b,g);if(!mat4.inverse(g))return null;mat4.multiplyVec4(g,f);if(0===f[3])return null;e[0]=f[0]/f[3];e[1]=f[1]/f[3];e[2]=f[2]/f[3];return e};vec3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};
7 | mat3.create=function(a){var b=new MatrixArray(9);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8]);return b};mat3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return b};mat3.identity=function(a){a||(a=mat3.create());a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=1;a[5]=0;a[6]=0;a[7]=0;a[8]=1;return a};
8 | mat3.transpose=function(a,b){if(!b||a===b){var c=a[1],d=a[2],e=a[5];a[1]=a[3];a[2]=a[6];a[3]=c;a[5]=a[7];a[6]=d;a[7]=e;return a}b[0]=a[0];b[1]=a[3];b[2]=a[6];b[3]=a[1];b[4]=a[4];b[5]=a[7];b[6]=a[2];b[7]=a[5];b[8]=a[8];return b};mat3.toMat4=function(a,b){b||(b=mat4.create());b[15]=1;b[14]=0;b[13]=0;b[12]=0;b[11]=0;b[10]=a[8];b[9]=a[7];b[8]=a[6];b[7]=0;b[6]=a[5];b[5]=a[4];b[4]=a[3];b[3]=0;b[2]=a[2];b[1]=a[1];b[0]=a[0];return b};
9 | mat3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};mat4.create=function(a){var b=new MatrixArray(16);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15]);return b};
10 | mat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return b};mat4.identity=function(a){a||(a=mat4.create());a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1;return a};
11 | mat4.transpose=function(a,b){if(!b||a===b){var c=a[1],d=a[2],e=a[3],g=a[6],f=a[7],h=a[11];a[1]=a[4];a[2]=a[8];a[3]=a[12];a[4]=c;a[6]=a[9];a[7]=a[13];a[8]=d;a[9]=g;a[11]=a[14];a[12]=e;a[13]=f;a[14]=h;return a}b[0]=a[0];b[1]=a[4];b[2]=a[8];b[3]=a[12];b[4]=a[1];b[5]=a[5];b[6]=a[9];b[7]=a[13];b[8]=a[2];b[9]=a[6];b[10]=a[10];b[11]=a[14];b[12]=a[3];b[13]=a[7];b[14]=a[11];b[15]=a[15];return b};
12 | mat4.determinant=function(a){var b=a[0],c=a[1],d=a[2],e=a[3],g=a[4],f=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],n=a[11],o=a[12],m=a[13],p=a[14],a=a[15];return o*k*h*e-j*m*h*e-o*f*l*e+g*m*l*e+j*f*p*e-g*k*p*e-o*k*d*i+j*m*d*i+o*c*l*i-b*m*l*i-j*c*p*i+b*k*p*i+o*f*d*n-g*m*d*n-o*c*h*n+b*m*h*n+g*c*p*n-b*f*p*n-j*f*d*a+g*k*d*a+j*c*h*a-b*k*h*a-g*c*l*a+b*f*l*a};
13 | mat4.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=a[9],n=a[10],o=a[11],m=a[12],p=a[13],r=a[14],s=a[15],A=c*h-d*f,B=c*i-e*f,t=c*j-g*f,u=d*i-e*h,v=d*j-g*h,w=e*j-g*i,x=k*p-l*m,y=k*r-n*m,z=k*s-o*m,C=l*r-n*p,D=l*s-o*p,E=n*s-o*r,q=A*E-B*D+t*C+u*z-v*y+w*x;if(!q)return null;q=1/q;b[0]=(h*E-i*D+j*C)*q;b[1]=(-d*E+e*D-g*C)*q;b[2]=(p*w-r*v+s*u)*q;b[3]=(-l*w+n*v-o*u)*q;b[4]=(-f*E+i*z-j*y)*q;b[5]=(c*E-e*z+g*y)*q;b[6]=(-m*w+r*t-s*B)*q;b[7]=(k*w-n*t+o*B)*q;b[8]=
14 | (f*D-h*z+j*x)*q;b[9]=(-c*D+d*z-g*x)*q;b[10]=(m*v-p*t+s*A)*q;b[11]=(-k*v+l*t-o*A)*q;b[12]=(-f*C+h*y-i*x)*q;b[13]=(c*C-d*y+e*x)*q;b[14]=(-m*u+p*B-r*A)*q;b[15]=(k*u-l*B+n*A)*q;return b};mat4.toRotationMat=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};
15 | mat4.toMat3=function(a,b){b||(b=mat3.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[4];b[4]=a[5];b[5]=a[6];b[6]=a[8];b[7]=a[9];b[8]=a[10];return b};mat4.toInverseMat3=function(a,b){var c=a[0],d=a[1],e=a[2],g=a[4],f=a[5],h=a[6],i=a[8],j=a[9],k=a[10],l=k*f-h*j,n=-k*g+h*i,o=j*g-f*i,m=c*l+d*n+e*o;if(!m)return null;m=1/m;b||(b=mat3.create());b[0]=l*m;b[1]=(-k*d+e*j)*m;b[2]=(h*d-e*f)*m;b[3]=n*m;b[4]=(k*c-e*i)*m;b[5]=(-h*c+e*g)*m;b[6]=o*m;b[7]=(-j*c+d*i)*m;b[8]=(f*c-d*g)*m;return b};
16 | mat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],f=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],n=a[9],o=a[10],m=a[11],p=a[12],r=a[13],s=a[14],a=a[15],A=b[0],B=b[1],t=b[2],u=b[3],v=b[4],w=b[5],x=b[6],y=b[7],z=b[8],C=b[9],D=b[10],E=b[11],q=b[12],F=b[13],G=b[14],b=b[15];c[0]=A*d+B*h+t*l+u*p;c[1]=A*e+B*i+t*n+u*r;c[2]=A*g+B*j+t*o+u*s;c[3]=A*f+B*k+t*m+u*a;c[4]=v*d+w*h+x*l+y*p;c[5]=v*e+w*i+x*n+y*r;c[6]=v*g+w*j+x*o+y*s;c[7]=v*f+w*k+x*m+y*a;c[8]=z*d+C*h+D*l+E*p;c[9]=z*e+C*i+D*n+E*r;c[10]=z*g+C*
17 | j+D*o+E*s;c[11]=z*f+C*k+D*m+E*a;c[12]=q*d+F*h+G*l+b*p;c[13]=q*e+F*i+G*n+b*r;c[14]=q*g+F*j+G*o+b*s;c[15]=q*f+F*k+G*m+b*a;return c};mat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],b=b[2];c[0]=a[0]*d+a[4]*e+a[8]*b+a[12];c[1]=a[1]*d+a[5]*e+a[9]*b+a[13];c[2]=a[2]*d+a[6]*e+a[10]*b+a[14];return c};
18 | mat4.multiplyVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2],b=b[3];c[0]=a[0]*d+a[4]*e+a[8]*g+a[12]*b;c[1]=a[1]*d+a[5]*e+a[9]*g+a[13]*b;c[2]=a[2]*d+a[6]*e+a[10]*g+a[14]*b;c[3]=a[3]*d+a[7]*e+a[11]*g+a[15]*b;return c};
19 | mat4.translate=function(a,b,c){var d=b[0],e=b[1],b=b[2],g,f,h,i,j,k,l,n,o,m,p,r;if(!c||a===c)return a[12]=a[0]*d+a[4]*e+a[8]*b+a[12],a[13]=a[1]*d+a[5]*e+a[9]*b+a[13],a[14]=a[2]*d+a[6]*e+a[10]*b+a[14],a[15]=a[3]*d+a[7]*e+a[11]*b+a[15],a;g=a[0];f=a[1];h=a[2];i=a[3];j=a[4];k=a[5];l=a[6];n=a[7];o=a[8];m=a[9];p=a[10];r=a[11];c[0]=g;c[1]=f;c[2]=h;c[3]=i;c[4]=j;c[5]=k;c[6]=l;c[7]=n;c[8]=o;c[9]=m;c[10]=p;c[11]=r;c[12]=g*d+j*e+o*b+a[12];c[13]=f*d+k*e+m*b+a[13];c[14]=h*d+l*e+p*b+a[14];c[15]=i*d+n*e+r*b+a[15];
20 | return c};mat4.scale=function(a,b,c){var d=b[0],e=b[1],b=b[2];if(!c||a===c)return a[0]*=d,a[1]*=d,a[2]*=d,a[3]*=d,a[4]*=e,a[5]*=e,a[6]*=e,a[7]*=e,a[8]*=b,a[9]*=b,a[10]*=b,a[11]*=b,a;c[0]=a[0]*d;c[1]=a[1]*d;c[2]=a[2]*d;c[3]=a[3]*d;c[4]=a[4]*e;c[5]=a[5]*e;c[6]=a[6]*e;c[7]=a[7]*e;c[8]=a[8]*b;c[9]=a[9]*b;c[10]=a[10]*b;c[11]=a[11]*b;c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15];return c};
21 | mat4.rotate=function(a,b,c,d){var e=c[0],g=c[1],c=c[2],f=Math.sqrt(e*e+g*g+c*c),h,i,j,k,l,n,o,m,p,r,s,A,B,t,u,v,w,x,y,z;if(!f)return null;1!==f&&(f=1/f,e*=f,g*=f,c*=f);h=Math.sin(b);i=Math.cos(b);j=1-i;b=a[0];f=a[1];k=a[2];l=a[3];n=a[4];o=a[5];m=a[6];p=a[7];r=a[8];s=a[9];A=a[10];B=a[11];t=e*e*j+i;u=g*e*j+c*h;v=c*e*j-g*h;w=e*g*j-c*h;x=g*g*j+i;y=c*g*j+e*h;z=e*c*j+g*h;e=g*c*j-e*h;g=c*c*j+i;d?a!==d&&(d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[0]=b*t+n*u+r*v;d[1]=f*t+o*u+s*v;d[2]=k*t+m*u+A*
22 | v;d[3]=l*t+p*u+B*v;d[4]=b*w+n*x+r*y;d[5]=f*w+o*x+s*y;d[6]=k*w+m*x+A*y;d[7]=l*w+p*x+B*y;d[8]=b*z+n*e+r*g;d[9]=f*z+o*e+s*g;d[10]=k*z+m*e+A*g;d[11]=l*z+p*e+B*g;return d};mat4.rotateX=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[4],g=a[5],f=a[6],h=a[7],i=a[8],j=a[9],k=a[10],l=a[11];c?a!==c&&(c[0]=a[0],c[1]=a[1],c[2]=a[2],c[3]=a[3],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[4]=e*b+i*d;c[5]=g*b+j*d;c[6]=f*b+k*d;c[7]=h*b+l*d;c[8]=e*-d+i*b;c[9]=g*-d+j*b;c[10]=f*-d+k*b;c[11]=h*-d+l*b;return c};
23 | mat4.rotateY=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[0],g=a[1],f=a[2],h=a[3],i=a[8],j=a[9],k=a[10],l=a[11];c?a!==c&&(c[4]=a[4],c[5]=a[5],c[6]=a[6],c[7]=a[7],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[0]=e*b+i*-d;c[1]=g*b+j*-d;c[2]=f*b+k*-d;c[3]=h*b+l*-d;c[8]=e*d+i*b;c[9]=g*d+j*b;c[10]=f*d+k*b;c[11]=h*d+l*b;return c};
24 | mat4.rotateZ=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[0],g=a[1],f=a[2],h=a[3],i=a[4],j=a[5],k=a[6],l=a[7];c?a!==c&&(c[8]=a[8],c[9]=a[9],c[10]=a[10],c[11]=a[11],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[0]=e*b+i*d;c[1]=g*b+j*d;c[2]=f*b+k*d;c[3]=h*b+l*d;c[4]=e*-d+i*b;c[5]=g*-d+j*b;c[6]=f*-d+k*b;c[7]=h*-d+l*b;return c};
25 | mat4.frustum=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=2*e/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=2*e/i;f[6]=0;f[7]=0;f[8]=(b+a)/h;f[9]=(d+c)/i;f[10]=-(g+e)/j;f[11]=-1;f[12]=0;f[13]=0;f[14]=-(2*g*e)/j;f[15]=0;return f};mat4.perspective=function(a,b,c,d,e){a=c*Math.tan(a*Math.PI/360);b*=a;return mat4.frustum(-b,b,-a,a,c,d,e)};
26 | mat4.ortho=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=2/i;f[6]=0;f[7]=0;f[8]=0;f[9]=0;f[10]=-2/j;f[11]=0;f[12]=-(a+b)/h;f[13]=-(d+c)/i;f[14]=-(g+e)/j;f[15]=1;return f};
27 | mat4.lookAt=function(a,b,c,d){d||(d=mat4.create());var e,g,f,h,i,j,k,l,n=a[0],o=a[1],a=a[2];g=c[0];f=c[1];e=c[2];c=b[1];j=b[2];if(n===b[0]&&o===c&&a===j)return mat4.identity(d);c=n-b[0];j=o-b[1];k=a-b[2];l=1/Math.sqrt(c*c+j*j+k*k);c*=l;j*=l;k*=l;b=f*k-e*j;e=e*c-g*k;g=g*j-f*c;(l=Math.sqrt(b*b+e*e+g*g))?(l=1/l,b*=l,e*=l,g*=l):g=e=b=0;f=j*g-k*e;h=k*b-c*g;i=c*e-j*b;(l=Math.sqrt(f*f+h*h+i*i))?(l=1/l,f*=l,h*=l,i*=l):i=h=f=0;d[0]=b;d[1]=f;d[2]=c;d[3]=0;d[4]=e;d[5]=h;d[6]=j;d[7]=0;d[8]=g;d[9]=i;d[10]=k;d[11]=
28 | 0;d[12]=-(b*n+e*o+g*a);d[13]=-(f*n+h*o+i*a);d[14]=-(c*n+j*o+k*a);d[15]=1;return d};mat4.fromRotationTranslation=function(a,b,c){c||(c=mat4.create());var d=a[0],e=a[1],g=a[2],f=a[3],h=d+d,i=e+e,j=g+g,a=d*h,k=d*i,d=d*j,l=e*i,e=e*j,g=g*j,h=f*h,i=f*i,f=f*j;c[0]=1-(l+g);c[1]=k+f;c[2]=d-i;c[3]=0;c[4]=k-f;c[5]=1-(a+g);c[6]=e+h;c[7]=0;c[8]=d+i;c[9]=e-h;c[10]=1-(a+l);c[11]=0;c[12]=b[0];c[13]=b[1];c[14]=b[2];c[15]=1;return c};
29 | mat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+", "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"};quat4.create=function(a){var b=new MatrixArray(4);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]);return b};quat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];return b};
30 | quat4.calculateW=function(a,b){var c=a[0],d=a[1],e=a[2];if(!b||a===b)return a[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e)),a;b[0]=c;b[1]=d;b[2]=e;b[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return b};quat4.inverse=function(a,b){if(!b||a===b)return a[0]*=-1,a[1]*=-1,a[2]*=-1,a;b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];b[3]=a[3];return b};quat4.length=function(a){var b=a[0],c=a[1],d=a[2],a=a[3];return Math.sqrt(b*b+c*c+d*d+a*a)};
31 | quat4.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=Math.sqrt(c*c+d*d+e*e+g*g);if(0===f)return b[0]=0,b[1]=0,b[2]=0,b[3]=0,b;f=1/f;b[0]=c*f;b[1]=d*f;b[2]=e*f;b[3]=g*f;return b};quat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],a=a[3],f=b[0],h=b[1],i=b[2],b=b[3];c[0]=d*b+a*f+e*i-g*h;c[1]=e*b+a*h+g*f-d*i;c[2]=g*b+a*i+d*h-e*f;c[3]=a*b-d*f-e*h-g*i;return c};
32 | quat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2],b=a[0],f=a[1],h=a[2],a=a[3],i=a*d+f*g-h*e,j=a*e+h*d-b*g,k=a*g+b*e-f*d,d=-b*d-f*e-h*g;c[0]=i*a+d*-b+j*-h-k*-f;c[1]=j*a+d*-f+k*-b-i*-h;c[2]=k*a+d*-h+i*-f-j*-b;return c};quat4.toMat3=function(a,b){b||(b=mat3.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h,c=c*i,l=d*h,d=d*i,e=e*i,f=g*f,h=g*h,g=g*i;b[0]=1-(l+e);b[1]=k+g;b[2]=c-h;b[3]=k-g;b[4]=1-(j+e);b[5]=d+f;b[6]=c+h;b[7]=d-f;b[8]=1-(j+l);return b};
33 | quat4.toMat4=function(a,b){b||(b=mat4.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h,c=c*i,l=d*h,d=d*i,e=e*i,f=g*f,h=g*h,g=g*i;b[0]=1-(l+e);b[1]=k+g;b[2]=c-h;b[3]=0;b[4]=k-g;b[5]=1-(j+e);b[6]=d+f;b[7]=0;b[8]=c+h;b[9]=d-f;b[10]=1-(j+l);b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};
34 | quat4.slerp=function(a,b,c,d){d||(d=a);var e=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3],g,f;if(1<=Math.abs(e))return d!==a&&(d[0]=a[0],d[1]=a[1],d[2]=a[2],d[3]=a[3]),d;g=Math.acos(e);f=Math.sqrt(1-e*e);if(0.001>Math.abs(f))return d[0]=0.5*a[0]+0.5*b[0],d[1]=0.5*a[1]+0.5*b[1],d[2]=0.5*a[2]+0.5*b[2],d[3]=0.5*a[3]+0.5*b[3],d;e=Math.sin((1-c)*g)/f;c=Math.sin(c*g)/f;d[0]=a[0]*e+b[0]*c;d[1]=a[1]*e+b[1]*c;d[2]=a[2]*e+b[2]*c;d[3]=a[3]*e+b[3]*c;return d};
35 | quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"};
--------------------------------------------------------------------------------
/js/helpers.js:
--------------------------------------------------------------------------------
1 | // ==========================================
2 | // Helpers
3 | //
4 | // This file contains helper classes and functions.
5 | // ==========================================
6 |
7 | // ==========================================
8 | // Vector class
9 | // ==========================================
10 |
11 | function Vector( x, y, z )
12 | {
13 | this.x = x;
14 | this.y = y;
15 | this.z = z;
16 | }
17 |
18 | Vector.prototype.add = function( vec )
19 | {
20 | return new Vector( this.x + vec.x, this.y + vec.y, this.z + vec.z );
21 | }
22 |
23 | Vector.prototype.sub = function( vec )
24 | {
25 | return new Vector( this.x - vec.x, this.y - vec.y, this.z - vec.z );
26 | }
27 |
28 | Vector.prototype.mul = function( n )
29 | {
30 | return new Vector( this.x*n, this.y*n, this.z*n );
31 | }
32 |
33 | Vector.prototype.length = function()
34 | {
35 | return Math.sqrt( this.x*this.x + this.y*this.y + this.z*this.z );
36 | }
37 |
38 | Vector.prototype.distance = function( vec )
39 | {
40 | return this.sub( vec ).length();
41 | }
42 |
43 | Vector.prototype.normal = function()
44 | {
45 | if ( this.x == 0 && this.y == 0 && this.z == 0 ) return new Vector( 0, 0, 0 );
46 | var l = this.length();
47 | return new Vector( this.x/l, this.y/l, this.z/l );
48 | }
49 |
50 | Vector.prototype.dot = function( vec )
51 | {
52 | return this.x * vec.x + this.y * vec.y + this.z * vec.z;
53 | }
54 |
55 | Vector.prototype.toArray = function()
56 | {
57 | return [ this.x, this.y, this.z ];
58 | }
59 |
60 | Vector.prototype.toString = function()
61 | {
62 | return "( " + this.x + ", " + this.y + ", " + this.z + " )";
63 | }
64 |
65 | // lineRectCollide( line, rect )
66 | //
67 | // Checks if an axis-aligned line and a bounding box overlap.
68 | // line = { y, x1, x2 } or line = { x, y1, y2 }
69 | // rect = { x, y, size }
70 |
71 | function lineRectCollide( line, rect )
72 | {
73 | if ( line.y != null )
74 | return rect.y > line.y - rect.size/2 && rect.y < line.y + rect.size/2 && rect.x > line.x1 - rect.size/2 && rect.x < line.x2 + rect.size/2;
75 | else
76 | return rect.x > line.x - rect.size/2 && rect.x < line.x + rect.size/2 && rect.y > line.y1 - rect.size/2 && rect.y < line.y2 + rect.size/2;
77 | }
78 |
79 | // rectRectCollide( r1, r2 )
80 | //
81 | // Checks if two rectangles (x1, y1, x2, y2) overlap.
82 |
83 | function rectRectCollide( r1, r2 )
84 | {
85 | if ( r2.x1 > r1.x1 && r2.x1 < r1.x2 && r2.y1 > r1.y1 && r2.y1 < r1.y2 ) return true;
86 | if ( r2.x2 > r1.x1 && r2.x2 < r1.x2 && r2.y1 > r1.y1 && r2.y1 < r1.y2 ) return true;
87 | if ( r2.x2 > r1.x1 && r2.x2 < r1.x2 && r2.y2 > r1.y1 && r2.y2 < r1.y2 ) return true;
88 | if ( r2.x1 > r1.x1 && r2.x1 < r1.x2 && r2.y2 > r1.y1 && r2.y2 < r1.y2 ) return true;
89 | return false;
90 | }
91 |
92 | // Export to node.js
93 | if ( typeof( exports ) != "undefined" )
94 | {
95 | exports.Vector = Vector;
96 | }
--------------------------------------------------------------------------------
/js/network.js:
--------------------------------------------------------------------------------
1 | // ==========================================
2 | // Network
3 | //
4 | // This class manages the connection between the client and the
5 | // server and everything involved.
6 | // ==========================================
7 |
8 | // ==========================================
9 | // Client
10 | // ==========================================
11 |
12 | // Constructor( socketio )
13 | //
14 | // Creates a new client using the specified socket interface.
15 |
16 | function Client( socketio )
17 | {
18 | this.io = socketio;
19 | this.eventHandlers = {};
20 | this.kicked = false;
21 | }
22 |
23 | // connect( uri, nickname )
24 | //
25 | // Connect to a server with the specified nickname.
26 |
27 | Client.prototype.connect = function( uri, nickname )
28 | {
29 | var socket = this.socket = this.io.connect( uri, { reconnect: false } );
30 | this.nickname = nickname;
31 |
32 | // Hook events
33 | var s = this;
34 | socket.on( "connect", function() { s.onConnection(); } );
35 | socket.on( "disconnect", function() { s.onDisconnection(); } );
36 | socket.on( "world", function( data ) { s.onWorld( data ); } );
37 | socket.on( "spawn", function( data ) { s.onSpawn( data ); } );
38 | socket.on( "setblock", function( data ) { s.onBlockUpdate( data ); } );
39 | socket.on( "msg", function( data ) { s.onMessage( data ); } );
40 | socket.on( "kick", function( data ) { s.onKick( data ); } );
41 | socket.on( "join", function( data ) { s.onPlayerJoin( data ); } );
42 | socket.on( "leave", function( data ) { s.onPlayerLeave( data ); } );
43 | socket.on( "player", function( data ) { s.onPlayerUpdate( data ); } );
44 | socket.on( "setpos", function( data ) { s.onPlayerSetPos( data ); } );
45 | }
46 |
47 | // setBlock( x, y, z, mat )
48 | //
49 | // Called to do a networked block update.
50 |
51 | Client.prototype.setBlock = function( x, y, z, mat )
52 | {
53 | this.socket.emit( "setblock", {
54 | x: x,
55 | y: y,
56 | z: z,
57 | mat: mat.id
58 | } );
59 | }
60 |
61 | // sendMessage( msg )
62 | //
63 | // Send a chat message.
64 |
65 | Client.prototype.sendMessage = function( msg )
66 | {
67 | this.socket.emit( "chat", {
68 | msg: msg
69 | } );
70 | }
71 |
72 | // updatePlayer()
73 | //
74 | // Sends the current player position and orientation to the server.
75 |
76 | Client.prototype.updatePlayer = function()
77 | {
78 | var player = this.world.localPlayer;
79 |
80 | this.socket.emit( "player", {
81 | x: player.pos.x,
82 | y: player.pos.y,
83 | z: player.pos.z,
84 | pitch: player.angles[0],
85 | yaw: player.angles[1]
86 | } );
87 | }
88 |
89 | // on( event, callback )
90 | //
91 | // Hooks an event.
92 |
93 | Client.prototype.on = function( event, callback )
94 | {
95 | this.eventHandlers[event] = callback;
96 | }
97 |
98 | // onConnection()
99 | //
100 | // Called when the client has connected.
101 |
102 | Client.prototype.onConnection = function()
103 | {
104 | if ( this.eventHandlers["connect"] ) this.eventHandlers.connect();
105 |
106 | this.socket.emit( "nickname", { nickname: this.nickname } );
107 | }
108 |
109 | // onDisconnection()
110 | //
111 | // Called when the client was disconnected.
112 |
113 | Client.prototype.onDisconnection = function()
114 | {
115 | if ( this.eventHandlers["disconnect"] ) this.eventHandlers.disconnect( this.kicked );
116 | }
117 |
118 | // onWorld( data )
119 | //
120 | // Called when the server has sent the world.
121 |
122 | Client.prototype.onWorld = function( data )
123 | {
124 | // Create world from string representation
125 | var world = this.world = new World( data.sx, data.sy, data.sz );
126 | world.createFromString( data.blocks );
127 |
128 | if ( this.eventHandlers["world"] ) this.eventHandlers.world( world );
129 | }
130 |
131 | // onSpawn( data )
132 | //
133 | // Called when the local player is spawned.
134 |
135 | Client.prototype.onSpawn = function( data )
136 | {
137 | // Set spawn point
138 | this.world.spawnPoint = new Vector( data.x, data.y, data.z );
139 |
140 | if ( this.eventHandlers["spawn"] ) this.eventHandlers.spawn();
141 | }
142 |
143 | // onBlockUpdate( data )
144 | //
145 | // Called when a block update is received from the server.
146 |
147 | Client.prototype.onBlockUpdate = function( data )
148 | {
149 | var material = BLOCK.fromId( data.mat );
150 |
151 | if ( this.eventHandlers["block"] ) this.eventHandlers.block( data.x, data.y, data.z, this.world.blocks[data.x][data.y][data.z], material );
152 |
153 | this.world.setBlock( data.x, data.y, data.z, material );
154 | }
155 |
156 | // onMessage( data )
157 | //
158 | // Called when a message is received.
159 |
160 | Client.prototype.onMessage = function( data )
161 | {
162 | if ( data.type == "chat" ) {
163 | if ( this.eventHandlers["chat"] ) this.eventHandlers.chat( data.user, data.msg );
164 | } else if ( data.type == "generic" ) {
165 | if ( this.eventHandlers["message"] ) this.eventHandlers.message( data.msg );
166 | }
167 | }
168 |
169 | // onKick( data )
170 | //
171 | // Called when a kick message is received.
172 |
173 | Client.prototype.onKick = function( data )
174 | {
175 | this.kicked = true;
176 | if ( this.eventHandlers["kick"] ) this.eventHandlers.kick( data.msg );
177 | }
178 |
179 | // onPlayerJoin( data )
180 | //
181 | // Called when a new player joins the game.
182 |
183 | Client.prototype.onPlayerJoin = function( data )
184 | {
185 | data.moving = false;
186 | data.aniframe = 0;
187 | this.world.players[data.nick] = data;
188 | }
189 |
190 | // onPlayerLeave( data )
191 | //
192 | // Called when a player has left the game.
193 |
194 | Client.prototype.onPlayerLeave = function( data )
195 | {
196 | if ( this.world.players[data.nick].nametag ) {
197 | this.world.renderer.gl.deleteBuffer( this.world.players[data.nick].nametag.model );
198 | this.world.renderer.gl.deleteTexture( this.world.players[data.nick].nametag.texture );
199 | }
200 |
201 | delete this.world.players[data.nick];
202 | }
203 |
204 | // onPlayerUpdate( data )
205 | //
206 | // Called when the server has sent updated player info.
207 |
208 | Client.prototype.onPlayerUpdate = function( data )
209 | {
210 | if ( !this.world ) return;
211 |
212 | var pl = this.world.players[data.nick];
213 | if ( Math.abs(data.x - pl.x) > 0.1 ||
214 | Math.abs(data.y - pl.y) > 0.1 ||
215 | Math.abs(data.z - pl.z) > 0.1)
216 | {
217 | pl.moving = true;
218 | }
219 |
220 | pl.x = data.x;
221 | pl.y = data.y;
222 | pl.z = data.z;
223 | pl.pitch = data.pitch;
224 | pl.yaw = data.yaw;
225 | window.setTimeout(function(){pl.moving=false},100);
226 | }
227 |
228 | // onPlayerSetPos( data )
229 | //
230 | // Called when the server wants to set the position of the local player.
231 |
232 | Client.prototype.onPlayerSetPos = function( data )
233 | {
234 | this.world.localPlayer.pos = new Vector( data.x, data.y, data.z );
235 | this.world.localPlayer.velocity = new Vector( 0, 0, 0 );
236 | }
237 |
238 | // ==========================================
239 | // Server
240 | // ==========================================
241 |
242 | // Constructor( socketio, slots )
243 | //
244 | // Creates a new server listening for clients using the specified
245 | // socket interface. Slots is an optional maximum amount of clients.
246 |
247 | function Server( socketio, slots )
248 | {
249 | var express = require('express');
250 | var app = express();
251 | var http = require('http').Server(app);
252 | app.use(express.static('.'));
253 |
254 | var io = this.io = socketio(http);
255 | var s = this;
256 |
257 | io.sockets.on( "connection", function( socket ) { s.onConnection( socket ); } );
258 |
259 | this.eventHandlers = {};
260 | this.activeNicknames = {};
261 | this.activeAddresses = {};
262 |
263 | this.maxSlots = slots;
264 | this.usedSlots = 0;
265 |
266 | this.oneUserPerIp = true;
267 |
268 | http.listen(3000, function() {});
269 | }
270 |
271 | // setWorld( world )
272 | //
273 | // Assign a world to be networked.
274 |
275 | Server.prototype.setWorld = function( world )
276 | {
277 | this.world = world;
278 | }
279 |
280 | // setLogger( fn )
281 | //
282 | // Assign a log function to output activity to.
283 |
284 | Server.prototype.setLogger = function( fn )
285 | {
286 | this.log = fn;
287 | }
288 |
289 | // setOneUserPerIp( enabled )
290 | //
291 | // Enable/disable the one user per ip rule.
292 |
293 | Server.prototype.setOneUserPerIp = function( enabled )
294 | {
295 | this.oneUserPerIp = enabled;
296 | }
297 |
298 | // on( event, callback )
299 | //
300 | // Hooks an event.
301 |
302 | Server.prototype.on = function( event, callback )
303 | {
304 | this.eventHandlers[event] = callback;
305 | }
306 |
307 | // sendMessage( msg[, socket] )
308 | //
309 | // Send a generic message to a certain client or everyone.
310 |
311 | Server.prototype.sendMessage = function( msg, socket )
312 | {
313 | var obj = socket ? socket : this.io.sockets;
314 | obj.emit( "msg", {
315 | type: "generic",
316 | msg: msg
317 | } );
318 | }
319 |
320 | // broadcastMessage( msg, socket )
321 | //
322 | // Send a generic message to everyone except for the
323 | // specified client.
324 |
325 | Server.prototype.broadcastMessage = function( msg, socket )
326 | {
327 | socket.broadcast.emit( "msg", {
328 | type: "generic",
329 | msg: msg
330 | } );
331 | }
332 |
333 | // kick( socket, msg )
334 | //
335 | // Kick a client with the specified message.
336 |
337 | Server.prototype.kick = function( socket, msg )
338 | {
339 | if ( this.log ) this.log( "Client " + this.getIp(socket) + " was kicked (" + msg + ")." );
340 |
341 | if ( socket._nickname != null )
342 | this.sendMessage( socket._nickname + " was kicked (" + msg + ")." );
343 |
344 | socket.emit( "kick", {
345 | msg: msg
346 | } );
347 | socket.disconnect();
348 | }
349 |
350 | // setPos( socket, x, y, z )
351 | //
352 | // Request a client to change their position.
353 |
354 | Server.prototype.setPos = function( socket, x, y, z )
355 | {
356 | socket.emit( "setpos", {
357 | x: x,
358 | y: y,
359 | z: z
360 | } );
361 | }
362 |
363 | // findPlayerByName( name )
364 | //
365 | // Attempts to find a player by their nickname.
366 |
367 | Server.prototype.findPlayerByName = function( name )
368 | {
369 | for ( var p in this.world.players )
370 | if ( p.toLowerCase().indexOf( name.toLowerCase() ) != -1 ) return this.world.players[p];
371 | return null;
372 | }
373 |
374 | // onConnection( socket )
375 | //
376 | // Called when a new client has connected.
377 |
378 | Server.prototype.onConnection = function( socket )
379 | {
380 | if ( this.log ) this.log( "Client " + this.getIp(socket) + " connected to the server." );
381 |
382 | // Check if a slot limit is active
383 | if ( this.maxSlots != null && this.usedSlots == this.maxSlots ) {
384 | this.kick( socket, "The server is full!" );
385 | return;
386 | }
387 |
388 | // Prevent people from blocking the server with multiple open clients
389 | if ( this.activeAddresses[this.getIp(socket)] && this.oneUserPerIp )
390 | {
391 | this.kick( socket, "Multiple clients connecting from the same IP address!" );
392 | return;
393 | }
394 | this.activeAddresses[this.getIp(socket)] = true;
395 | this.usedSlots++;
396 |
397 | // Hook events
398 | var s = this;
399 | socket.on( "nickname", function( data ) { s.onNickname( socket, data ); } );
400 | socket.on( "setblock", function( data ) { s.onBlockUpdate( socket, data ); } );
401 | socket.on( "chat", function( data ) { s.onChatMessage( socket, data ); } );
402 | socket.on( "player", function( data ) { s.onPlayerUpdate( socket, data ); } );
403 | socket.on( "disconnect", function() { s.onDisconnect( socket ); } );
404 | }
405 |
406 | // onNickname( socket, nickname )
407 | //
408 | // Called when a client has sent their nickname.
409 |
410 | Server.prototype.onNickname = function( socket, data )
411 | {
412 | if ( data.nickname.length == 0 || data.nickname.length > 15 ) return false;
413 |
414 | // Prevent people from changing their username
415 | if ( socket._nickname == null )
416 | {
417 | var nickname = this.sanitiseInput( data.nickname );
418 |
419 | for ( var n in this.activeNicknames ) {
420 | if ( n.toLowerCase() == nickname.toLowerCase() ) {
421 | this.kick( socket, "That username is already in use!" );
422 | return;
423 | }
424 | }
425 |
426 | if ( this.log ) this.log( "Client " + this.getIp(socket) + " is now known as " + nickname + "." );
427 | if ( this.eventHandlers["join"] ) this.eventHandlers.join( socket, nickname );
428 | this.activeNicknames[data.nickname] = true;
429 |
430 | // Associate nickname with socket
431 | socket._nickname = nickname;
432 |
433 | // Send world to client
434 | var world = this.world;
435 |
436 | socket.emit( "world", {
437 | sx: world.sx,
438 | sy: world.sy,
439 | sz: world.sz,
440 | blocks: world.toNetworkString()
441 | } );
442 |
443 | // Spawn client
444 | socket.emit( "spawn", {
445 | x: world.spawnPoint.x,
446 | y: world.spawnPoint.y,
447 | z: world.spawnPoint.z,
448 | } );
449 |
450 | // Tell client about other players
451 | for ( var p in this.world.players )
452 | {
453 | var pl = this.world.players[p];
454 |
455 | socket.emit( "join", {
456 | nick: p,
457 | x: pl.x,
458 | y: pl.y,
459 | z: pl.z,
460 | pitch: pl.pitch,
461 | yaw: pl.yaw
462 | } );
463 | }
464 |
465 | // Inform other players
466 | socket.broadcast.emit( "join", {
467 | nick: nickname,
468 | x: world.spawnPoint.x,
469 | y: world.spawnPoint.y,
470 | z: world.spawnPoint.z,
471 | pitch: 0,
472 | yaw: 0
473 | } );
474 |
475 | // Add player to world
476 | world.players[nickname] = {
477 | socket: socket,
478 | nick: nickname,
479 | lastBlockCheck: +new Date(),
480 | blocks: 0,
481 | x: world.spawnPoint.x,
482 | y: world.spawnPoint.y,
483 | z: world.spawnPoint.z,
484 | pitch: 0,
485 | yaw: 0
486 | };
487 | }
488 | }
489 |
490 | // onBlockUpdate( socket, data )
491 | //
492 | // Called when a client wants to change a block.
493 |
494 | Server.prototype.onBlockUpdate = function( socket, data )
495 | {
496 | var world = this.world;
497 |
498 | if ( typeof( data.x ) != "number" || typeof( data.y ) != "number" || typeof( data.z ) != "number" || typeof( data.mat ) != "number" ) return false;
499 | if ( data.x < 0 || data.y < 0 || data.z < 0 || data.x >= world.sx || data.y >= world.sy || data.z >= world.sz ) return false;
500 | if ( Math.sqrt( (data.x-world.spawnPoint.x)*(data.x-world.spawnPoint.x) + (data.y-world.spawnPoint.y)*(data.y-world.spawnPoint.y) + (data.z-world.spawnPoint.z)*(data.z-world.spawnPoint.z) ) < 10 ) return false;
501 |
502 | var material = BLOCK.fromId( data.mat );
503 | if ( material == null || ( !material.spawnable && data.mat != 0 ) ) return false;
504 |
505 | // Check if the user has authenticated themselves before allowing them to set blocks
506 | if ( socket._nickname != null )
507 | {
508 | try {
509 | world.setBlock( data.x, data.y, data.z, material );
510 |
511 | var pl = this.world.players[socket._nickname];
512 | pl.blocks++;
513 | if ( +new Date() > pl.lastBlockCheck + 100 ) {
514 | if ( pl.blocks > 5 ) {
515 | this.kick( socket, "Block spamming." );
516 | return;
517 | }
518 |
519 | pl.lastBlockCheck = +new Date();
520 | pl.blocks = 0;
521 | }
522 |
523 | this.io.sockets.emit( "setblock", {
524 | x: data.x,
525 | y: data.y,
526 | z: data.z,
527 | mat: data.mat
528 | } );
529 | } catch ( e ) {
530 | console.log( "Error setting block at ( " + data.x + ", " + data.y + ", " + data.z + " ): " + e );
531 | }
532 | }
533 | }
534 |
535 | // onChatMessage( socket, data )
536 | //
537 | // Called when a client sends a chat message.
538 |
539 | Server.prototype.onChatMessage = function( socket, data )
540 | {
541 | if ( typeof( data.msg ) != "string" || data.msg.trim().length == 0 || data.msg.length > 100 ) return false;
542 | var msg = this.sanitiseInput( data.msg );
543 |
544 | // Check if the user has authenticated themselves before allowing them to send messages
545 | if ( socket._nickname != null )
546 | {
547 | if ( this.log ) this.log( "<" + socket._nickname + "> " + msg );
548 |
549 | var callback = false;
550 | if ( this.eventHandlers["chat"] ) callback = this.eventHandlers.chat( socket, socket._nickname, msg );
551 |
552 | if ( !callback )
553 | {
554 | this.io.sockets.emit( "msg", {
555 | type: "chat",
556 | user: socket._nickname,
557 | msg: msg
558 | } );
559 | }
560 | }
561 | }
562 |
563 | // onPlayerUpdate( socket, data )
564 | //
565 | // Called when a client sends a position/orientation update.
566 |
567 | function normaliseAngle( ang )
568 | {
569 | ang = ang % (Math.PI*2);
570 | if ( ang < 0 ) ang = Math.PI*2 + ang;
571 | return ang;
572 | }
573 |
574 | Server.prototype.onPlayerUpdate = function( socket, data )
575 | {
576 | if ( typeof( data.x ) != "number" || typeof( data.y ) != "number" || typeof( data.z ) != "number" ) return false;
577 | if ( typeof( data.pitch ) != "number" || typeof( data.yaw ) != "number" ) return false;
578 |
579 | // Check if the user has authenticated themselves before allowing them to send updates
580 | if ( socket._nickname != null )
581 | {
582 | var pl = this.world.players[socket._nickname];
583 | pl.x = data.x;
584 | pl.y = data.y;
585 | pl.z = data.z;
586 | pl.pitch = data.pitch;
587 | pl.yaw = data.yaw;
588 |
589 | // Forward update to other players
590 | for ( var p in this.world.players ) {
591 | var tpl = this.world.players[p];
592 | if ( tpl.socket == socket ) continue;
593 |
594 | var ang = Math.PI + Math.atan2( tpl.y - pl.y, tpl.x - pl.x );
595 | var nyaw = Math.PI - tpl.yaw - Math.PI/2;
596 | var inFrustrum = Math.abs( normaliseAngle( nyaw ) - normaliseAngle( ang ) ) < Math.PI/2;
597 |
598 | if ( inFrustrum )
599 | {
600 | tpl.socket.volatile.emit( "player", {
601 | nick: socket._nickname,
602 | x: pl.x,
603 | y: pl.y,
604 | z: pl.z,
605 | pitch: pl.pitch,
606 | yaw: pl.yaw
607 | } );
608 | }
609 | }
610 | }
611 | }
612 |
613 | // onDisconnect( socket, data )
614 | //
615 | // Called when a client has disconnected.
616 |
617 | Server.prototype.onDisconnect = function( socket )
618 | {
619 | if ( this.log ) this.log( "Client " + this.getIp(socket) + " disconnected." );
620 |
621 | this.usedSlots--;
622 | delete this.activeAddresses[this.getIp(socket)];
623 |
624 | if ( socket._nickname != null )
625 | {
626 | delete this.activeNicknames[socket._nickname];
627 | delete this.world.players[socket._nickname];
628 |
629 | // Inform other players
630 | socket.broadcast.emit( "leave", {
631 | nick: socket._nickname
632 | } );
633 |
634 | if ( this.eventHandlers["leave"] )
635 | this.eventHandlers.leave( socket._nickname );
636 | }
637 | }
638 |
639 | // sanitiseInput( str )
640 | //
641 | // Prevents XSS exploits and other bad things.
642 |
643 | Server.prototype.sanitiseInput = function( str )
644 | {
645 | return str.trim().replace( //g, ">" ).replace( /\\/g, """ );
646 | }
647 |
648 | Server.prototype.getIp = function( socket )
649 | {
650 | return socket.request.connection.remoteAddress;
651 | }
652 |
653 | // Export to node.js
654 | if ( typeof( exports ) != "undefined" )
655 | {
656 | exports.Server = Server;
657 | }
658 |
--------------------------------------------------------------------------------
/js/physics.js:
--------------------------------------------------------------------------------
1 | // ==========================================
2 | // Physics
3 | //
4 | // This class contains the code that takes care of simulating
5 | // processes like gravity and fluid flow in the world.
6 | // ==========================================
7 |
8 | // Constructor()
9 | //
10 | // Creates a new physics simulator.
11 |
12 | function Physics()
13 | {
14 | this.lastStep = -1;
15 | }
16 |
17 | // setWorld( world )
18 | //
19 | // Assigns a world to simulate to this physics simulator.
20 |
21 | Physics.prototype.setWorld = function( world )
22 | {
23 | this.world = world;
24 | }
25 |
26 | // simulate()
27 | //
28 | // Perform one iteration of physics simulation.
29 | // Should be called about once every second.
30 |
31 | Physics.prototype.simulate = function()
32 | {
33 | var world = this.world;
34 | var blocks = world.blocks;
35 |
36 | var step = Math.floor( new Date().getTime() / 100 );
37 | if ( step == this.lastStep ) return;
38 | this.lastStep = step;
39 |
40 | // Gravity
41 | if ( step % 1 == 0 )
42 | {
43 | for ( var x = 0; x < world.sx; x++ ) {
44 | for ( var y = 0; y < world.sy; y++ ) {
45 | for ( var z = 0; z < world.sz; z++ ) {
46 | if ( blocks[x][y][z].gravity && z > 0 && blocks[x][y][z-1] == BLOCK.AIR )
47 | {
48 | world.setBlock( x, y, z - 1, blocks[x][y][z] );
49 | world.setBlock( x, y, z, BLOCK.AIR );
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
56 | // Fluids
57 | if ( step % 10 == 0 )
58 | {
59 | // Newly spawned fluid blocks are stored so that those aren't
60 | // updated in the same step, creating a simulation avalanche.
61 | var newFluidBlocks = {};
62 |
63 | for ( var x = 0; x < world.sx; x++ ) {
64 | for ( var y = 0; y < world.sy; y++ ) {
65 | for ( var z = 0; z < world.sz; z++ ) {
66 | var material = blocks[x][y][z];
67 | if ( material.fluid && newFluidBlocks[x+","+y+","+z] == null )
68 | {
69 | if ( x > 0 && blocks[x-1][y][z] == BLOCK.AIR ) {
70 | world.setBlock( x - 1, y, z, material );
71 | newFluidBlocks[(x-1)+","+y+","+z] = true;
72 | }
73 | if ( x < world.sx - 1 && blocks[x+1][y][z] == BLOCK.AIR ) {
74 | world.setBlock( x + 1, y, z, material );
75 | newFluidBlocks[(x+1)+","+y+","+z] = true;
76 | }
77 | if ( y > 0 && blocks[x][y-1][z] == BLOCK.AIR ) {
78 | world.setBlock( x, y - 1, z, material );
79 | newFluidBlocks[x+","+(y-1)+","+z] = true;
80 | }
81 | if ( y < world.sy - 1 && blocks[x][y+1][z] == BLOCK.AIR ) {
82 | world.setBlock( x, y + 1, z, material );
83 | newFluidBlocks[x+","+(y+1)+","+z] = true;
84 | }
85 | }
86 | }
87 | }
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/js/player.js:
--------------------------------------------------------------------------------
1 | // ==========================================
2 | // Player
3 | //
4 | // This class contains the code that manages the local player.
5 | // ==========================================
6 |
7 | // Mouse event enumeration
8 | MOUSE = {};
9 | MOUSE.DOWN = 1;
10 | MOUSE.UP = 2;
11 | MOUSE.MOVE = 3;
12 |
13 | // Constructor()
14 | //
15 | // Creates a new local player manager.
16 |
17 | function Player()
18 | {
19 | }
20 |
21 | // setWorld( world )
22 | //
23 | // Assign the local player to a world.
24 |
25 | Player.prototype.setWorld = function( world )
26 | {
27 | this.world = world;
28 | this.world.localPlayer = this;
29 | this.pos = world.spawnPoint;
30 | this.velocity = new Vector( 0, 0, 0 );
31 | this.angles = [ 0, Math.PI, 0 ];
32 | this.falling = false;
33 | this.keys = {};
34 | this.buildMaterial = BLOCK.DIRT;
35 | this.eventHandlers = {};
36 | }
37 |
38 | // setClient( client )
39 | //
40 | // Assign the local player to a socket client.
41 |
42 | Player.prototype.setClient = function( client )
43 | {
44 | this.client = client;
45 | }
46 |
47 | // setInputCanvas( id )
48 | //
49 | // Set the canvas the renderer uses for some input operations.
50 |
51 | Player.prototype.setInputCanvas = function( id )
52 | {
53 | var canvas = this.canvas = document.getElementById( id );
54 |
55 | var t = this;
56 | document.onkeydown = function( e ) { if ( e.target.tagName != "INPUT" ) { t.onKeyEvent( e.keyCode, true ); return false; } }
57 | document.onkeyup = function( e ) { if ( e.target.tagName != "INPUT" ) { t.onKeyEvent( e.keyCode, false ); return false; } }
58 | canvas.onmousedown = function( e ) { t.onMouseEvent( e.clientX, e.clientY, MOUSE.DOWN, e.which == 3 ); return false; }
59 | canvas.onmouseup = function( e ) { t.onMouseEvent( e.clientX, e.clientY, MOUSE.UP, e.which == 3 ); return false; }
60 | canvas.onmousemove = function( e ) { t.onMouseEvent( e.clientX, e.clientY, MOUSE.MOVE, e.which == 3 ); return false; }
61 | }
62 |
63 | // setMaterialSelector( id )
64 | //
65 | // Sets the table with the material selectors.
66 |
67 | Player.prototype.setMaterialSelector = function( id )
68 | {
69 | var tableRow = document.getElementById( id ).getElementsByTagName( "tr" )[0];
70 | var texOffset = 0;
71 |
72 | for ( var mat in BLOCK )
73 | {
74 | if ( typeof( BLOCK[mat] ) == "object" && BLOCK[mat].spawnable == true )
75 | {
76 | var selector = document.createElement( "td" );
77 | selector.style.backgroundPosition = texOffset + "px 0px";
78 |
79 | var pl = this;
80 | selector.material = BLOCK[mat];
81 | selector.onclick = function()
82 | {
83 | this.style.opacity = "1.0";
84 |
85 | pl.prevSelector.style.opacity = null;
86 | pl.prevSelector = this;
87 |
88 | pl.buildMaterial = this.material;
89 | }
90 |
91 | if ( mat == "DIRT" ) {
92 | this.prevSelector = selector;
93 | selector.style.opacity = "1.0";
94 | }
95 |
96 | tableRow.appendChild( selector );
97 | texOffset -= 70;
98 | }
99 | }
100 | }
101 |
102 | // on( event, callback )
103 | //
104 | // Hook a player event.
105 |
106 | Player.prototype.on = function( event, callback )
107 | {
108 | this.eventHandlers[event] = callback;
109 | }
110 |
111 | // onKeyEvent( keyCode, down )
112 | //
113 | // Hook for keyboard input.
114 |
115 | Player.prototype.onKeyEvent = function( keyCode, down )
116 | {
117 | var key = String.fromCharCode( keyCode ).toLowerCase();
118 | this.keys[key] = down;
119 | this.keys[keyCode] = down;
120 |
121 | if ( !down && key == "t" && this.eventHandlers["openChat"] ) this.eventHandlers.openChat();
122 | }
123 |
124 | // onMouseEvent( x, y, type, rmb )
125 | //
126 | // Hook for mouse input.
127 |
128 | Player.prototype.onMouseEvent = function( x, y, type, rmb )
129 | {
130 | if ( type == MOUSE.DOWN ) {
131 | this.dragStart = { x: x, y: y };
132 | this.mouseDown = true;
133 | this.yawStart = this.targetYaw = this.angles[1];
134 | this.pitchStart = this.targetPitch = this.angles[0];
135 | } else if ( type == MOUSE.UP ) {
136 | if ( Math.abs( this.dragStart.x - x ) + Math.abs( this.dragStart.y - y ) < 4 )
137 | this.doBlockAction( x, y, !rmb );
138 |
139 | this.dragging = false;
140 | this.mouseDown = false;
141 | this.canvas.style.cursor = "default";
142 | } else if ( type == MOUSE.MOVE && this.mouseDown ) {
143 | this.dragging = true;
144 | this.targetPitch = this.pitchStart - ( y - this.dragStart.y ) / 200;
145 | this.targetYaw = this.yawStart + ( x - this.dragStart.x ) / 200;
146 |
147 | this.canvas.style.cursor = "move";
148 | }
149 | }
150 |
151 | // doBlockAction( x, y )
152 | //
153 | // Called to perform an action based on the player's block selection and input.
154 |
155 | Player.prototype.doBlockAction = function( x, y, destroy )
156 | {
157 | var bPos = new Vector( Math.floor( this.pos.x ), Math.floor( this.pos.y ), Math.floor( this.pos.z ) );
158 | var block = this.canvas.renderer.pickAt( new Vector( bPos.x - 4, bPos.y - 4, bPos.z - 4 ), new Vector( bPos.x + 4, bPos.y + 4, bPos.z + 4 ), x, y );
159 |
160 | if ( block != false )
161 | {
162 | var obj = this.client ? this.client : this.world;
163 |
164 | if ( destroy )
165 | obj.setBlock( block.x, block.y, block.z, BLOCK.AIR );
166 | else
167 | obj.setBlock( block.x + block.n.x, block.y + block.n.y, block.z + block.n.z, this.buildMaterial );
168 | }
169 | }
170 |
171 | // getEyePos()
172 | //
173 | // Returns the position of the eyes of the player for rendering.
174 |
175 | Player.prototype.getEyePos = function()
176 | {
177 | return this.pos.add( new Vector( 0.0, 0.0, 1.7 ) );
178 | }
179 |
180 | // update()
181 | //
182 | // Updates this local player (gravity, movement)
183 |
184 | Player.prototype.update = function()
185 | {
186 | var world = this.world;
187 | var velocity = this.velocity;
188 | var pos = this.pos;
189 | var bPos = new Vector( Math.floor( pos.x ), Math.floor( pos.y ), Math.floor( pos.z ) );
190 |
191 | if ( this.lastUpdate != null )
192 | {
193 | var delta = ( new Date().getTime() - this.lastUpdate ) / 1000;
194 |
195 | // View
196 | if ( this.dragging )
197 | {
198 | this.angles[0] += ( this.targetPitch - this.angles[0] ) * 30 * delta;
199 | this.angles[1] += ( this.targetYaw - this.angles[1] ) * 30 * delta;
200 | if ( this.angles[0] < -Math.PI/2 ) this.angles[0] = -Math.PI/2;
201 | if ( this.angles[0] > Math.PI/2 ) this.angles[0] = Math.PI/2;
202 | }
203 |
204 | // Gravity
205 | if ( this.falling )
206 | velocity.z += -0.5;
207 |
208 | // Jumping
209 | if ( this.keys[" "] && !this.falling )
210 | velocity.z = 8;
211 |
212 | // Walking
213 | var walkVelocity = new Vector( 0, 0, 0 );
214 | if ( !this.falling )
215 | {
216 | if ( this.keys["w"] ) {
217 | walkVelocity.x += Math.cos( Math.PI / 2 - this.angles[1] );
218 | walkVelocity.y += Math.sin( Math.PI / 2 - this.angles[1] );
219 | }
220 | if ( this.keys["s"] ) {
221 | walkVelocity.x += Math.cos( Math.PI + Math.PI / 2 - this.angles[1] );
222 | walkVelocity.y += Math.sin( Math.PI + Math.PI / 2 - this.angles[1] );
223 | }
224 | if ( this.keys["a"] ) {
225 | walkVelocity.x += Math.cos( Math.PI / 2 + Math.PI / 2 - this.angles[1] );
226 | walkVelocity.y += Math.sin( Math.PI / 2 + Math.PI / 2 - this.angles[1] );
227 | }
228 | if ( this.keys["d"] ) {
229 | walkVelocity.x += Math.cos( -Math.PI / 2 + Math.PI / 2 - this.angles[1] );
230 | walkVelocity.y += Math.sin( -Math.PI / 2 + Math.PI / 2 - this.angles[1] );
231 | }
232 | }
233 | if ( walkVelocity.length() > 0 ) {
234 | walkVelocity = walkVelocity.normal();
235 | velocity.x = walkVelocity.x * 4;
236 | velocity.y = walkVelocity.y * 4;
237 | } else {
238 | velocity.x /= this.falling ? 1.01 : 1.5;
239 | velocity.y /= this.falling ? 1.01 : 1.5;
240 | }
241 |
242 | // Resolve collision
243 | this.pos = this.resolveCollision( pos, bPos, velocity.mul( delta ) );
244 | }
245 |
246 | this.lastUpdate = new Date().getTime();
247 | }
248 |
249 | // resolveCollision( pos, bPos, velocity )
250 | //
251 | // Resolves collisions between the player and blocks on XY level for the next movement step.
252 |
253 | Player.prototype.resolveCollision = function( pos, bPos, velocity )
254 | {
255 | var world = this.world;
256 | var playerRect = { x: pos.x + velocity.x, y: pos.y + velocity.y, size: 0.25 };
257 |
258 | // Collect XY collision sides
259 | var collisionCandidates = [];
260 |
261 | for ( var x = bPos.x - 1; x <= bPos.x + 1; x++ )
262 | {
263 | for ( var y = bPos.y - 1; y <= bPos.y + 1; y++ )
264 | {
265 | for ( var z = bPos.z; z <= bPos.z + 1; z++ )
266 | {
267 | if ( world.getBlock( x, y, z ) != BLOCK.AIR )
268 | {
269 | if ( world.getBlock( x - 1, y, z ) == BLOCK.AIR ) collisionCandidates.push( { x: x, dir: -1, y1: y, y2: y + 1 } );
270 | if ( world.getBlock( x + 1, y, z ) == BLOCK.AIR ) collisionCandidates.push( { x: x + 1, dir: 1, y1: y, y2: y + 1 } );
271 | if ( world.getBlock( x, y - 1, z ) == BLOCK.AIR ) collisionCandidates.push( { y: y, dir: -1, x1: x, x2: x + 1 } );
272 | if ( world.getBlock( x, y + 1, z ) == BLOCK.AIR ) collisionCandidates.push( { y: y + 1, dir: 1, x1: x, x2: x + 1 } );
273 | }
274 | }
275 | }
276 | }
277 |
278 | // Solve XY collisions
279 | for( var i in collisionCandidates )
280 | {
281 | var side = collisionCandidates[i];
282 |
283 | if ( lineRectCollide( side, playerRect ) )
284 | {
285 | if ( side.x != null && velocity.x * side.dir < 0 ) {
286 | pos.x = side.x + playerRect.size / 2 * ( velocity.x > 0 ? -1 : 1 );
287 | velocity.x = 0;
288 | } else if ( side.y != null && velocity.y * side.dir < 0 ) {
289 | pos.y = side.y + playerRect.size / 2 * ( velocity.y > 0 ? -1 : 1 );
290 | velocity.y = 0;
291 | }
292 | }
293 | }
294 |
295 | var playerFace = { x1: pos.x + velocity.x - 0.125, y1: pos.y + velocity.y - 0.125, x2: pos.x + velocity.x + 0.125, y2: pos.y + velocity.y + 0.125 };
296 | var newBZLower = Math.floor( pos.z + velocity.z );
297 | var newBZUpper = Math.floor( pos.z + 1.7 + velocity.z * 1.1 );
298 |
299 | // Collect Z collision sides
300 | collisionCandidates = [];
301 |
302 | for ( var x = bPos.x - 1; x <= bPos.x + 1; x++ )
303 | {
304 | for ( var y = bPos.y - 1; y <= bPos.y + 1; y++ )
305 | {
306 | if ( world.getBlock( x, y, newBZLower ) != BLOCK.AIR )
307 | collisionCandidates.push( { z: newBZLower + 1, dir: 1, x1: x, y1: y, x2: x + 1, y2: y + 1 } );
308 | if ( world.getBlock( x, y, newBZUpper ) != BLOCK.AIR )
309 | collisionCandidates.push( { z: newBZUpper, dir: -1, x1: x, y1: y, x2: x + 1, y2: y + 1 } );
310 | }
311 | }
312 |
313 | // Solve Z collisions
314 | this.falling = true;
315 | for ( var i in collisionCandidates )
316 | {
317 | var face = collisionCandidates[i];
318 |
319 | if ( rectRectCollide( face, playerFace ) && velocity.z * face.dir < 0 )
320 | {
321 | if ( velocity.z < 0 ) {
322 | this.falling = false;
323 | pos.z = face.z;
324 | velocity.z = 0;
325 | this.velocity.z = 0;
326 | } else {
327 | pos.z = face.z - 1.8;
328 | velocity.z = 0;
329 | this.velocity.z = 0;
330 | }
331 |
332 | break;
333 | }
334 | }
335 |
336 | // Return solution
337 | return pos.add( velocity );
338 | }
--------------------------------------------------------------------------------
/js/render.js:
--------------------------------------------------------------------------------
1 | // ==========================================
2 | // Renderer
3 | //
4 | // This class contains the code that takes care of visualising the
5 | // elements in the specified world.
6 | // ==========================================
7 |
8 | // Shaders
9 | var vertexSource =
10 | "uniform mat4 uProjMatrix;"+
11 | "uniform mat4 uViewMatrix;"+
12 | "uniform mat4 uModelMatrix;"+
13 | "attribute vec3 aPos;"+
14 | "attribute vec4 aColor;"+
15 | "attribute vec2 aTexCoord;"+
16 | "varying vec4 vColor;"+
17 | "varying vec2 vTexCoord;"+
18 | "void main() {"+
19 | " gl_Position = uProjMatrix * uViewMatrix * ( uModelMatrix * vec4( aPos, 1.0 ) );"+
20 | " vColor = aColor;"+
21 | " vTexCoord = aTexCoord;"+
22 | "}";
23 | var fragmentSource =
24 | "precision highp float;"+
25 | "uniform sampler2D uSampler;"+
26 | "varying vec4 vColor;"+
27 | "varying vec2 vTexCoord;"+
28 | "void main() {"+
29 | " vec4 color = texture2D( uSampler, vec2( vTexCoord.s, vTexCoord.t ) ) * vec4( vColor.rgb, 1.0 );"+
30 | " if ( color.a < 0.1 ) discard;"+
31 | " gl_FragColor = vec4( color.rgb, vColor.a );"+
32 | "}";
33 |
34 | // Constructor( id )
35 | //
36 | // Creates a new renderer with the specified canvas as target.
37 | //
38 | // id - Identifier of the HTML canvas element to render to.
39 |
40 | function Renderer( id )
41 | {
42 | var canvas = this.canvas = document.getElementById( id );
43 | canvas.renderer = this;
44 | canvas.width = canvas.clientWidth;
45 | canvas.height = canvas.clientHeight;
46 |
47 | // Initialise WebGL
48 | var gl;
49 | try
50 | {
51 | gl = this.gl = canvas.getContext( "experimental-webgl" );
52 | } catch ( e ) {
53 | throw "Your browser doesn't support WebGL!";
54 | }
55 |
56 | gl.viewportWidth = canvas.width;
57 | gl.viewportHeight = canvas.height;
58 |
59 | gl.clearColor( 0.62, 0.81, 1.0, 1.0 );
60 | gl.enable( gl.DEPTH_TEST );
61 | gl.enable( gl.CULL_FACE );
62 | gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
63 |
64 | // Load shaders
65 | this.loadShaders();
66 |
67 | // Load player model
68 | this.loadPlayerHeadModel();
69 | this.loadPlayerBodyModel();
70 |
71 | // Create projection and view matrices
72 | var projMatrix = this.projMatrix = mat4.create();
73 | var viewMatrix = this.viewMatrix = mat4.create();
74 |
75 | // Create dummy model matrix
76 | var modelMatrix = this.modelMatrix = mat4.create();
77 | mat4.identity( modelMatrix );
78 | gl.uniformMatrix4fv( this.uModelMat, false, modelMatrix );
79 |
80 | // Create 1px white texture for pure vertex color operations (e.g. picking)
81 | var whiteTexture = this.texWhite = gl.createTexture();
82 | gl.activeTexture( gl.TEXTURE0 );
83 | gl.bindTexture( gl.TEXTURE_2D, whiteTexture );
84 | var white = new Uint8Array( [ 255, 255, 255, 255 ] );
85 | gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, white );
86 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
87 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
88 | gl.uniform1i( this.uSampler, 0 );
89 |
90 | // Load player texture
91 | var playerTexture = this.texPlayer = gl.createTexture();
92 | playerTexture.image = new Image();
93 | playerTexture.image.onload = function()
94 | {
95 | gl.bindTexture( gl.TEXTURE_2D, playerTexture );
96 | gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, playerTexture.image );
97 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
98 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
99 | };
100 | playerTexture.image.src = "media/player.png";
101 |
102 | // Load terrain texture
103 | var terrainTexture = this.texTerrain = gl.createTexture();
104 | terrainTexture.image = new Image();
105 | terrainTexture.image.onload = function()
106 | {
107 | gl.bindTexture( gl.TEXTURE_2D, terrainTexture );
108 | gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, terrainTexture.image );
109 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
110 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
111 | };
112 | terrainTexture.image.src = "media/terrain.png";
113 |
114 | // Create canvas used to draw name tags
115 | var textCanvas = this.textCanvas = document.createElement( "canvas" );
116 | textCanvas.width = 256;
117 | textCanvas.height = 64;
118 | textCanvas.style.display = "none";
119 | var ctx = this.textContext = textCanvas.getContext( "2d" );
120 | ctx.textAlign = "left";
121 | ctx.textBaseline = "middle";
122 | ctx.font = "24px Minecraftia";
123 | document.getElementsByTagName( "body" )[0].appendChild( textCanvas );
124 | }
125 |
126 | // draw()
127 | //
128 | // Render one frame of the world to the canvas.
129 |
130 | Renderer.prototype.draw = function()
131 | {
132 | var gl = this.gl;
133 |
134 | // Initialise view
135 | this.updateViewport();
136 | gl.viewport( 0, 0, gl.viewportWidth, gl.viewportHeight );
137 | gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
138 |
139 | // Draw level chunks
140 | var chunks = this.chunks;
141 |
142 | gl.bindTexture( gl.TEXTURE_2D, this.texTerrain );
143 |
144 | if ( chunks != null )
145 | {
146 | for ( var i = 0; i < chunks.length; i++ )
147 | {
148 | if ( chunks[i].buffer != null )
149 | this.drawBuffer( chunks[i].buffer );
150 | }
151 | }
152 |
153 | // Draw players
154 | var players = this.world.players;
155 |
156 | gl.enable( gl.BLEND );
157 |
158 | for ( var p in world.players )
159 | {
160 | var player = world.players[p];
161 |
162 | if(player.moving || Math.abs(player.aniframe) > 0.1){
163 | player.aniframe += 0.15;
164 | if(player.aniframe > Math.PI)
165 | player.aniframe = -Math.PI;
166 | aniangle = Math.PI/2 * Math.sin(player.aniframe);
167 | if(!player.moving && Math.abs(aniangle) < 0.1 )
168 | player.aniframe = 0;
169 |
170 |
171 | }
172 | else
173 | aniangle = 0;
174 |
175 | // Draw head
176 | var pitch = player.pitch;
177 | if ( pitch < -0.32 ) pitch = -0.32;
178 | if ( pitch > 0.32 ) pitch = 0.32;
179 |
180 | mat4.identity( this.modelMatrix );
181 | mat4.translate( this.modelMatrix, [ player.x, player.y, player.z + 1.7 ] );
182 | mat4.rotateZ( this.modelMatrix, Math.PI - player.yaw );
183 | mat4.rotateX( this.modelMatrix, -pitch );
184 | gl.uniformMatrix4fv( this.uModelMat, false, this.modelMatrix );
185 |
186 | gl.bindTexture( gl.TEXTURE_2D, this.texPlayer );
187 | this.drawBuffer( this.playerHead );
188 |
189 | // Draw body
190 | mat4.identity( this.modelMatrix );
191 | mat4.translate( this.modelMatrix, [ player.x, player.y, player.z + 0.01 ] );
192 | mat4.rotateZ( this.modelMatrix, Math.PI - player.yaw );
193 | gl.uniformMatrix4fv( this.uModelMat, false, this.modelMatrix );
194 | this.drawBuffer( this.playerBody );
195 |
196 | mat4.translate( this.modelMatrix, [ 0, 0, 1.4 ] );
197 | mat4.rotateX( this.modelMatrix, 0.75* aniangle);
198 | gl.uniformMatrix4fv( this.uModelMat, false, this.modelMatrix );
199 | this.drawBuffer( this.playerLeftArm );
200 |
201 | mat4.rotateX( this.modelMatrix, -1.5*aniangle);
202 | gl.uniformMatrix4fv( this.uModelMat, false, this.modelMatrix );
203 | this.drawBuffer( this.playerRightArm );
204 | mat4.rotateX( this.modelMatrix, 0.75*aniangle);
205 |
206 | mat4.translate( this.modelMatrix, [ 0, 0, -0.67 ] );
207 |
208 | mat4.rotateX( this.modelMatrix, 0.5*aniangle);
209 | gl.uniformMatrix4fv( this.uModelMat, false, this.modelMatrix );
210 | this.drawBuffer( this.playerRightLeg );
211 |
212 | mat4.rotateX( this.modelMatrix, -aniangle);
213 | gl.uniformMatrix4fv( this.uModelMat, false, this.modelMatrix );
214 | this.drawBuffer( this.playerLeftLeg );
215 |
216 | // Draw player name
217 | if ( !player.nametag ) {
218 | player.nametag = this.buildPlayerName( player.nick );
219 | }
220 |
221 | // Calculate angle so that the nametag always faces the local player
222 | var ang = -Math.PI/2 + Math.atan2( this.camPos[1] - player.y, this.camPos[0] - player.x );
223 |
224 | mat4.identity( this.modelMatrix );
225 | mat4.translate( this.modelMatrix, [ player.x, player.y, player.z + 2.05 ] );
226 | mat4.rotateZ( this.modelMatrix, ang );
227 | mat4.scale( this.modelMatrix, [ 0.005, 1, 0.005 ] );
228 | gl.uniformMatrix4fv( this.uModelMat, false, this.modelMatrix );
229 |
230 | gl.bindTexture( gl.TEXTURE_2D, player.nametag.texture );
231 | this.drawBuffer( player.nametag.model );
232 | }
233 |
234 | gl.disable( gl.BLEND );
235 |
236 | mat4.identity( this.modelMatrix );
237 | gl.uniformMatrix4fv( this.uModelMat, false, this.modelMatrix );
238 | }
239 |
240 | // buildPlayerName( nickname )
241 | //
242 | // Returns the texture and vertex buffer for drawing the name
243 | // tag of the specified player.
244 |
245 | Renderer.prototype.buildPlayerName = function( nickname )
246 | {
247 | var gl = this.gl;
248 | var canvas = this.textCanvas;
249 | var ctx = this.textContext;
250 |
251 | nickname = nickname.replace( /</g, "<" ).replace( />/g, ">" ).replace( /"/, "\"" );
252 |
253 | var w = ctx.measureText( nickname ).width + 16;
254 | var h = 45;
255 |
256 | // Draw text box
257 | ctx.fillStyle = "#000";
258 | ctx.fillRect( 0, 0, w, 45 );
259 |
260 | ctx.fillStyle = "#fff";
261 | ctx.fillText( nickname, 10, 20 );
262 |
263 | // Create texture
264 | var tex = gl.createTexture();
265 | gl.bindTexture( gl.TEXTURE_2D, tex );
266 | gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas );
267 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
268 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
269 |
270 | // Create model
271 | var vertices = [
272 | -w/2, 0, h, w/256, 0, 1, 1, 1, 0.7,
273 | w/2, 0, h, 0, 0, 1, 1, 1, 0.7,
274 | w/2, 0, 0, 0, h/64, 1, 1, 1, 0.7,
275 | w/2, 0, 0, 0, h/64, 1, 1, 1, 0.7,
276 | -w/2, 0, 0, w/256, h/64, 1, 1, 1, 0.7,
277 | -w/2, 0, h, w/256, 0, 1, 1, 1, 0.7
278 | ];
279 |
280 | var buffer = gl.createBuffer();
281 | buffer.vertices = vertices.length / 9;
282 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
283 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.STATIC_DRAW );
284 |
285 | return {
286 | texture: tex,
287 | model: buffer
288 | };
289 | }
290 |
291 | // pickAt( min, max, mx, myy )
292 | //
293 | // Returns the block at mouse position mx and my.
294 | // The blocks that can be reached lie between min and max.
295 | //
296 | // Each side is rendered with the X, Y and Z position of the
297 | // block in the RGB color values and the normal of the side is
298 | // stored in the color alpha value. In that way, all information
299 | // can be retrieved by simply reading the pixel the mouse is over.
300 | //
301 | // WARNING: This implies that the level can never be larger than
302 | // 254x254x254 blocks! (Value 255 is used for sky.)
303 |
304 | Renderer.prototype.pickAt = function( min, max, mx, my )
305 | {
306 | var gl = this.gl;
307 | var world = this.world;
308 |
309 | // Create framebuffer for picking render
310 | var fbo = gl.createFramebuffer();
311 | gl.bindFramebuffer( gl.FRAMEBUFFER, fbo );
312 |
313 | var bt = gl.createTexture();
314 | gl.bindTexture( gl.TEXTURE_2D, bt );
315 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
316 | gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
317 | gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 512, 512, 0, gl.RGBA, gl.UNSIGNED_BYTE, null );
318 |
319 | var renderbuffer = gl.createRenderbuffer();
320 | gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer );
321 | gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 512, 512 );
322 |
323 | gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, bt, 0 );
324 | gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );
325 |
326 | // Build buffer with block pick candidates
327 | var vertices = [];
328 |
329 | for ( var x = min.x; x <= max.x; x++ ) {
330 | for ( var y = min.y; y <= max.y; y++ ) {
331 | for ( var z = min.z; z <= max.z; z++ ) {
332 | if ( world.getBlock( x, y, z ) != BLOCK.AIR )
333 | BLOCK.pushPickingVertices( vertices, x, y, z );
334 | }
335 | }
336 | }
337 |
338 | var buffer = gl.createBuffer();
339 | buffer.vertices = vertices.length / 9;
340 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
341 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.STREAM_DRAW );
342 |
343 | // Draw buffer
344 | gl.bindTexture( gl.TEXTURE_2D, this.texWhite );
345 |
346 | gl.viewport( 0, 0, 512, 512 );
347 | gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
348 | gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
349 |
350 | this.drawBuffer( buffer );
351 |
352 | // Read pixel
353 | var pixel = new Uint8Array( 4 );
354 | gl.readPixels( mx/gl.viewportWidth*512, (1-my/gl.viewportHeight)*512, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel );
355 |
356 | // Reset states
357 | gl.bindTexture( gl.TEXTURE_2D, this.texTerrain );
358 | gl.bindFramebuffer( gl.FRAMEBUFFER, null );
359 | gl.clearColor( 0.62, 0.81, 1.0, 1.0 );
360 |
361 | // Clean up
362 | gl.deleteBuffer( buffer );
363 | gl.deleteRenderbuffer( renderbuffer );
364 | gl.deleteTexture( bt );
365 | gl.deleteFramebuffer( fbo );
366 |
367 | // Build result
368 | if ( pixel[0] != 255 )
369 | {
370 | var normal;
371 | if ( pixel[3] == 1 ) normal = new Vector( 0, 0, 1 );
372 | else if ( pixel[3] == 2 ) normal = new Vector( 0, 0, -1 );
373 | else if ( pixel[3] == 3 ) normal = new Vector( 0, -1, 0 );
374 | else if ( pixel[3] == 4 ) normal = new Vector( 0, 1, 0 );
375 | else if ( pixel[3] == 5 ) normal = new Vector( -1, 0, 0 );
376 | else if ( pixel[3] == 6 ) normal = new Vector( 1, 0, 0 );
377 |
378 | return {
379 | x: pixel[0],
380 | y: pixel[1],
381 | z: pixel[2],
382 | n: normal
383 | }
384 | } else {
385 | return false;
386 | }
387 | }
388 |
389 | // updateViewport()
390 | //
391 | // Check if the viewport is still the same size and update
392 | // the render configuration if required.
393 |
394 | Renderer.prototype.updateViewport = function()
395 | {
396 | var gl = this.gl;
397 | var canvas = this.canvas;
398 |
399 | if ( canvas.clientWidth != gl.viewportWidth || canvas.clientHeight != gl.viewportHeight )
400 | {
401 | gl.viewportWidth = canvas.clientWidth;
402 | gl.viewportHeight = canvas.clientHeight;
403 |
404 | canvas.width = canvas.clientWidth;
405 | canvas.height = canvas.clientHeight;
406 |
407 | // Update perspective projection based on new w/h ratio
408 | this.setPerspective( this.fov, this.min, this.max );
409 | }
410 | }
411 |
412 | // loadShaders()
413 | //
414 | // Takes care of loading the shaders.
415 |
416 | Renderer.prototype.loadShaders = function()
417 | {
418 | var gl = this.gl;
419 |
420 | // Create shader program
421 | var program = this.program = gl.createProgram();
422 |
423 | // Compile vertex shader
424 | var vertexShader = gl.createShader( gl.VERTEX_SHADER );
425 | gl.shaderSource( vertexShader, vertexSource );
426 | gl.compileShader( vertexShader );
427 | gl.attachShader( program, vertexShader );
428 |
429 | if ( !gl.getShaderParameter( vertexShader, gl.COMPILE_STATUS ) )
430 | throw "Could not compile vertex shader!\n" + gl.getShaderInfoLog( vertexShader );
431 |
432 | // Compile fragment shader
433 | var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER );
434 | gl.shaderSource( fragmentShader, fragmentSource );
435 | gl.compileShader( fragmentShader );
436 | gl.attachShader( program, fragmentShader );
437 |
438 | if ( !gl.getShaderParameter( fragmentShader, gl.COMPILE_STATUS ) )
439 | throw "Could not compile fragment shader!\n" + gl.getShaderInfoLog( fragmentShader );
440 |
441 | // Finish program
442 | gl.linkProgram( program );
443 |
444 | if ( !gl.getProgramParameter( program, gl.LINK_STATUS ) )
445 | throw "Could not link the shader program!";
446 |
447 | gl.useProgram( program );
448 |
449 | // Store variable locations
450 | this.uProjMat = gl.getUniformLocation( program, "uProjMatrix" );
451 | this.uViewMat= gl.getUniformLocation( program, "uViewMatrix" );
452 | this.uModelMat= gl.getUniformLocation( program, "uModelMatrix" );
453 | this.uSampler = gl.getUniformLocation( program, "uSampler" );
454 | this.aPos = gl.getAttribLocation( program, "aPos" );
455 | this.aColor = gl.getAttribLocation( program, "aColor" );
456 | this.aTexCoord = gl.getAttribLocation( program, "aTexCoord" );
457 |
458 | // Enable input
459 | gl.enableVertexAttribArray( this.aPos );
460 | gl.enableVertexAttribArray( this.aColor );
461 | gl.enableVertexAttribArray( this.aTexCoord );
462 | }
463 |
464 | // setWorld( world, chunkSize )
465 | //
466 | // Makes the renderer start tracking a new world and set up the chunk structure.
467 | //
468 | // world - The world object to operate on.
469 | // chunkSize - X, Y and Z dimensions of each chunk, doesn't have to fit exactly inside the world.
470 |
471 | Renderer.prototype.setWorld = function( world, chunkSize )
472 | {
473 | this.world = world;
474 | world.renderer = this;
475 | this.chunkSize = chunkSize;
476 |
477 | // Create chunk list
478 | var chunks = this.chunks = [];
479 | for ( var x = 0; x < world.sx; x += chunkSize ) {
480 | for ( var y = 0; y < world.sy; y += chunkSize ) {
481 | for ( var z = 0; z < world.sz; z += chunkSize ) {
482 | chunks.push( {
483 | start: [ x, y, z ],
484 | end: [ Math.min( world.sx, x + chunkSize ), Math.min( world.sy, y + chunkSize ), Math.min( world.sz, z + chunkSize ) ],
485 | dirty: true
486 | } );
487 | }
488 | }
489 | }
490 | }
491 |
492 | // onBlockChanged( x, y, z )
493 | //
494 | // Callback from world to inform the renderer of a changed block
495 |
496 | Renderer.prototype.onBlockChanged = function( x, y, z )
497 | {
498 | var chunks = this.chunks;
499 |
500 | for ( var i = 0; i < chunks.length; i++ )
501 | {
502 | // Neighbouring chunks are updated as well if the block is on a chunk border
503 | // Also, all chunks below the block are updated because of lighting
504 | if ( x >= chunks[i].start[0] && x < chunks[i].end[0] && y >= chunks[i].start[1] && y < chunks[i].end[1] && z >= chunks[i].start[2] && z < chunks[i].end[2] )
505 | chunks[i].dirty = true;
506 | else if ( x >= chunks[i].start[0] && x < chunks[i].end[0] && y >= chunks[i].start[1] && y < chunks[i].end[1] && ( z >= chunks[i].end[2] || z == chunks[i].start[2] - 1 ) )
507 | chunks[i].dirty = true;
508 | else if ( x >= chunks[i].start[0] && x < chunks[i].end[0] && z >= chunks[i].start[2] && z < chunks[i].end[2] && ( y == chunks[i].end[1] || y == chunks[i].start[1] - 1 ) )
509 | chunks[i].dirty = true;
510 | else if ( y >= chunks[i].start[1] && y < chunks[i].end[1] && z >= chunks[i].start[2] && z < chunks[i].end[2] && ( x == chunks[i].end[0] || x == chunks[i].start[0] - 1 ) )
511 | chunks[i].dirty = true;
512 | }
513 | }
514 |
515 | // buildChunks( count )
516 | //
517 | // Build up to dirty chunks.
518 |
519 | function pushQuad( v, p1, p2, p3, p4 )
520 | {
521 | v.push( p1[0], p1[1], p1[2], p1[3], p1[4], p1[5], p1[6], p1[7], p1[8] );
522 | v.push( p2[0], p2[1], p2[2], p2[3], p2[4], p2[5], p2[6], p2[7], p2[8] );
523 | v.push( p3[0], p3[1], p3[2], p3[3], p3[4], p3[5], p3[6], p3[7], p3[8] );
524 |
525 | v.push( p3[0], p3[1], p3[2], p3[3], p3[4], p3[5], p3[6], p3[7], p3[8] );
526 | v.push( p4[0], p4[1], p4[2], p4[3], p4[4], p4[5], p4[6], p4[7], p4[8] );
527 | v.push( p1[0], p1[1], p1[2], p1[3], p1[4], p1[5], p1[6], p1[7], p1[8] );
528 | }
529 |
530 | Renderer.prototype.buildChunks = function( count )
531 | {
532 | var gl = this.gl;
533 | var chunks = this.chunks;
534 | var world = this.world;
535 |
536 | for ( var i = 0; i < chunks.length; i++ )
537 | {
538 | var chunk = chunks[i];
539 |
540 | if ( chunk.dirty )
541 | {
542 | var vertices = [];
543 |
544 | // Create map of lowest blocks that are still lit
545 | var lightmap = {};
546 | for ( var x = chunk.start[0] - 1; x < chunk.end[0] + 1; x++ )
547 | {
548 | lightmap[x] = {};
549 |
550 | for ( var y = chunk.start[1] - 1; y < chunk.end[1] + 1; y++ )
551 | {
552 | for ( var z = world.sz - 1; z >= 0; z-- )
553 | {
554 | lightmap[x][y] = z;
555 | if ( !world.getBlock( x, y, z ).transparent ) break;
556 | }
557 | }
558 | }
559 |
560 | // Add vertices for blocks
561 | for ( var x = chunk.start[0]; x < chunk.end[0]; x++ ) {
562 | for ( var y = chunk.start[1]; y < chunk.end[1]; y++ ) {
563 | for ( var z = chunk.start[2]; z < chunk.end[2]; z++ ) {
564 | if ( world.blocks[x][y][z] == BLOCK.AIR ) continue;
565 | BLOCK.pushVertices( vertices, world, lightmap, x, y, z );
566 | }
567 | }
568 | }
569 |
570 | // Create WebGL buffer
571 | if ( chunk.buffer ) gl.deleteBuffer( chunk.buffer );
572 |
573 | var buffer = chunk.buffer = gl.createBuffer();
574 | buffer.vertices = vertices.length / 9;
575 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
576 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.STATIC_DRAW );
577 |
578 | chunk.dirty = false;
579 | count--;
580 | }
581 |
582 | if ( count == 0 ) break;
583 | }
584 | }
585 |
586 | // setPerspective( fov, min, max )
587 | //
588 | // Sets the properties of the perspective projection.
589 |
590 | Renderer.prototype.setPerspective = function( fov, min, max )
591 | {
592 | var gl = this.gl;
593 |
594 | this.fov = fov;
595 | this.min = min;
596 | this.max = max;
597 |
598 | mat4.perspective( fov, gl.viewportWidth / gl.viewportHeight, min, max, this.projMatrix );
599 | gl.uniformMatrix4fv( this.uProjMat, false, this.projMatrix );
600 | }
601 |
602 | // setCamera( pos, ang )
603 | //
604 | // Moves the camera to the specified orientation.
605 | //
606 | // pos - Position in world coordinates.
607 | // ang - Pitch, yaw and roll.
608 |
609 | Renderer.prototype.setCamera = function( pos, ang )
610 | {
611 | var gl = this.gl;
612 |
613 | this.camPos = pos;
614 |
615 | mat4.identity( this.viewMatrix );
616 |
617 | mat4.rotate( this.viewMatrix, -ang[0] - Math.PI / 2, [ 1, 0, 0 ], this.viewMatrix );
618 | mat4.rotate( this.viewMatrix, ang[1], [ 0, 0, 1 ], this.viewMatrix );
619 | mat4.rotate( this.viewMatrix, -ang[2], [ 0, 1, 0 ], this.viewMatrix );
620 |
621 | mat4.translate( this.viewMatrix, [ -pos[0], -pos[1], -pos[2] ], this.viewMatrix );
622 |
623 | gl.uniformMatrix4fv( this.uViewMat, false, this.viewMatrix );
624 | }
625 |
626 | Renderer.prototype.drawBuffer = function( buffer )
627 | {
628 | var gl = this.gl;
629 |
630 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
631 |
632 | gl.vertexAttribPointer( this.aPos, 3, gl.FLOAT, false, 9*4, 0 );
633 | gl.vertexAttribPointer( this.aColor, 4, gl.FLOAT, false, 9*4, 5*4 );
634 | gl.vertexAttribPointer( this.aTexCoord, 2, gl.FLOAT, false, 9*4, 3*4 );
635 |
636 | gl.drawArrays( gl.TRIANGLES, 0, buffer.vertices );
637 | }
638 |
639 | // loadPlayerHeadModel()
640 | //
641 | // Loads the player head model into a vertex buffer for rendering.
642 |
643 | Renderer.prototype.loadPlayerHeadModel = function()
644 | {
645 | var gl = this.gl;
646 |
647 | // Player head
648 | var vertices = [
649 | // Top
650 | -0.25, -0.25, 0.25, 8/64, 0, 1, 1, 1, 1,
651 | 0.25, -0.25, 0.25, 16/64, 0, 1, 1, 1, 1,
652 | 0.25, 0.25, 0.25, 16/64, 8/32, 1, 1, 1, 1,
653 | 0.25, 0.25, 0.25, 16/64, 8/32, 1, 1, 1, 1,
654 | -0.25, 0.25, 0.25, 8/64, 8/32, 1, 1, 1, 1,
655 | -0.25, -0.25, 0.25, 8/64, 0, 1, 1, 1, 1,
656 |
657 | // Bottom
658 | -0.25, -0.25, -0.25, 16/64, 0, 1, 1, 1, 1,
659 | -0.25, 0.25, -0.25, 16/64, 8/32, 1, 1, 1, 1,
660 | 0.25, 0.25, -0.25, 24/64, 8/32, 1, 1, 1, 1,
661 | 0.25, 0.25, -0.25, 24/64, 8/32, 1, 1, 1, 1,
662 | 0.25, -0.25, -0.25, 24/64, 0, 1, 1, 1, 1,
663 | -0.25, -0.25, -0.25, 16/64, 0, 1, 1, 1, 1,
664 |
665 | // Front
666 | -0.25, -0.25, 0.25, 8/64, 8/32, 1, 1, 1, 1,
667 | -0.25, -0.25, -0.25, 8/64, 16/32, 1, 1, 1, 1,
668 | 0.25, -0.25, -0.25, 16/64, 16/32, 1, 1, 1, 1,
669 | 0.25, -0.25, -0.25, 16/64, 16/32, 1, 1, 1, 1,
670 | 0.25, -0.25, 0.25, 16/64, 8/32, 1, 1, 1, 1,
671 | -0.25, -0.25, 0.25, 8/64, 8/32, 1, 1, 1, 1,
672 |
673 | // Rear
674 | -0.25, 0.25, 0.25, 24/64, 8/32, 1, 1, 1, 1,
675 | 0.25, 0.25, 0.25, 32/64, 8/32, 1, 1, 1, 1,
676 | 0.25, 0.25, -0.25, 32/64, 16/32, 1, 1, 1, 1,
677 | 0.25, 0.25, -0.25, 32/64, 16/32, 1, 1, 1, 1,
678 | -0.25, 0.25, -0.25, 24/64, 16/32, 1, 1, 1, 1,
679 | -0.25, 0.25, 0.25, 24/64, 8/32, 1, 1, 1, 1,
680 |
681 | // Right
682 | -0.25, -0.25, 0.25, 16/64, 8/32, 1, 1, 1, 1,
683 | -0.25, 0.25, 0.25, 24/64, 8/32, 1, 1, 1, 1,
684 | -0.25, 0.25, -0.25, 24/64, 16/32, 1, 1, 1, 1,
685 | -0.25, 0.25, -0.25, 24/64, 16/32, 1, 1, 1, 1,
686 | -0.25, -0.25, -0.25, 16/64, 16/32, 1, 1, 1, 1,
687 | -0.25, -0.25, 0.25, 16/64, 8/32, 1, 1, 1, 1,
688 |
689 | // Left
690 | 0.25, -0.25, 0.25, 0, 8/32, 1, 1, 1, 1,
691 | 0.25, -0.25, -0.25, 0, 16/32, 1, 1, 1, 1,
692 | 0.25, 0.25, -0.25, 8/64, 16/32, 1, 1, 1, 1,
693 | 0.25, 0.25, -0.25, 8/64, 16/32, 1, 1, 1, 1,
694 | 0.25, 0.25, 0.25, 8/64, 8/32, 1, 1, 1, 1,
695 | 0.25, -0.25, 0.25, 0, 8/32, 1, 1, 1, 1
696 | ];
697 |
698 | var buffer = this.playerHead = gl.createBuffer();
699 | buffer.vertices = vertices.length / 9;
700 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
701 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.DYNAMIC_DRAW );
702 | }
703 |
704 | // loadPlayerBodyModel()
705 | //
706 | // Loads the player body model into a vertex buffer for rendering.
707 |
708 | Renderer.prototype.loadPlayerBodyModel = function()
709 | {
710 | var gl = this.gl;
711 |
712 | var vertices = [
713 | // Player torso
714 |
715 | // Top
716 | -0.30, -0.125, 1.45, 20/64, 16/32, 1, 1, 1, 1,
717 | 0.30, -0.125, 1.45, 28/64, 16/32, 1, 1, 1, 1,
718 | 0.30, 0.125, 1.45, 28/64, 20/32, 1, 1, 1, 1,
719 | 0.30, 0.125, 1.45, 28/64, 20/32, 1, 1, 1, 1,
720 | -0.30, 0.125, 1.45, 20/64, 20/32, 1, 1, 1, 1,
721 | -0.30, -0.125, 1.45, 20/64, 16/32, 1, 1, 1, 1,
722 |
723 | // Bottom
724 | -0.30, -0.125, 0.73, 28/64, 16/32, 1, 1, 1, 1,
725 | -0.30, 0.125, 0.73, 28/64, 20/32, 1, 1, 1, 1,
726 | 0.30, 0.125, 0.73, 36/64, 20/32, 1, 1, 1, 1,
727 | 0.30, 0.125, 0.73, 36/64, 20/32, 1, 1, 1, 1,
728 | 0.30, -0.125, 0.73, 36/64, 16/32, 1, 1, 1, 1,
729 | -0.30, -0.125, 0.73, 28/64, 16/32, 1, 1, 1, 1,
730 |
731 | // Front
732 | -0.30, -0.125, 1.45, 20/64, 20/32, 1, 1, 1, 1,
733 | -0.30, -0.125, 0.73, 20/64, 32/32, 1, 1, 1, 1,
734 | 0.30, -0.125, 0.73, 28/64, 32/32, 1, 1, 1, 1,
735 | 0.30, -0.125, 0.73, 28/64, 32/32, 1, 1, 1, 1,
736 | 0.30, -0.125, 1.45, 28/64, 20/32, 1, 1, 1, 1,
737 | -0.30, -0.125, 1.45, 20/64, 20/32, 1, 1, 1, 1,
738 |
739 | // Rear
740 | -0.30, 0.125, 1.45, 40/64, 20/32, 1, 1, 1, 1,
741 | 0.30, 0.125, 1.45, 32/64, 20/32, 1, 1, 1, 1,
742 | 0.30, 0.125, 0.73, 32/64, 32/32, 1, 1, 1, 1,
743 | 0.30, 0.125, 0.73, 32/64, 32/32, 1, 1, 1, 1,
744 | -0.30, 0.125, 0.73, 40/64, 32/32, 1, 1, 1, 1,
745 | -0.30, 0.125, 1.45, 40/64, 20/32, 1, 1, 1, 1,
746 |
747 | // Right
748 | -0.30, -0.125, 1.45, 16/64, 20/32, 1, 1, 1, 1,
749 | -0.30, 0.125, 1.45, 20/64, 20/32, 1, 1, 1, 1,
750 | -0.30, 0.125, 0.73, 20/64, 32/32, 1, 1, 1, 1,
751 | -0.30, 0.125, 0.73, 20/64, 32/32, 1, 1, 1, 1,
752 | -0.30, -0.125, 0.73, 16/64, 32/32, 1, 1, 1, 1,
753 | -0.30, -0.125, 1.45, 16/64, 20/32, 1, 1, 1, 1,
754 |
755 | // Left
756 | 0.30, -0.125, 1.45, 28/64, 20/32, 1, 1, 1, 1,
757 | 0.30, -0.125, 0.73, 28/64, 32/32, 1, 1, 1, 1,
758 | 0.30, 0.125, 0.73, 32/64, 32/32, 1, 1, 1, 1,
759 | 0.30, 0.125, 0.73, 32/64, 32/32, 1, 1, 1, 1,
760 | 0.30, 0.125, 1.45, 32/64, 20/32, 1, 1, 1, 1,
761 | 0.30, -0.125, 1.45, 28/64, 20/32, 1, 1, 1, 1,
762 |
763 | ];
764 |
765 | var buffer = this.playerBody = gl.createBuffer();
766 | buffer.vertices = vertices.length / 9;
767 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
768 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.DYNAMIC_DRAW );
769 |
770 | var vertices = [
771 | // Left arm
772 |
773 | // Top
774 | 0.30, -0.125, 0.05, 44/64, 16/32, 1, 1, 1, 1,
775 | 0.55, -0.125, 0.05, 48/64, 16/32, 1, 1, 1, 1,
776 | 0.55, 0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
777 | 0.55, 0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
778 | 0.30, 0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
779 | 0.30, -0.125, 0.05, 44/64, 16/32, 1, 1, 1, 1,
780 |
781 | // Bottom
782 | 0.30, -0.125, -0.67, 48/64, 16/32, 1, 1, 1, 1,
783 | 0.30, 0.125, -0.67, 48/64, 20/32, 1, 1, 1, 1,
784 | 0.55, 0.125, -0.67, 52/64, 20/32, 1, 1, 1, 1,
785 | 0.55, 0.125, -0.67, 52/64, 20/32, 1, 1, 1, 1,
786 | 0.55, -0.125, -0.67, 52/64, 16/32, 1, 1, 1, 1,
787 | 0.30, -0.125, -0.67, 48/64, 16/32, 1, 1, 1, 1,
788 |
789 | // Front
790 | 0.30, -0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
791 | 0.30, -0.125, -0.67, 48/64, 32/32, 1, 1, 1, 1,
792 | 0.55, -0.125, -0.67, 44/64, 32/32, 1, 1, 1, 1,
793 | 0.55, -0.125, -0.67, 44/64, 32/32, 1, 1, 1, 1,
794 | 0.55, -0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
795 | 0.30, -0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
796 |
797 | // Rear
798 | 0.30, 0.125, 0.05, 52/64, 20/32, 1, 1, 1, 1,
799 | 0.55, 0.125, 0.05, 56/64, 20/32, 1, 1, 1, 1,
800 | 0.55, 0.125, -0.67, 56/64, 32/32, 1, 1, 1, 1,
801 | 0.55, 0.125, -0.67, 56/64, 32/32, 1, 1, 1, 1,
802 | 0.30, 0.125, -0.67, 52/64, 32/32, 1, 1, 1, 1,
803 | 0.30, 0.125, 0.05, 52/64, 20/32, 1, 1, 1, 1,
804 |
805 | // Right
806 | 0.30, -0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
807 | 0.30, 0.125, 0.05, 52/64, 20/32, 1, 1, 1, 1,
808 | 0.30, 0.125, -0.67, 52/64, 32/32, 1, 1, 1, 1,
809 | 0.30, 0.125, -0.67, 52/64, 32/32, 1, 1, 1, 1,
810 | 0.30, -0.125, -0.67, 48/64, 32/32, 1, 1, 1, 1,
811 | 0.30, -0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
812 |
813 | // Left
814 | 0.55, -0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
815 | 0.55, -0.125, -0.67, 44/64, 32/32, 1, 1, 1, 1,
816 | 0.55, 0.125, -0.67, 40/64, 32/32, 1, 1, 1, 1,
817 | 0.55, 0.125, -0.67, 40/64, 32/32, 1, 1, 1, 1,
818 | 0.55, 0.125, 0.05, 40/64, 20/32, 1, 1, 1, 1,
819 | 0.55, -0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
820 |
821 | ];
822 |
823 | var buffer = this.playerLeftArm = gl.createBuffer();
824 | buffer.vertices = vertices.length / 9;
825 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
826 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.DYNAMIC_DRAW );
827 |
828 | var vertices = [
829 | // Right arm
830 |
831 | // Top
832 | -0.55, -0.125, 0.05, 44/64, 16/32, 1, 1, 1, 1,
833 | -0.30, -0.125, 0.05, 48/64, 16/32, 1, 1, 1, 1,
834 | -0.30, 0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
835 | -0.30, 0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
836 | -0.55, 0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
837 | -0.55, -0.125, 0.05, 44/64, 16/32, 1, 1, 1, 1,
838 |
839 | // Bottom
840 | -0.55, -0.125, -0.67, 52/64, 16/32, 1, 1, 1, 1,
841 | -0.55, 0.125, -0.67, 52/64, 20/32, 1, 1, 1, 1,
842 | -0.30, 0.125, -0.67, 48/64, 20/32, 1, 1, 1, 1,
843 | -0.30, 0.125, -0.67, 48/64, 20/32, 1, 1, 1, 1,
844 | -0.30, -0.125, -0.67, 48/64, 16/32, 1, 1, 1, 1,
845 | -0.55, -0.125, -0.67, 52/64, 16/32, 1, 1, 1, 1,
846 |
847 | // Front
848 | -0.55, -0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
849 | -0.55, -0.125, -0.67, 44/64, 32/32, 1, 1, 1, 1,
850 | -0.30, -0.125, -0.67, 48/64, 32/32, 1, 1, 1, 1,
851 | -0.30, -0.125, -0.67, 48/64, 32/32, 1, 1, 1, 1,
852 | -0.30, -0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
853 | -0.55, -0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
854 |
855 | // Rear
856 | -0.55, 0.125, 0.05, 56/64, 20/32, 1, 1, 1, 1,
857 | -0.30, 0.125, 0.05, 52/64, 20/32, 1, 1, 1, 1,
858 | -0.30, 0.125, -0.67, 52/64, 32/32, 1, 1, 1, 1,
859 | -0.30, 0.125, -0.67, 52/64, 32/32, 1, 1, 1, 1,
860 | -0.55, 0.125, -0.67, 56/64, 32/32, 1, 1, 1, 1,
861 | -0.55, 0.125, 0.05, 56/64, 20/32, 1, 1, 1, 1,
862 |
863 | // Right
864 | -0.55, -0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
865 | -0.55, 0.125, 0.05, 40/64, 20/32, 1, 1, 1, 1,
866 | -0.55, 0.125, -0.67, 40/64, 32/32, 1, 1, 1, 1,
867 | -0.55, 0.125, -0.67, 40/64, 32/32, 1, 1, 1, 1,
868 | -0.55, -0.125, -0.67, 44/64, 32/32, 1, 1, 1, 1,
869 | -0.55, -0.125, 0.05, 44/64, 20/32, 1, 1, 1, 1,
870 |
871 | // Left
872 | -0.30, -0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
873 | -0.30, -0.125, -0.67, 48/64, 32/32, 1, 1, 1, 1,
874 | -0.30, 0.125, -0.67, 52/64, 32/32, 1, 1, 1, 1,
875 | -0.30, 0.125, -0.67, 52/64, 32/32, 1, 1, 1, 1,
876 | -0.30, 0.125, 0.05, 52/64, 20/32, 1, 1, 1, 1,
877 | -0.30, -0.125, 0.05, 48/64, 20/32, 1, 1, 1, 1,
878 |
879 | ];
880 |
881 | var buffer = this.playerRightArm = gl.createBuffer();
882 | buffer.vertices = vertices.length / 9;
883 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
884 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.DYNAMIC_DRAW );
885 |
886 | var vertices = [
887 | // Left leg
888 |
889 | // Top
890 | 0.01, -0.125, 0, 4/64, 16/32, 1, 1, 1, 1,
891 | 0.3, -0.125, 0, 8/64, 16/32, 1, 1, 1, 1,
892 | 0.3, 0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
893 | 0.3, 0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
894 | 0.01, 0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
895 | 0.01, -0.125, 0, 4/64, 16/32, 1, 1, 1, 1,
896 |
897 | // Bottom
898 | 0.01, -0.125, -0.73, 8/64, 16/32, 1, 1, 1, 1,
899 | 0.01, 0.125, -0.73, 8/64, 20/32, 1, 1, 1, 1,
900 | 0.3, 0.125, -0.73, 12/64, 20/32, 1, 1, 1, 1,
901 | 0.3, 0.125, -0.73, 12/64, 20/32, 1, 1, 1, 1,
902 | 0.3, -0.125, -0.73, 12/64, 16/32, 1, 1, 1, 1,
903 | 0.01, -0.125, -0.73, 8/64, 16/32, 1, 1, 1, 1,
904 |
905 | // Front
906 | 0.01, -0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
907 | 0.01, -0.125, -0.73, 4/64, 32/32, 1, 1, 1, 1,
908 | 0.3, -0.125, -0.73, 8/64, 32/32, 1, 1, 1, 1,
909 | 0.3, -0.125, -0.73, 8/64, 32/32, 1, 1, 1, 1,
910 | 0.3, -0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
911 | 0.01, -0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
912 |
913 | // Rear
914 | 0.01, 0.125, 0, 12/64, 20/32, 1, 1, 1, 1,
915 | 0.3, 0.125, 0, 16/64, 20/32, 1, 1, 1, 1,
916 | 0.3, 0.125, -0.73, 16/64, 32/32, 1, 1, 1, 1,
917 | 0.3, 0.125, -0.73, 16/64, 32/32, 1, 1, 1, 1,
918 | 0.01, 0.125, -0.73, 12/64, 32/32, 1, 1, 1, 1,
919 | 0.01, 0.125, 0, 12/64, 20/32, 1, 1, 1, 1,
920 |
921 | // Right
922 | 0.01, -0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
923 | 0.01, 0.125, 0, 12/64, 20/32, 1, 1, 1, 1,
924 | 0.01, 0.125, -0.73, 12/64, 32/32, 1, 1, 1, 1,
925 | 0.01, 0.125, -0.73, 12/64, 32/32, 1, 1, 1, 1,
926 | 0.01, -0.125, -0.73, 8/64, 32/32, 1, 1, 1, 1,
927 | 0.01, -0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
928 |
929 | // Left
930 | 0.3, -0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
931 | 0.3, -0.125, -0.73, 4/64, 32/32, 1, 1, 1, 1,
932 | 0.3, 0.125, -0.73, 0/64, 32/32, 1, 1, 1, 1,
933 | 0.3, 0.125, -0.73, 0/64, 32/32, 1, 1, 1, 1,
934 | 0.3, 0.125, 0, 0/64, 20/32, 1, 1, 1, 1,
935 | 0.3, -0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
936 | ];
937 |
938 | var buffer = this.playerLeftLeg = gl.createBuffer();
939 | buffer.vertices = vertices.length / 9;
940 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
941 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.DYNAMIC_DRAW );
942 |
943 | var vertices = [
944 | // Right leg
945 |
946 | // Top
947 | -0.3, -0.125, 0, 4/64, 16/32, 1, 1, 1, 1,
948 | -0.01, -0.125, 0, 8/64, 16/32, 1, 1, 1, 1,
949 | -0.01, 0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
950 | -0.01, 0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
951 | -0.3, 0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
952 | -0.3, -0.125, 0, 4/64, 16/32, 1, 1, 1, 1,
953 |
954 | // Bottom
955 | -0.3, -0.125, -0.73, 8/64, 16/32, 1, 1, 1, 1,
956 | -0.3, 0.125, -0.73, 8/64, 20/32, 1, 1, 1, 1,
957 | -0.01, 0.125, -0.73, 12/64, 20/32, 1, 1, 1, 1,
958 | -0.01, 0.125, -0.73, 12/64, 20/32, 1, 1, 1, 1,
959 | -0.01, -0.125, -0.73, 12/64, 16/32, 1, 1, 1, 1,
960 | -0.3, -0.125, -0.73, 8/64, 16/32, 1, 1, 1, 1,
961 |
962 | // Front
963 | -0.3, -0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
964 | -0.3, -0.125, -0.73, 4/64, 32/32, 1, 1, 1, 1,
965 | -0.01, -0.125, -0.73, 8/64, 32/32, 1, 1, 1, 1,
966 | -0.01, -0.125, -0.73, 8/64, 32/32, 1, 1, 1, 1,
967 | -0.01, -0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
968 | -0.3, -0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
969 |
970 | // Rear
971 | -0.3, 0.125, 0, 16/64, 20/32, 1, 1, 1, 1,
972 | -0.01, 0.125, 0, 12/64, 20/32, 1, 1, 1, 1,
973 | -0.01, 0.125, -0.73, 12/64, 32/32, 1, 1, 1, 1,
974 | -0.01, 0.125, -0.73, 12/64, 32/32, 1, 1, 1, 1,
975 | -0.3, 0.125, -0.73, 16/64, 32/32, 1, 1, 1, 1,
976 | -0.3, 0.125, 0, 16/64, 20/32, 1, 1, 1, 1,
977 |
978 | // Right
979 | -0.3, -0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
980 | -0.3, 0.125, 0, 0/64, 20/32, 1, 1, 1, 1,
981 | -0.3, 0.125, -0.73, 0/64, 32/32, 1, 1, 1, 1,
982 | -0.3, 0.125, -0.73, 0/64, 32/32, 1, 1, 1, 1,
983 | -0.3, -0.125, -0.73, 4/64, 32/32, 1, 1, 1, 1,
984 | -0.3, -0.125, 0, 4/64, 20/32, 1, 1, 1, 1,
985 |
986 | // Left
987 | -0.01, -0.125, 0, 8/64, 20/32, 1, 1, 1, 1,
988 | -0.01, -0.125, -0.73, 8/64, 32/32, 1, 1, 1, 1,
989 | -0.01, 0.125, -0.73, 12/64, 32/32, 1, 1, 1, 1,
990 | -0.01, 0.125, -0.73, 12/64, 32/32, 1, 1, 1, 1,
991 | -0.01, 0.125, 0, 12/64, 20/32, 1, 1, 1, 1,
992 | -0.01, -0.125, 0, 8/64, 20/32, 1, 1, 1, 1
993 | ];
994 |
995 | var buffer = this.playerRightLeg = gl.createBuffer();
996 | buffer.vertices = vertices.length / 9;
997 | gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
998 | gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.DYNAMIC_DRAW );
999 | }
1000 |
--------------------------------------------------------------------------------
/js/world.js:
--------------------------------------------------------------------------------
1 | // ==========================================
2 | // World container
3 | //
4 | // This class contains the elements that make up the game world.
5 | // Other modules retrieve information from the world or alter it
6 | // using this class.
7 | // ==========================================
8 |
9 | // Constructor( sx, sy, sz )
10 | //
11 | // Creates a new world container with the specified world size.
12 | // Up and down should always be aligned with the Z-direction.
13 | //
14 | // sx - World size in the X-direction.
15 | // sy - World size in the Y-direction.
16 | // sz - World size in the Z-direction.
17 |
18 | function World( sx, sy, sz )
19 | {
20 | // Initialise world array
21 | this.blocks = new Array( sx );
22 | for ( var x = 0; x < sx; x++ )
23 | {
24 | this.blocks[x] = new Array( sy );
25 | for ( var y = 0; y < sy; y++ )
26 | {
27 | this.blocks[x][y] = new Array( sz );
28 | }
29 | }
30 | this.sx = sx;
31 | this.sy = sy;
32 | this.sz = sz;
33 |
34 | this.players = {};
35 | }
36 |
37 | // createFlatWorld()
38 | //
39 | // Sets up the world so that the bottom half is filled with dirt
40 | // and the top half with air.
41 |
42 | World.prototype.createFlatWorld = function( height )
43 | {
44 | this.spawnPoint = new Vector( this.sx / 2 + 0.5, this.sy / 2 + 0.5, height );
45 |
46 | for ( var x = 0; x < this.sx; x++ )
47 | for ( var y = 0; y < this.sy; y++ )
48 | for ( var z = 0; z < this.sz; z++ )
49 | this.blocks[x][y][z] = z < height ? BLOCK.DIRT : BLOCK.AIR;
50 | }
51 |
52 | // createFromString( str )
53 | //
54 | // Creates a world from a string representation.
55 | // This is the opposite of toNetworkString().
56 | //
57 | // NOTE: The world must have already been created
58 | // with the appropriate size!
59 |
60 | World.prototype.createFromString = function( str )
61 | {
62 | var i = 0;
63 |
64 | for ( var x = 0; x < this.sx; x++ ) {
65 | for ( var y = 0; y < this.sy; y++ ) {
66 | for ( var z = 0; z < this.sz; z++ ) {
67 | this.blocks[x][y][z] = BLOCK.fromId( str.charCodeAt( i ) - 97 );
68 | i = i + 1;
69 | }
70 | }
71 | }
72 | }
73 |
74 | // getBlock( x, y, z )
75 | //
76 | // Get the type of the block at the specified position.
77 | // Mostly for neatness, since accessing the array
78 | // directly is easier and faster.
79 |
80 | World.prototype.getBlock = function( x, y, z )
81 | {
82 | if ( x < 0 || y < 0 || z < 0 || x > this.sx - 1 || y > this.sy - 1 || z > this.sz - 1 ) return BLOCK.AIR;
83 | return this.blocks[x][y][z];
84 | }
85 |
86 | // setBlock( x, y, z )
87 |
88 | World.prototype.setBlock = function( x, y, z, type )
89 | {
90 | this.blocks[x][y][z] = type;
91 | if ( this.renderer != null ) this.renderer.onBlockChanged( x, y, z );
92 | }
93 |
94 | // toNetworkString()
95 | //
96 | // Returns a string representation of this world.
97 |
98 | World.prototype.toNetworkString = function()
99 | {
100 | var blockArray = [];
101 |
102 | for ( var x = 0; x < this.sx; x++ )
103 | for ( var y = 0; y < this.sy; y++ )
104 | for ( var z = 0; z < this.sz; z++ )
105 | blockArray.push( String.fromCharCode( 97 + this.blocks[x][y][z].id ) );
106 |
107 | return blockArray.join( "" );
108 | }
109 |
110 | // Export to node.js
111 | if ( typeof( exports ) != "undefined" )
112 | {
113 | // loadFromFile( filename )
114 | //
115 | // Load a world from a file previously saved with saveToFile().
116 | // The world must have already been allocated with the
117 | // appropriate dimensions.
118 |
119 | World.prototype.loadFromFile = function( filename )
120 | {
121 | var fs = require( "fs" );
122 | try {
123 | fs.lstatSync( filename );
124 | var data = fs.readFileSync( filename, "utf8" ).split( "," );
125 | this.createFromString( data[3] );
126 | this.spawnPoint = new Vector( parseInt( data[0] ), parseInt( data[1] ), parseInt( data[2] ) );
127 | return true;
128 | } catch ( e ) {
129 | return false;
130 | }
131 | }
132 |
133 | // saveToFile( filename )
134 | //
135 | // Saves a world and the spawn point to a file.
136 | // The world can be loaded from it afterwards with loadFromFile().
137 |
138 | World.prototype.saveToFile = function( filename )
139 | {
140 | var data = this.spawnPoint.x + "," + this.spawnPoint.y + "," + this.spawnPoint.z + "," + this.toNetworkString();
141 | require( "fs" ).writeFileSync( filename, data );
142 | }
143 |
144 | exports.World = World;
145 | }
--------------------------------------------------------------------------------
/media/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Overv/WebCraft/47b3f6910864ea0d35ed203e1e337aa958fad606/media/background.png
--------------------------------------------------------------------------------
/media/blockthumbs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Overv/WebCraft/47b3f6910864ea0d35ed203e1e337aa958fad606/media/blockthumbs.png
--------------------------------------------------------------------------------
/media/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Overv/WebCraft/47b3f6910864ea0d35ed203e1e337aa958fad606/media/player.png
--------------------------------------------------------------------------------
/media/terrain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Overv/WebCraft/47b3f6910864ea0d35ed203e1e337aa958fad606/media/terrain.png
--------------------------------------------------------------------------------
/multiplayer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebCraft
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Nickname:
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
225 |
226 |
227 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // ==========================================
2 | // Server
3 | //
4 | // This file contains all of the code necessary for managing a
5 | // WebCraft server on the Node.js platform.
6 | // ==========================================
7 |
8 | // Parameters
9 | var WORLD_SX = 128;
10 | var WORLD_SY = 128;
11 | var WORLD_SZ = 32;
12 | var WORLD_GROUNDHEIGHT = 16;
13 | var SECONDS_BETWEEN_SAVES = 60;
14 | var ADMIN_IP = "";
15 |
16 | // Load modules
17 | var modules = {};
18 | modules.helpers = require( "./js/helpers.js" );
19 | modules.blocks = require( "./js/blocks.js" );
20 | modules.world = require( "./js/world.js" );
21 | modules.network = require( "./js/network.js" );
22 | modules.io = require( "socket.io" );
23 | modules.fs = require( "fs" );
24 | var log = require( "util" ).log;
25 |
26 | // Set-up evil globals
27 | global.Vector = modules.helpers.Vector;
28 | global.BLOCK = modules.blocks.BLOCK;
29 |
30 | // Create new empty world or load one from file
31 | var world = new modules.world.World( WORLD_SX, WORLD_SY, WORLD_SZ );
32 | log( "Creating world..." );
33 | if ( world.loadFromFile( "world" ) ) {
34 | log( "Loaded the world from file." );
35 | } else {
36 | log( "Creating a new empty world." );
37 | world.createFlatWorld( WORLD_GROUNDHEIGHT );
38 | world.saveToFile( "world" );
39 | }
40 |
41 | // Start server
42 | var server = new modules.network.Server( modules.io, 16 );
43 | server.setWorld( world );
44 | server.setLogger( log );
45 | server.setOneUserPerIp( true );
46 | log( "Waiting for clients..." );
47 |
48 | // Chat commands
49 | server.on( "chat", function( client, nickname, msg )
50 | {
51 | if ( msg == "/spawn" ) {
52 | server.setPos( client, world.spawnPoint.x, world.spawnPoint.y, world.spawnPoint.z );
53 | return true;
54 | } else if ( msg.substr( 0, 3 ) == "/tp" ) {
55 | var target = msg.substr( 4 );
56 | target = server.findPlayerByName( target );
57 |
58 | if ( target != null ) {
59 | server.setPos( client, target.x, target.y, target.z );
60 | server.sendMessage( nickname + " was teleported to " + target.nick + "." );
61 | return true;
62 | } else {
63 | server.sendMessage( "Couldn't find that player!", client );
64 | return false;
65 | }
66 | } else if ( msg.substr( 0, 5 ) == "/kick" && client.handshake.address.address == ADMIN_IP ) {
67 | var target = msg.substr( 6 );
68 | target = server.findPlayerByName( target );
69 |
70 | if ( target != null ) {
71 | server.kick( target.socket, "Kicked by Overv" );
72 | return true;
73 | } else {
74 | server.sendMessage( "Couldn't find that player!", client );
75 | return false;
76 | }
77 | } else if ( msg == "/list" ) {
78 | var playerlist = "";
79 | for ( var p in world.players )
80 | playerlist += p + ", ";
81 | playerlist = playerlist.substring( 0, playerlist.length - 2 );
82 | server.sendMessage( "Players: " + playerlist, client );
83 | return true;
84 | } else if ( msg.substr( 0, 1 ) == "/" ) {
85 | server.sendMessage( "Unknown command!", client );
86 | return false;
87 | }
88 | } );
89 |
90 | // Send a welcome message to new clients
91 | server.on( "join", function( client, nickname )
92 | {
93 | server.sendMessage( "Welcome! Enjoy your stay, " + nickname + "!", client );
94 | server.broadcastMessage( nickname + " joined the game.", client );
95 | } );
96 |
97 | // And let players know of a disconnecting user
98 | server.on( "leave", function( nickname )
99 | {
100 | server.sendMessage( nickname + " left the game." );
101 | } );
102 |
103 | // Periodical saves
104 | setInterval( function()
105 | {
106 | world.saveToFile( "world" );
107 | log( "Saved world to file." );
108 | }, SECONDS_BETWEEN_SAVES * 1000 );
--------------------------------------------------------------------------------
/singleplayer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebCraft
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
73 |
74 |
--------------------------------------------------------------------------------
/style/main.css:
--------------------------------------------------------------------------------
1 | /* Minecraft-like pixely font */
2 | @font-face {
3 | font-family: minecraftia;
4 | src: url( 'minecraftia.ttf' );
5 | }
6 |
7 | /* General page style */
8 |
9 | body {
10 | height: 100%;
11 |
12 | font-family: minecraftia;
13 | font-size: 16px;
14 |
15 | text-shadow: #3f3f3f 2px 2px 0px;
16 |
17 | background: url( '../media/background.png' );
18 | }
19 |
20 | /* Render surface */
21 |
22 | #renderSurface {
23 | position: absolute;
24 | left: 0;
25 | top: 0;
26 |
27 | width: 100%;
28 | height: 100%;
29 | }
30 |
31 | /* Material selector */
32 |
33 | #materialSelector {
34 | position: relative;
35 | margin: auto;
36 |
37 | background: rgba( 0, 0, 0, 0.6 );
38 | }
39 |
40 | #materialSelector tr {
41 | height: 70px;
42 | }
43 |
44 | #materialSelector td {
45 | width: 70px;
46 | margin: 0;
47 | padding: 0;
48 |
49 | cursor: pointer;
50 |
51 | opacity: 0.3;
52 |
53 | background: url( '../media/blockthumbs.png' );
54 | background-position: 0px 0px;
55 | }
56 |
57 | #materialSelector td:hover {
58 | opacity: 0.8;
59 | }
60 |
61 | /* Username input */
62 |
63 | #nickname {
64 | position: absolute;
65 | top: 40%;
66 | left: 42%;
67 |
68 | width: 300px;
69 |
70 | cursor: default;
71 |
72 | color: #fff;
73 | }
74 |
75 | #nickname input {
76 | width: 100%;
77 |
78 | background: none;
79 | border: none;
80 | border-bottom: 1px solid #888;
81 | outline: none;
82 |
83 | color: white;
84 |
85 | font-family: minecraftia;
86 | font-size: 24px;
87 | }
88 |
89 | /* Join information */
90 |
91 | #joininfo {
92 | position: absolute;
93 | top: 42%;
94 |
95 | width: 99%;
96 |
97 | cursor: default;
98 |
99 | text-align: center;
100 | color: #fff;
101 | font-size: 24px;
102 | }
103 |
104 | /* Chatbox */
105 |
106 | #chatbox {
107 | position: absolute;
108 | left: 20px;
109 | bottom: 55px;
110 |
111 | width: 600px;
112 | height: 195px;
113 | overflow: hidden;
114 |
115 | padding-left: 10px;
116 | padding-right: 10px;
117 |
118 | cursor: default;
119 |
120 | background: rgba( 0, 0, 0, 0.6 );
121 | color: white;
122 | }
123 |
124 | #chatbox_text {
125 | position: absolute;
126 | bottom: 8px;
127 |
128 | text-shadow: none;
129 | }
130 |
131 | #chatbox_entry {
132 | position: absolute;
133 | left: 20px;
134 | bottom: 18px;
135 |
136 | width: 610px;
137 | height: 30px;
138 |
139 | padding-left: 10px;
140 | padding-bottom: 2px;
141 |
142 | background: rgba( 0, 0, 0, 0.6 );
143 | border: none;
144 | outline: none;
145 |
146 | color: white;
147 | font-family: minecraftia;
148 | font-size: 16px;
149 | }
--------------------------------------------------------------------------------
/style/minecraftia.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Overv/WebCraft/47b3f6910864ea0d35ed203e1e337aa958fad606/style/minecraftia.ttf
--------------------------------------------------------------------------------
/style/minecraftia.txt:
--------------------------------------------------------------------------------
1 | The font file in this project was created by Andrew Tyler www.AndrewTyler.net and font@andrewtyler.net
2 |
3 | License:
4 | http://creativecommons.org/licenses/by-sa/3.0/us/
--------------------------------------------------------------------------------