├── 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 | [![GitHub issues](https://img.shields.io/github/issues/Overv/WebCraft.svg)](https://github.com/Overv/WebCraft/issues) 4 | [![GitHub forks](https://img.shields.io/github/forks/Overv/WebCraft.svg)](https://github.com/Overv/WebCraft/network) 5 | [![GitHub stars](https://img.shields.io/github/stars/Overv/WebCraft.svg)](https://github.com/Overv/WebCraft/stargazers) 6 | [![GitHub license](https://img.shields.io/github/license/Overv/WebCraft.svg)](https://github.com/Overv/WebCraft/blob/master/LICENSE) 7 | [![Maintainability](https://api.codeclimate.com/v1/badges/ce6335d88c8072a3f1d3/maintainability)](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 | ![Singleplayer structure](http://i.imgur.com/2qBGy.png) 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 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 |
41 | Nickname:
42 | 43 |
44 | 45 | 46 | 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 | 28 | 29 |
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/ --------------------------------------------------------------------------------