├── css └── style.css ├── data ├── maps │ └── webgl.bsp └── wads │ └── custom │ └── webgl.wad ├── images └── webgl.png ├── index.html ├── js ├── J3DIMath.js ├── binfile.js ├── bsp.js ├── bspdef.js ├── camera.js ├── entity.js ├── events.js ├── jDataView.js ├── jquery-1.7.1.min.js ├── main.js ├── mathlib.js ├── movement.js ├── texutils.js └── wad.js ├── license └── readme /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | font-size: 12px; 4 | text-align: center; 5 | } 6 | 7 | h1 { 8 | text-shadow: 0 2px 2px rgba(0, 0, 0, 0.35); 9 | } 10 | 11 | #app { 12 | margin: auto; 13 | } 14 | 15 | canvas { 16 | display: inline-block; 17 | -webkit-box-shadow: 0 0 10px rgba(0,0,0, 0.8); 18 | -moz-box-shadow: 0 0 10px rgba(0,0,0, 0.8); 19 | box-shadow: 0 0 10px rgba(0,0,0, 0.8); 20 | border: 1px solid white; 21 | cursor: move; 22 | } 23 | 24 | #controlpanel { 25 | display: inline-block; 26 | vertical-align: top; 27 | margin: 0 0 0 20px; 28 | padding: 0; 29 | list-style: none; 30 | text-align: left; 31 | } 32 | 33 | #controlpanel > li { 34 | border: 1px solid black; 35 | margin-bottom: 10px; 36 | padding: 10px; 37 | border-radius: 5px; 38 | 39 | background: rgb(252,255,244); /* Old browsers */ 40 | background: -moz-linear-gradient(top, rgba(252,255,244,1) 0%, rgba(223,229,215,1) 40%, rgba(179,190,173,1) 100%); /* FF3.6+ */ 41 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(252,255,244,1)), color-stop(40%,rgba(223,229,215,1)), color-stop(100%,rgba(179,190,173,1))); /* Chrome,Safari4+ */ 42 | background: -webkit-linear-gradient(top, rgba(252,255,244,1) 0%,rgba(223,229,215,1) 40%,rgba(179,190,173,1) 100%); /* Chrome10+,Safari5.1+ */ 43 | background: -o-linear-gradient(top, rgba(252,255,244,1) 0%,rgba(223,229,215,1) 40%,rgba(179,190,173,1) 100%); /* Opera 11.10+ */ 44 | background: -ms-linear-gradient(top, rgba(252,255,244,1) 0%,rgba(223,229,215,1) 40%,rgba(179,190,173,1) 100%); /* IE10+ */ 45 | background: linear-gradient(top, rgba(252,255,244,1) 0%,rgba(223,229,215,1) 40%,rgba(179,190,173,1) 100%); /* W3C */ 46 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcfff4', endColorstr='#b3bead',GradientType=0 ); /* IE6-9 */ 47 | 48 | text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.35); 49 | } 50 | 51 | h2 { 52 | margin: 0 0 5px 0; 53 | font-size: 20px; 54 | } 55 | 56 | #info p { 57 | display: block; 58 | float: left; 59 | clear: left; 60 | } 61 | 62 | #info img { 63 | float: right; 64 | } 65 | 66 | #log > div { 67 | height: 150px; 68 | overflow-y: scroll; 69 | } 70 | 71 | #wadmissing > ul, #wads > ul { 72 | padding: 10px 20px; 73 | } 74 | 75 | #wadmissing > ul > li > span, #wads > ul > li > span { 76 | margin-right: 15px; 77 | } 78 | 79 | p { 80 | margin: 0; 81 | padding: 2px; 82 | } 83 | 84 | table, th, td { 85 | border: 1px solid black; 86 | } 87 | 88 | table { 89 | border-collapse:collapse; 90 | } 91 | 92 | .normal { 93 | background: #eeeeee; 94 | } 95 | 96 | .success { 97 | background: #2E8B57; 98 | } 99 | 100 | .warning { 101 | background: #F0E68C; 102 | } 103 | 104 | .error { 105 | background: #FF4500; 106 | } 107 | 108 | .clear { 109 | clear: both; 110 | } -------------------------------------------------------------------------------- /data/maps/webgl.bsp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhardmgruber/hlbsp-web/d7459250faa7aaed72f1ac149d12c1e6d5d5c817/data/maps/webgl.bsp -------------------------------------------------------------------------------- /data/wads/custom/webgl.wad: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhardmgruber/hlbsp-web/d7459250faa7aaed72f1ac149d12c1e6d5d5c817/data/wads/custom/webgl.wad -------------------------------------------------------------------------------- /images/webgl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bernhardmgruber/hlbsp-web/d7459250faa7aaed72f1ac149d12c1e6d5d5c817/images/webgl.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HLBSP viewer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |

HLBSP viewer

28 | 29 |
30 | Your browser does not support canvas ;( 31 | 32 | 73 |
74 | 75 | 98 | 155 | 156 | -------------------------------------------------------------------------------- /js/J3DIMath.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Apple Inc. All Rights Reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | // J3DI (Jedi) - A support library for WebGL. 27 | 28 | /* 29 | J3DI Math Classes. Currently includes: 30 | 31 | J3DIMatrix4 - A 4x4 Matrix 32 | */ 33 | 34 | /* 35 | J3DIMatrix4 class 36 | 37 | This class implements a 4x4 matrix. It has functions which duplicate the 38 | functionality of the OpenGL matrix stack and glut functions. On browsers 39 | that support it, CSSMatrix is used to accelerate operations. 40 | 41 | IDL: 42 | 43 | [ 44 | Constructor(in J3DIMatrix4 matrix), // copy passed matrix into new J3DIMatrix4 45 | Constructor(in sequence array) // create new J3DIMatrix4 with 16 floats (row major) 46 | Constructor() // create new J3DIMatrix4 with identity matrix 47 | ] 48 | interface J3DIMatrix4 { 49 | void load(in J3DIMatrix4 matrix); // copy the values from the passed matrix 50 | void load(in sequence array); // copy 16 floats into the matrix 51 | sequence getAsArray(); // return the matrix as an array of 16 floats 52 | Float32Array getAsFloat32Array(); // return the matrix as a Float32Array with 16 values 53 | void setUniform(in WebGLRenderingContext ctx, // Send the matrix to the passed uniform location in the passed context 54 | in WebGLUniformLocation loc, 55 | in boolean transpose); 56 | void makeIdentity(); // replace the matrix with identity 57 | void transpose(); // replace the matrix with its transpose 58 | void invert(); // replace the matrix with its inverse 59 | 60 | void translate(in float x, in float y, in float z); // multiply the matrix by passed translation values on the right 61 | void translate(in J3DVector3 v); // multiply the matrix by passed translation values on the right 62 | void scale(in float x, in float y, in float z); // multiply the matrix by passed scale values on the right 63 | void scale(in J3DVector3 v); // multiply the matrix by passed scale values on the right 64 | void rotate(in float angle, // multiply the matrix by passed rotation values on the right 65 | in float x, in float y, in float z); // (angle is in degrees) 66 | void rotate(in float angle, in J3DVector3 v); // multiply the matrix by passed rotation values on the right 67 | // (angle is in degrees) 68 | void multiply(in CanvasMatrix matrix); // multiply the matrix by the passed matrix on the right 69 | void divide(in float divisor); // divide the matrix by the passed divisor 70 | void ortho(in float left, in float right, // multiply the matrix by the passed ortho values on the right 71 | in float bottom, in float top, 72 | in float near, in float far); 73 | void frustum(in float left, in float right, // multiply the matrix by the passed frustum values on the right 74 | in float bottom, in float top, 75 | in float near, in float far); 76 | void perspective(in float fovy, in float aspect, // multiply the matrix by the passed perspective values on the right 77 | in float zNear, in float zFar); 78 | void lookat(in J3DVector3 eye, // multiply the matrix by the passed lookat 79 | in J3DVector3 center, in J3DVector3 up); // values on the right 80 | bool decompose(in J3DVector3 translate, // decompose the matrix into the passed vector 81 | in J3DVector3 rotate, 82 | in J3DVector3 scale, 83 | in J3DVector3 skew, 84 | in sequence perspective); 85 | } 86 | 87 | [ 88 | Constructor(in J3DVector3 vector), // copy passed vector into new J3DVector3 89 | Constructor(in sequence array) // create new J3DVector3 with 3 floats from array 90 | Constructor(in float x, in float y, in float z) // create new J3DVector3 with 3 floats 91 | Constructor() // create new J3DVector3 with (0,0,0) 92 | ] 93 | interface J3DVector3 { 94 | void load(in J3DVector3 vector); // copy the values from the passed vector 95 | void load(in sequence array); // copy 3 floats into the vector from array 96 | void load(in float x, in float y, in float z); // copy 3 floats into the vector 97 | sequence getAsArray(); // return the vector as an array of 3 floats 98 | Float32Array getAsFloat32Array(); // return the matrix as a Float32Array with 16 values 99 | void multMatrix(in J3DIMatrix4 matrix); // multiply the vector by the passed matrix (on the right) 100 | float vectorLength(); // return the length of the vector 101 | float dot(); // return the dot product of the vector 102 | void cross(in J3DVector3 v); // replace the vector with vector x v 103 | void divide(in float divisor); // divide the vector by the passed divisor 104 | } 105 | */ 106 | 107 | J3DIHasCSSMatrix = false; 108 | J3DIHasCSSMatrixCopy = false; 109 | /* 110 | if ("WebKitCSSMatrix" in window && ("media" in window && window.media.matchMedium("(-webkit-transform-3d)")) || 111 | ("styleMedia" in window && window.styleMedia.matchMedium("(-webkit-transform-3d)"))) { 112 | J3DIHasCSSMatrix = true; 113 | if ("copy" in WebKitCSSMatrix.prototype) 114 | J3DIHasCSSMatrixCopy = true; 115 | } 116 | */ 117 | 118 | // console.log("J3DIHasCSSMatrix="+J3DIHasCSSMatrix); 119 | // console.log("J3DIHasCSSMatrixCopy="+J3DIHasCSSMatrixCopy); 120 | 121 | // 122 | // J3DIMatrix4 123 | // 124 | J3DIMatrix4 = function(m) 125 | { 126 | if (J3DIHasCSSMatrix) 127 | this.$matrix = new WebKitCSSMatrix; 128 | else 129 | this.$matrix = new Object; 130 | 131 | if (typeof m == 'object') { 132 | if ("length" in m && m.length >= 16) { 133 | this.load(m); 134 | return; 135 | } 136 | else if (m instanceof J3DIMatrix4) { 137 | this.load(m); 138 | return; 139 | } 140 | } 141 | this.makeIdentity(); 142 | } 143 | 144 | J3DIMatrix4.prototype.load = function() 145 | { 146 | if (arguments.length == 1 && typeof arguments[0] == 'object') { 147 | var matrix; 148 | 149 | if (arguments[0] instanceof J3DIMatrix4) { 150 | matrix = arguments[0].$matrix; 151 | 152 | this.$matrix.m11 = matrix.m11; 153 | this.$matrix.m12 = matrix.m12; 154 | this.$matrix.m13 = matrix.m13; 155 | this.$matrix.m14 = matrix.m14; 156 | 157 | this.$matrix.m21 = matrix.m21; 158 | this.$matrix.m22 = matrix.m22; 159 | this.$matrix.m23 = matrix.m23; 160 | this.$matrix.m24 = matrix.m24; 161 | 162 | this.$matrix.m31 = matrix.m31; 163 | this.$matrix.m32 = matrix.m32; 164 | this.$matrix.m33 = matrix.m33; 165 | this.$matrix.m34 = matrix.m34; 166 | 167 | this.$matrix.m41 = matrix.m41; 168 | this.$matrix.m42 = matrix.m42; 169 | this.$matrix.m43 = matrix.m43; 170 | this.$matrix.m44 = matrix.m44; 171 | return; 172 | } 173 | else 174 | matrix = arguments[0]; 175 | 176 | if ("length" in matrix && matrix.length >= 16) { 177 | this.$matrix.m11 = matrix[0]; 178 | this.$matrix.m12 = matrix[1]; 179 | this.$matrix.m13 = matrix[2]; 180 | this.$matrix.m14 = matrix[3]; 181 | 182 | this.$matrix.m21 = matrix[4]; 183 | this.$matrix.m22 = matrix[5]; 184 | this.$matrix.m23 = matrix[6]; 185 | this.$matrix.m24 = matrix[7]; 186 | 187 | this.$matrix.m31 = matrix[8]; 188 | this.$matrix.m32 = matrix[9]; 189 | this.$matrix.m33 = matrix[10]; 190 | this.$matrix.m34 = matrix[11]; 191 | 192 | this.$matrix.m41 = matrix[12]; 193 | this.$matrix.m42 = matrix[13]; 194 | this.$matrix.m43 = matrix[14]; 195 | this.$matrix.m44 = matrix[15]; 196 | return; 197 | } 198 | } 199 | 200 | this.makeIdentity(); 201 | } 202 | 203 | J3DIMatrix4.prototype.getAsArray = function() 204 | { 205 | return [ 206 | this.$matrix.m11, this.$matrix.m12, this.$matrix.m13, this.$matrix.m14, 207 | this.$matrix.m21, this.$matrix.m22, this.$matrix.m23, this.$matrix.m24, 208 | this.$matrix.m31, this.$matrix.m32, this.$matrix.m33, this.$matrix.m34, 209 | this.$matrix.m41, this.$matrix.m42, this.$matrix.m43, this.$matrix.m44 210 | ]; 211 | } 212 | 213 | J3DIMatrix4.prototype.getAsFloat32Array = function() 214 | { 215 | if (J3DIHasCSSMatrixCopy) { 216 | var array = new Float32Array(16); 217 | this.$matrix.copy(array); 218 | return array; 219 | } 220 | return new Float32Array(this.getAsArray()); 221 | } 222 | 223 | J3DIMatrix4.prototype.setUniform = function(ctx, loc, transpose) 224 | { 225 | if (J3DIMatrix4.setUniformArray == undefined) { 226 | J3DIMatrix4.setUniformWebGLArray = new Float32Array(16); 227 | J3DIMatrix4.setUniformArray = new Array(16); 228 | } 229 | 230 | if (J3DIHasCSSMatrixCopy) 231 | this.$matrix.copy(J3DIMatrix4.setUniformWebGLArray); 232 | else { 233 | J3DIMatrix4.setUniformArray[0] = this.$matrix.m11; 234 | J3DIMatrix4.setUniformArray[1] = this.$matrix.m12; 235 | J3DIMatrix4.setUniformArray[2] = this.$matrix.m13; 236 | J3DIMatrix4.setUniformArray[3] = this.$matrix.m14; 237 | J3DIMatrix4.setUniformArray[4] = this.$matrix.m21; 238 | J3DIMatrix4.setUniformArray[5] = this.$matrix.m22; 239 | J3DIMatrix4.setUniformArray[6] = this.$matrix.m23; 240 | J3DIMatrix4.setUniformArray[7] = this.$matrix.m24; 241 | J3DIMatrix4.setUniformArray[8] = this.$matrix.m31; 242 | J3DIMatrix4.setUniformArray[9] = this.$matrix.m32; 243 | J3DIMatrix4.setUniformArray[10] = this.$matrix.m33; 244 | J3DIMatrix4.setUniformArray[11] = this.$matrix.m34; 245 | J3DIMatrix4.setUniformArray[12] = this.$matrix.m41; 246 | J3DIMatrix4.setUniformArray[13] = this.$matrix.m42; 247 | J3DIMatrix4.setUniformArray[14] = this.$matrix.m43; 248 | J3DIMatrix4.setUniformArray[15] = this.$matrix.m44; 249 | 250 | J3DIMatrix4.setUniformWebGLArray.set(J3DIMatrix4.setUniformArray); 251 | } 252 | 253 | ctx.uniformMatrix4fv(loc, transpose, J3DIMatrix4.setUniformWebGLArray); 254 | } 255 | 256 | J3DIMatrix4.prototype.makeIdentity = function() 257 | { 258 | this.$matrix.m11 = 1; 259 | this.$matrix.m12 = 0; 260 | this.$matrix.m13 = 0; 261 | this.$matrix.m14 = 0; 262 | 263 | this.$matrix.m21 = 0; 264 | this.$matrix.m22 = 1; 265 | this.$matrix.m23 = 0; 266 | this.$matrix.m24 = 0; 267 | 268 | this.$matrix.m31 = 0; 269 | this.$matrix.m32 = 0; 270 | this.$matrix.m33 = 1; 271 | this.$matrix.m34 = 0; 272 | 273 | this.$matrix.m41 = 0; 274 | this.$matrix.m42 = 0; 275 | this.$matrix.m43 = 0; 276 | this.$matrix.m44 = 1; 277 | } 278 | 279 | J3DIMatrix4.prototype.transpose = function() 280 | { 281 | var tmp = this.$matrix.m12; 282 | this.$matrix.m12 = this.$matrix.m21; 283 | this.$matrix.m21 = tmp; 284 | 285 | tmp = this.$matrix.m13; 286 | this.$matrix.m13 = this.$matrix.m31; 287 | this.$matrix.m31 = tmp; 288 | 289 | tmp = this.$matrix.m14; 290 | this.$matrix.m14 = this.$matrix.m41; 291 | this.$matrix.m41 = tmp; 292 | 293 | tmp = this.$matrix.m23; 294 | this.$matrix.m23 = this.$matrix.m32; 295 | this.$matrix.m32 = tmp; 296 | 297 | tmp = this.$matrix.m24; 298 | this.$matrix.m24 = this.$matrix.m42; 299 | this.$matrix.m42 = tmp; 300 | 301 | tmp = this.$matrix.m34; 302 | this.$matrix.m34 = this.$matrix.m43; 303 | this.$matrix.m43 = tmp; 304 | } 305 | 306 | J3DIMatrix4.prototype.invert = function() 307 | { 308 | if (J3DIHasCSSMatrix) { 309 | this.$matrix = this.$matrix.inverse(); 310 | return; 311 | } 312 | 313 | // Calculate the 4x4 determinant 314 | // If the determinant is zero, 315 | // then the inverse matrix is not unique. 316 | var det = this._determinant4x4(); 317 | 318 | if (Math.abs(det) < 1e-8) 319 | return null; 320 | 321 | this._makeAdjoint(); 322 | 323 | // Scale the adjoint matrix to get the inverse 324 | this.$matrix.m11 /= det; 325 | this.$matrix.m12 /= det; 326 | this.$matrix.m13 /= det; 327 | this.$matrix.m14 /= det; 328 | 329 | this.$matrix.m21 /= det; 330 | this.$matrix.m22 /= det; 331 | this.$matrix.m23 /= det; 332 | this.$matrix.m24 /= det; 333 | 334 | this.$matrix.m31 /= det; 335 | this.$matrix.m32 /= det; 336 | this.$matrix.m33 /= det; 337 | this.$matrix.m34 /= det; 338 | 339 | this.$matrix.m41 /= det; 340 | this.$matrix.m42 /= det; 341 | this.$matrix.m43 /= det; 342 | this.$matrix.m44 /= det; 343 | } 344 | 345 | J3DIMatrix4.prototype.translate = function(x,y,z) 346 | { 347 | if (typeof x == 'object' && "length" in x) { 348 | var t = x; 349 | x = t[0]; 350 | y = t[1]; 351 | z = t[2]; 352 | } 353 | else { 354 | if (x == undefined) 355 | x = 0; 356 | if (y == undefined) 357 | y = 0; 358 | if (z == undefined) 359 | z = 0; 360 | } 361 | 362 | if (J3DIHasCSSMatrix) { 363 | this.$matrix = this.$matrix.translate(x, y, z); 364 | return; 365 | } 366 | 367 | var matrix = new J3DIMatrix4(); 368 | matrix.$matrix.m41 = x; 369 | matrix.$matrix.m42 = y; 370 | matrix.$matrix.m43 = z; 371 | 372 | this.multiply(matrix); 373 | } 374 | 375 | J3DIMatrix4.prototype.scale = function(x,y,z) 376 | { 377 | if (typeof x == 'object' && "length" in x) { 378 | var t = x; 379 | x = t[0]; 380 | y = t[1]; 381 | z = t[2]; 382 | } 383 | else { 384 | if (x == undefined) 385 | x = 1; 386 | if (z == undefined) { 387 | if (y == undefined) { 388 | y = x; 389 | z = x; 390 | } 391 | else 392 | z = 1; 393 | } 394 | else if (y == undefined) 395 | y = x; 396 | } 397 | 398 | if (J3DIHasCSSMatrix) { 399 | this.$matrix = this.$matrix.scale(x, y, z); 400 | return; 401 | } 402 | 403 | var matrix = new J3DIMatrix4(); 404 | matrix.$matrix.m11 = x; 405 | matrix.$matrix.m22 = y; 406 | matrix.$matrix.m33 = z; 407 | 408 | this.multiply(matrix); 409 | } 410 | 411 | J3DIMatrix4.prototype.rotate = function(angle,x,y,z) 412 | { 413 | // Forms are (angle, x,y,z), (angle,vector), (angleX, angleY, angleZ), (angle) 414 | if (typeof x == 'object' && "length" in x) { 415 | var t = x; 416 | x = t[0]; 417 | y = t[1]; 418 | z = t[2]; 419 | } 420 | else { 421 | if (arguments.length == 1) { 422 | x = 0; 423 | y = 0; 424 | z = 1; 425 | } 426 | else if (arguments.length == 3) { 427 | this.rotate(angle, 1,0,0); // about X axis 428 | this.rotate(x, 0,1,0); // about Y axis 429 | this.rotate(y, 0,0,1); // about Z axis 430 | return; 431 | } 432 | } 433 | 434 | if (J3DIHasCSSMatrix) { 435 | this.$matrix = this.$matrix.rotateAxisAngle(x, y, z, angle); 436 | return; 437 | } 438 | 439 | // angles are in degrees. Switch to radians 440 | angle = angle / 180 * Math.PI; 441 | 442 | angle /= 2; 443 | var sinA = Math.sin(angle); 444 | var cosA = Math.cos(angle); 445 | var sinA2 = sinA * sinA; 446 | 447 | // normalize 448 | var len = Math.sqrt(x * x + y * y + z * z); 449 | if (len == 0) { 450 | // bad vector, just use something reasonable 451 | x = 0; 452 | y = 0; 453 | z = 1; 454 | } else if (len != 1) { 455 | x /= len; 456 | y /= len; 457 | z /= len; 458 | } 459 | 460 | var mat = new J3DIMatrix4(); 461 | 462 | // optimize case where axis is along major axis 463 | if (x == 1 && y == 0 && z == 0) { 464 | mat.$matrix.m11 = 1; 465 | mat.$matrix.m12 = 0; 466 | mat.$matrix.m13 = 0; 467 | mat.$matrix.m21 = 0; 468 | mat.$matrix.m22 = 1 - 2 * sinA2; 469 | mat.$matrix.m23 = 2 * sinA * cosA; 470 | mat.$matrix.m31 = 0; 471 | mat.$matrix.m32 = -2 * sinA * cosA; 472 | mat.$matrix.m33 = 1 - 2 * sinA2; 473 | mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0; 474 | mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0; 475 | mat.$matrix.m44 = 1; 476 | } else if (x == 0 && y == 1 && z == 0) { 477 | mat.$matrix.m11 = 1 - 2 * sinA2; 478 | mat.$matrix.m12 = 0; 479 | mat.$matrix.m13 = -2 * sinA * cosA; 480 | mat.$matrix.m21 = 0; 481 | mat.$matrix.m22 = 1; 482 | mat.$matrix.m23 = 0; 483 | mat.$matrix.m31 = 2 * sinA * cosA; 484 | mat.$matrix.m32 = 0; 485 | mat.$matrix.m33 = 1 - 2 * sinA2; 486 | mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0; 487 | mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0; 488 | mat.$matrix.m44 = 1; 489 | } else if (x == 0 && y == 0 && z == 1) { 490 | mat.$matrix.m11 = 1 - 2 * sinA2; 491 | mat.$matrix.m12 = 2 * sinA * cosA; 492 | mat.$matrix.m13 = 0; 493 | mat.$matrix.m21 = -2 * sinA * cosA; 494 | mat.$matrix.m22 = 1 - 2 * sinA2; 495 | mat.$matrix.m23 = 0; 496 | mat.$matrix.m31 = 0; 497 | mat.$matrix.m32 = 0; 498 | mat.$matrix.m33 = 1; 499 | mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0; 500 | mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0; 501 | mat.$matrix.m44 = 1; 502 | } else { 503 | var x2 = x*x; 504 | var y2 = y*y; 505 | var z2 = z*z; 506 | 507 | mat.$matrix.m11 = 1 - 2 * (y2 + z2) * sinA2; 508 | mat.$matrix.m12 = 2 * (x * y * sinA2 + z * sinA * cosA); 509 | mat.$matrix.m13 = 2 * (x * z * sinA2 - y * sinA * cosA); 510 | mat.$matrix.m21 = 2 * (y * x * sinA2 - z * sinA * cosA); 511 | mat.$matrix.m22 = 1 - 2 * (z2 + x2) * sinA2; 512 | mat.$matrix.m23 = 2 * (y * z * sinA2 + x * sinA * cosA); 513 | mat.$matrix.m31 = 2 * (z * x * sinA2 + y * sinA * cosA); 514 | mat.$matrix.m32 = 2 * (z * y * sinA2 - x * sinA * cosA); 515 | mat.$matrix.m33 = 1 - 2 * (x2 + y2) * sinA2; 516 | mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0; 517 | mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0; 518 | mat.$matrix.m44 = 1; 519 | } 520 | this.multiply(mat); 521 | } 522 | 523 | J3DIMatrix4.prototype.multiply = function(mat) 524 | { 525 | if (J3DIHasCSSMatrix) { 526 | this.$matrix = this.$matrix.multiply(mat.$matrix); 527 | return; 528 | } 529 | 530 | var m11 = (mat.$matrix.m11 * this.$matrix.m11 + mat.$matrix.m12 * this.$matrix.m21 531 | + mat.$matrix.m13 * this.$matrix.m31 + mat.$matrix.m14 * this.$matrix.m41); 532 | var m12 = (mat.$matrix.m11 * this.$matrix.m12 + mat.$matrix.m12 * this.$matrix.m22 533 | + mat.$matrix.m13 * this.$matrix.m32 + mat.$matrix.m14 * this.$matrix.m42); 534 | var m13 = (mat.$matrix.m11 * this.$matrix.m13 + mat.$matrix.m12 * this.$matrix.m23 535 | + mat.$matrix.m13 * this.$matrix.m33 + mat.$matrix.m14 * this.$matrix.m43); 536 | var m14 = (mat.$matrix.m11 * this.$matrix.m14 + mat.$matrix.m12 * this.$matrix.m24 537 | + mat.$matrix.m13 * this.$matrix.m34 + mat.$matrix.m14 * this.$matrix.m44); 538 | 539 | var m21 = (mat.$matrix.m21 * this.$matrix.m11 + mat.$matrix.m22 * this.$matrix.m21 540 | + mat.$matrix.m23 * this.$matrix.m31 + mat.$matrix.m24 * this.$matrix.m41); 541 | var m22 = (mat.$matrix.m21 * this.$matrix.m12 + mat.$matrix.m22 * this.$matrix.m22 542 | + mat.$matrix.m23 * this.$matrix.m32 + mat.$matrix.m24 * this.$matrix.m42); 543 | var m23 = (mat.$matrix.m21 * this.$matrix.m13 + mat.$matrix.m22 * this.$matrix.m23 544 | + mat.$matrix.m23 * this.$matrix.m33 + mat.$matrix.m24 * this.$matrix.m43); 545 | var m24 = (mat.$matrix.m21 * this.$matrix.m14 + mat.$matrix.m22 * this.$matrix.m24 546 | + mat.$matrix.m23 * this.$matrix.m34 + mat.$matrix.m24 * this.$matrix.m44); 547 | 548 | var m31 = (mat.$matrix.m31 * this.$matrix.m11 + mat.$matrix.m32 * this.$matrix.m21 549 | + mat.$matrix.m33 * this.$matrix.m31 + mat.$matrix.m34 * this.$matrix.m41); 550 | var m32 = (mat.$matrix.m31 * this.$matrix.m12 + mat.$matrix.m32 * this.$matrix.m22 551 | + mat.$matrix.m33 * this.$matrix.m32 + mat.$matrix.m34 * this.$matrix.m42); 552 | var m33 = (mat.$matrix.m31 * this.$matrix.m13 + mat.$matrix.m32 * this.$matrix.m23 553 | + mat.$matrix.m33 * this.$matrix.m33 + mat.$matrix.m34 * this.$matrix.m43); 554 | var m34 = (mat.$matrix.m31 * this.$matrix.m14 + mat.$matrix.m32 * this.$matrix.m24 555 | + mat.$matrix.m33 * this.$matrix.m34 + mat.$matrix.m34 * this.$matrix.m44); 556 | 557 | var m41 = (mat.$matrix.m41 * this.$matrix.m11 + mat.$matrix.m42 * this.$matrix.m21 558 | + mat.$matrix.m43 * this.$matrix.m31 + mat.$matrix.m44 * this.$matrix.m41); 559 | var m42 = (mat.$matrix.m41 * this.$matrix.m12 + mat.$matrix.m42 * this.$matrix.m22 560 | + mat.$matrix.m43 * this.$matrix.m32 + mat.$matrix.m44 * this.$matrix.m42); 561 | var m43 = (mat.$matrix.m41 * this.$matrix.m13 + mat.$matrix.m42 * this.$matrix.m23 562 | + mat.$matrix.m43 * this.$matrix.m33 + mat.$matrix.m44 * this.$matrix.m43); 563 | var m44 = (mat.$matrix.m41 * this.$matrix.m14 + mat.$matrix.m42 * this.$matrix.m24 564 | + mat.$matrix.m43 * this.$matrix.m34 + mat.$matrix.m44 * this.$matrix.m44); 565 | 566 | this.$matrix.m11 = m11; 567 | this.$matrix.m12 = m12; 568 | this.$matrix.m13 = m13; 569 | this.$matrix.m14 = m14; 570 | 571 | this.$matrix.m21 = m21; 572 | this.$matrix.m22 = m22; 573 | this.$matrix.m23 = m23; 574 | this.$matrix.m24 = m24; 575 | 576 | this.$matrix.m31 = m31; 577 | this.$matrix.m32 = m32; 578 | this.$matrix.m33 = m33; 579 | this.$matrix.m34 = m34; 580 | 581 | this.$matrix.m41 = m41; 582 | this.$matrix.m42 = m42; 583 | this.$matrix.m43 = m43; 584 | this.$matrix.m44 = m44; 585 | } 586 | 587 | J3DIMatrix4.prototype.divide = function(divisor) 588 | { 589 | this.$matrix.m11 /= divisor; 590 | this.$matrix.m12 /= divisor; 591 | this.$matrix.m13 /= divisor; 592 | this.$matrix.m14 /= divisor; 593 | 594 | this.$matrix.m21 /= divisor; 595 | this.$matrix.m22 /= divisor; 596 | this.$matrix.m23 /= divisor; 597 | this.$matrix.m24 /= divisor; 598 | 599 | this.$matrix.m31 /= divisor; 600 | this.$matrix.m32 /= divisor; 601 | this.$matrix.m33 /= divisor; 602 | this.$matrix.m34 /= divisor; 603 | 604 | this.$matrix.m41 /= divisor; 605 | this.$matrix.m42 /= divisor; 606 | this.$matrix.m43 /= divisor; 607 | this.$matrix.m44 /= divisor; 608 | 609 | } 610 | 611 | J3DIMatrix4.prototype.ortho = function(left, right, bottom, top, near, far) 612 | { 613 | var tx = (left + right) / (left - right); 614 | var ty = (top + bottom) / (top - bottom); 615 | var tz = (far + near) / (far - near); 616 | 617 | var matrix = new J3DIMatrix4(); 618 | matrix.$matrix.m11 = 2 / (left - right); 619 | matrix.$matrix.m12 = 0; 620 | matrix.$matrix.m13 = 0; 621 | matrix.$matrix.m14 = 0; 622 | matrix.$matrix.m21 = 0; 623 | matrix.$matrix.m22 = 2 / (top - bottom); 624 | matrix.$matrix.m23 = 0; 625 | matrix.$matrix.m24 = 0; 626 | matrix.$matrix.m31 = 0; 627 | matrix.$matrix.m32 = 0; 628 | matrix.$matrix.m33 = -2 / (far - near); 629 | matrix.$matrix.m34 = 0; 630 | matrix.$matrix.m41 = tx; 631 | matrix.$matrix.m42 = ty; 632 | matrix.$matrix.m43 = tz; 633 | matrix.$matrix.m44 = 1; 634 | 635 | this.multiply(matrix); 636 | } 637 | 638 | J3DIMatrix4.prototype.frustum = function(left, right, bottom, top, near, far) 639 | { 640 | var matrix = new J3DIMatrix4(); 641 | var A = (right + left) / (right - left); 642 | var B = (top + bottom) / (top - bottom); 643 | var C = -(far + near) / (far - near); 644 | var D = -(2 * far * near) / (far - near); 645 | 646 | matrix.$matrix.m11 = (2 * near) / (right - left); 647 | matrix.$matrix.m12 = 0; 648 | matrix.$matrix.m13 = 0; 649 | matrix.$matrix.m14 = 0; 650 | 651 | matrix.$matrix.m21 = 0; 652 | matrix.$matrix.m22 = 2 * near / (top - bottom); 653 | matrix.$matrix.m23 = 0; 654 | matrix.$matrix.m24 = 0; 655 | 656 | matrix.$matrix.m31 = A; 657 | matrix.$matrix.m32 = B; 658 | matrix.$matrix.m33 = C; 659 | matrix.$matrix.m34 = -1; 660 | 661 | matrix.$matrix.m41 = 0; 662 | matrix.$matrix.m42 = 0; 663 | matrix.$matrix.m43 = D; 664 | matrix.$matrix.m44 = 0; 665 | 666 | this.multiply(matrix); 667 | } 668 | 669 | J3DIMatrix4.prototype.perspective = function(fovy, aspect, zNear, zFar) 670 | { 671 | var top = Math.tan(fovy * Math.PI / 360) * zNear; 672 | var bottom = -top; 673 | var left = aspect * bottom; 674 | var right = aspect * top; 675 | this.frustum(left, right, bottom, top, zNear, zFar); 676 | } 677 | 678 | J3DIMatrix4.prototype.lookat = function(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz) 679 | { 680 | if (typeof eyez == 'object' && "length" in eyez) { 681 | var t = eyez; 682 | upx = t[0]; 683 | upy = t[1]; 684 | upz = t[2]; 685 | 686 | t = eyey; 687 | centerx = t[0]; 688 | centery = t[1]; 689 | centerz = t[2]; 690 | 691 | t = eyex; 692 | eyex = t[0]; 693 | eyey = t[1]; 694 | eyez = t[2]; 695 | } 696 | 697 | var matrix = new J3DIMatrix4(); 698 | 699 | // Make rotation matrix 700 | 701 | // Z vector 702 | var zx = eyex - centerx; 703 | var zy = eyey - centery; 704 | var zz = eyez - centerz; 705 | var mag = Math.sqrt(zx * zx + zy * zy + zz * zz); 706 | if (mag) { 707 | zx /= mag; 708 | zy /= mag; 709 | zz /= mag; 710 | } 711 | 712 | // Y vector 713 | var yx = upx; 714 | var yy = upy; 715 | var yz = upz; 716 | 717 | // X vector = Y cross Z 718 | xx = yy * zz - yz * zy; 719 | xy = -yx * zz + yz * zx; 720 | xz = yx * zy - yy * zx; 721 | 722 | // Recompute Y = Z cross X 723 | yx = zy * xz - zz * xy; 724 | yy = -zx * xz + zz * xx; 725 | yx = zx * xy - zy * xx; 726 | 727 | // cross product gives area of parallelogram, which is < 1.0 for 728 | // non-perpendicular unit-length vectors; so normalize x, y here 729 | 730 | mag = Math.sqrt(xx * xx + xy * xy + xz * xz); 731 | if (mag) { 732 | xx /= mag; 733 | xy /= mag; 734 | xz /= mag; 735 | } 736 | 737 | mag = Math.sqrt(yx * yx + yy * yy + yz * yz); 738 | if (mag) { 739 | yx /= mag; 740 | yy /= mag; 741 | yz /= mag; 742 | } 743 | 744 | matrix.$matrix.m11 = xx; 745 | matrix.$matrix.m12 = xy; 746 | matrix.$matrix.m13 = xz; 747 | matrix.$matrix.m14 = 0; 748 | 749 | matrix.$matrix.m21 = yx; 750 | matrix.$matrix.m22 = yy; 751 | matrix.$matrix.m23 = yz; 752 | matrix.$matrix.m24 = 0; 753 | 754 | matrix.$matrix.m31 = zx; 755 | matrix.$matrix.m32 = zy; 756 | matrix.$matrix.m33 = zz; 757 | matrix.$matrix.m34 = 0; 758 | 759 | matrix.$matrix.m41 = 0; 760 | matrix.$matrix.m42 = 0; 761 | matrix.$matrix.m43 = 0; 762 | matrix.$matrix.m44 = 1; 763 | matrix.translate(-eyex, -eyey, -eyez); 764 | 765 | this.multiply(matrix); 766 | } 767 | 768 | // Returns true on success, false otherwise. All params are Array objects 769 | J3DIMatrix4.prototype.decompose = function(_translate, _rotate, _scale, _skew, _perspective) 770 | { 771 | // Normalize the matrix. 772 | if (this.$matrix.m44 == 0) 773 | return false; 774 | 775 | // Gather the params 776 | var translate, rotate, scale, skew, perspective; 777 | 778 | var translate = (_translate == undefined || !("length" in _translate)) ? new J3DIVector3 : _translate; 779 | var rotate = (_rotate == undefined || !("length" in _rotate)) ? new J3DIVector3 : _rotate; 780 | var scale = (_scale == undefined || !("length" in _scale)) ? new J3DIVector3 : _scale; 781 | var skew = (_skew == undefined || !("length" in _skew)) ? new J3DIVector3 : _skew; 782 | var perspective = (_perspective == undefined || !("length" in _perspective)) ? new Array(4) : _perspective; 783 | 784 | var matrix = new J3DIMatrix4(this); 785 | 786 | matrix.divide(matrix.$matrix.m44); 787 | 788 | // perspectiveMatrix is used to solve for perspective, but it also provides 789 | // an easy way to test for singularity of the upper 3x3 component. 790 | var perspectiveMatrix = new J3DIMatrix4(matrix); 791 | 792 | perspectiveMatrix.$matrix.m14 = 0; 793 | perspectiveMatrix.$matrix.m24 = 0; 794 | perspectiveMatrix.$matrix.m34 = 0; 795 | perspectiveMatrix.$matrix.m44 = 1; 796 | 797 | if (perspectiveMatrix._determinant4x4() == 0) 798 | return false; 799 | 800 | // First, isolate perspective. 801 | if (matrix.$matrix.m14 != 0 || matrix.$matrix.m24 != 0 || matrix.$matrix.m34 != 0) { 802 | // rightHandSide is the right hand side of the equation. 803 | var rightHandSide = [ matrix.$matrix.m14, matrix.$matrix.m24, matrix.$matrix.m34, matrix.$matrix.m44 ]; 804 | 805 | // Solve the equation by inverting perspectiveMatrix and multiplying 806 | // rightHandSide by the inverse. 807 | var inversePerspectiveMatrix = new J3DIMatrix4(perspectiveMatrix); 808 | inversePerspectiveMatrix.invert(); 809 | var transposedInversePerspectiveMatrix = new J3DIMatrix4(inversePerspectiveMatrix); 810 | transposedInversePerspectiveMatrix.transpose(); 811 | transposedInversePerspectiveMatrix.multVecMatrix(perspective, rightHandSide); 812 | 813 | // Clear the perspective partition 814 | matrix.$matrix.m14 = matrix.$matrix.m24 = matrix.$matrix.m34 = 0 815 | matrix.$matrix.m44 = 1; 816 | } 817 | else { 818 | // No perspective. 819 | perspective[0] = perspective[1] = perspective[2] = 0; 820 | perspective[3] = 1; 821 | } 822 | 823 | // Next take care of translation 824 | translate[0] = matrix.$matrix.m41 825 | matrix.$matrix.m41 = 0 826 | translate[1] = matrix.$matrix.m42 827 | matrix.$matrix.m42 = 0 828 | translate[2] = matrix.$matrix.m43 829 | matrix.$matrix.m43 = 0 830 | 831 | // Now get scale and shear. 'row' is a 3 element array of 3 component vectors 832 | var row0 = new J3DIVector3(matrix.$matrix.m11, matrix.$matrix.m12, matrix.$matrix.m13); 833 | var row1 = new J3DIVector3(matrix.$matrix.m21, matrix.$matrix.m22, matrix.$matrix.m23); 834 | var row2 = new J3DIVector3(matrix.$matrix.m31, matrix.$matrix.m32, matrix.$matrix.m33); 835 | 836 | // Compute X scale factor and normalize first row. 837 | scale[0] = row0.vectorLength(); 838 | row0.divide(scale[0]); 839 | 840 | // Compute XY shear factor and make 2nd row orthogonal to 1st. 841 | skew[0] = row0.dot(row1); 842 | row1.combine(row0, 1.0, -skew[0]); 843 | 844 | // Now, compute Y scale and normalize 2nd row. 845 | scale[1] = row1.vectorLength(); 846 | row1.divide(scale[1]); 847 | skew[0] /= scale[1]; 848 | 849 | // Compute XZ and YZ shears, orthogonalize 3rd row 850 | skew[1] = row1.dot(row2); 851 | row2.combine(row0, 1.0, -skew[1]); 852 | skew[2] = row1.dot(row2); 853 | row2.combine(row1, 1.0, -skew[2]); 854 | 855 | // Next, get Z scale and normalize 3rd row. 856 | scale[2] = row2.vectorLength(); 857 | row2.divide(scale[2]); 858 | skew[1] /= scale[2]; 859 | skew[2] /= scale[2]; 860 | 861 | // At this point, the matrix (in rows) is orthonormal. 862 | // Check for a coordinate system flip. If the determinant 863 | // is -1, then negate the matrix and the scaling factors. 864 | var pdum3 = new J3DIVector3(row1); 865 | pdum3.cross(row2); 866 | if (row0.dot(pdum3) < 0) { 867 | for (i = 0; i < 3; i++) { 868 | scale[i] *= -1; 869 | row[0][i] *= -1; 870 | row[1][i] *= -1; 871 | row[2][i] *= -1; 872 | } 873 | } 874 | 875 | // Now, get the rotations out 876 | rotate[1] = Math.asin(-row0[2]); 877 | if (Math.cos(rotate[1]) != 0) { 878 | rotate[0] = Math.atan2(row1[2], row2[2]); 879 | rotate[2] = Math.atan2(row0[1], row0[0]); 880 | } 881 | else { 882 | rotate[0] = Math.atan2(-row2[0], row1[1]); 883 | rotate[2] = 0; 884 | } 885 | 886 | // Convert rotations to degrees 887 | var rad2deg = 180 / Math.PI; 888 | rotate[0] *= rad2deg; 889 | rotate[1] *= rad2deg; 890 | rotate[2] *= rad2deg; 891 | 892 | return true; 893 | } 894 | 895 | J3DIMatrix4.prototype._determinant2x2 = function(a, b, c, d) 896 | { 897 | return a * d - b * c; 898 | } 899 | 900 | J3DIMatrix4.prototype._determinant3x3 = function(a1, a2, a3, b1, b2, b3, c1, c2, c3) 901 | { 902 | return a1 * this._determinant2x2(b2, b3, c2, c3) 903 | - b1 * this._determinant2x2(a2, a3, c2, c3) 904 | + c1 * this._determinant2x2(a2, a3, b2, b3); 905 | } 906 | 907 | J3DIMatrix4.prototype._determinant4x4 = function() 908 | { 909 | var a1 = this.$matrix.m11; 910 | var b1 = this.$matrix.m12; 911 | var c1 = this.$matrix.m13; 912 | var d1 = this.$matrix.m14; 913 | 914 | var a2 = this.$matrix.m21; 915 | var b2 = this.$matrix.m22; 916 | var c2 = this.$matrix.m23; 917 | var d2 = this.$matrix.m24; 918 | 919 | var a3 = this.$matrix.m31; 920 | var b3 = this.$matrix.m32; 921 | var c3 = this.$matrix.m33; 922 | var d3 = this.$matrix.m34; 923 | 924 | var a4 = this.$matrix.m41; 925 | var b4 = this.$matrix.m42; 926 | var c4 = this.$matrix.m43; 927 | var d4 = this.$matrix.m44; 928 | 929 | return a1 * this._determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4) 930 | - b1 * this._determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4) 931 | + c1 * this._determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4) 932 | - d1 * this._determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); 933 | } 934 | 935 | J3DIMatrix4.prototype._makeAdjoint = function() 936 | { 937 | var a1 = this.$matrix.m11; 938 | var b1 = this.$matrix.m12; 939 | var c1 = this.$matrix.m13; 940 | var d1 = this.$matrix.m14; 941 | 942 | var a2 = this.$matrix.m21; 943 | var b2 = this.$matrix.m22; 944 | var c2 = this.$matrix.m23; 945 | var d2 = this.$matrix.m24; 946 | 947 | var a3 = this.$matrix.m31; 948 | var b3 = this.$matrix.m32; 949 | var c3 = this.$matrix.m33; 950 | var d3 = this.$matrix.m34; 951 | 952 | var a4 = this.$matrix.m41; 953 | var b4 = this.$matrix.m42; 954 | var c4 = this.$matrix.m43; 955 | var d4 = this.$matrix.m44; 956 | 957 | // Row column labeling reversed since we transpose rows & columns 958 | this.$matrix.m11 = this._determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4); 959 | this.$matrix.m21 = - this._determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4); 960 | this.$matrix.m31 = this._determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4); 961 | this.$matrix.m41 = - this._determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); 962 | 963 | this.$matrix.m12 = - this._determinant3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4); 964 | this.$matrix.m22 = this._determinant3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4); 965 | this.$matrix.m32 = - this._determinant3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4); 966 | this.$matrix.m42 = this._determinant3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4); 967 | 968 | this.$matrix.m13 = this._determinant3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4); 969 | this.$matrix.m23 = - this._determinant3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4); 970 | this.$matrix.m33 = this._determinant3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4); 971 | this.$matrix.m43 = - this._determinant3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4); 972 | 973 | this.$matrix.m14 = - this._determinant3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3); 974 | this.$matrix.m24 = this._determinant3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3); 975 | this.$matrix.m34 = - this._determinant3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3); 976 | this.$matrix.m44 = this._determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3); 977 | } 978 | 979 | // 980 | // J3DIVector3 981 | // 982 | J3DIVector3 = function(x,y,z) 983 | { 984 | this.load(x,y,z); 985 | } 986 | 987 | J3DIVector3.prototype.load = function(x,y,z) 988 | { 989 | if (typeof x == 'object' && "length" in x) { 990 | this[0] = x[0]; 991 | this[1] = x[1]; 992 | this[2] = x[2]; 993 | } 994 | else if (typeof x == 'number') { 995 | this[0] = x; 996 | this[1] = y; 997 | this[2] = z; 998 | } 999 | else { 1000 | this[0] = 0; 1001 | this[1] = 0; 1002 | this[2] = 0; 1003 | } 1004 | } 1005 | 1006 | J3DIVector3.prototype.getAsArray = function() 1007 | { 1008 | return [ this[0], this[1], this[2] ]; 1009 | } 1010 | 1011 | J3DIVector3.prototype.getAsFloat32Array = function() 1012 | { 1013 | return new Float32Array(this.getAsArray()); 1014 | } 1015 | 1016 | J3DIVector3.prototype.vectorLength = function() 1017 | { 1018 | return Math.sqrt(this[0] * this[0] + this[1] * this[1] + this[2] * this[2]); 1019 | } 1020 | 1021 | J3DIVector3.prototype.divide = function(divisor) 1022 | { 1023 | this[0] /= divisor; this[1] /= divisor; this[2] /= divisor; 1024 | } 1025 | 1026 | J3DIVector3.prototype.cross = function(v) 1027 | { 1028 | this[0] = this[1] * v[2] - this[2] * v[1]; 1029 | this[1] = -this[0] * v[2] + this[2] * v[0]; 1030 | this[2] = this[0] * v[1] - this[1] * v[0]; 1031 | } 1032 | 1033 | J3DIVector3.prototype.dot = function(v) 1034 | { 1035 | return this[0] * v[0] + this[1] * v[1] + this[2] * v[2]; 1036 | } 1037 | 1038 | J3DIVector3.prototype.combine = function(v, ascl, bscl) 1039 | { 1040 | this[0] = (ascl * this[0]) + (bscl * v[0]); 1041 | this[1] = (ascl * this[1]) + (bscl * v[1]); 1042 | this[2] = (ascl * this[2]) + (bscl * v[2]); 1043 | } 1044 | 1045 | J3DIVector3.prototype.multVecMatrix = function(matrix) 1046 | { 1047 | var x = this[0]; 1048 | var y = this[1]; 1049 | var z = this[2]; 1050 | 1051 | this[0] = matrix.$matrix.m41 + x * matrix.$matrix.m11 + y * matrix.$matrix.m21 + z * matrix.$matrix.m31; 1052 | this[1] = matrix.$matrix.m42 + x * matrix.$matrix.m12 + y * matrix.$matrix.m22 + z * matrix.$matrix.m32; 1053 | this[2] = matrix.$matrix.m43 + x * matrix.$matrix.m13 + y * matrix.$matrix.m23 + z * matrix.$matrix.m33; 1054 | var w = matrix.$matrix.m44 + x * matrix.$matrix.m14 + y * matrix.$matrix.m24 + z * matrix.$matrix.m34; 1055 | if (w != 1 && w != 0) { 1056 | this[0] /= w; 1057 | this[1] /= w; 1058 | this[2] /= w; 1059 | } 1060 | } 1061 | 1062 | J3DIVector3.prototype.toString = function() 1063 | { 1064 | return "["+this[0]+","+this[1]+","+this[2]+"]"; 1065 | } 1066 | -------------------------------------------------------------------------------- /js/binfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * binfile.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | /** 25 | * Class for reading c-style datatypes from a raw ArrayBuffer obtained by reading a binary file. 26 | * The following code has been inspirated by Brandon Jones's High performance Binary Reader 27 | * http://media.tojicode.com/q3bsp/js/common/binFile_man.js 28 | * but has been rewritten using the DataView class. 29 | */ 30 | function BinaryFile(arrayBuffer) 31 | { 32 | this.buffer = arrayBuffer; 33 | this.view = new jDataView(this.buffer); // we use the jDataView wrapper class here to provide backward compability 34 | this.offset = 0; 35 | }; 36 | 37 | BinaryFile.prototype.seek = function(offest) 38 | { 39 | this.offset = offest; 40 | }; 41 | 42 | BinaryFile.prototype.readByte = function() 43 | { 44 | var b = this.view.getInt8(this.offset); 45 | this.offset += 1; 46 | return b; 47 | }; 48 | 49 | BinaryFile.prototype.readUByte = function() 50 | { 51 | var b = this.view.getUint8(this.offset); 52 | this.offset += 1; 53 | return b; 54 | }; 55 | 56 | BinaryFile.prototype.readShort = function() 57 | { 58 | var s = this.view.getInt16(this.offset, true); 59 | this.offset += 2; 60 | return s; 61 | }; 62 | 63 | BinaryFile.prototype.readUShort = function() 64 | { 65 | var s = this.view.getUint16(this.offset, true); 66 | this.offset += 2; 67 | return s; 68 | }; 69 | 70 | BinaryFile.prototype.readLong = function() 71 | { 72 | var l = this.view.getInt32(this.offset, true); 73 | this.offset += 4; 74 | return l; 75 | }; 76 | 77 | BinaryFile.prototype.readULong = function() 78 | { 79 | var l = this.view.getUint32(this.offset, true); 80 | this.offset += 4; 81 | return l; 82 | }; 83 | 84 | BinaryFile.prototype.readFloat = function() 85 | { 86 | var f = this.view.getFloat32(this.offset, true); 87 | this.offset += 4; 88 | return f; 89 | }; 90 | 91 | BinaryFile.prototype.readString = function(length) 92 | { 93 | var offset = this.offset; // keep an independent copy 94 | var str = ''; 95 | for(var i = 0; i < length; i++) 96 | { 97 | var ascii = this.view.getUint8(offset); 98 | if(ascii == 0) 99 | break; 100 | str += String.fromCharCode(ascii); 101 | offset += 1; 102 | } 103 | 104 | this.offset += length; 105 | 106 | return str; 107 | }; -------------------------------------------------------------------------------- /js/bsp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * bsp.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | /** 25 | * BSP class. 26 | * Responsible for loading, storing and rendering the bsp tree. 27 | */ 28 | function Bsp() 29 | { 30 | // 31 | // Data loaded form the bsp file 32 | // 33 | var header; 34 | 35 | var nodes; 36 | var leaves; 37 | var markSurfaces; 38 | var planes; 39 | var vertices; // actually not needed for rendering, vertices are stored in vertexBuffer. But just in case someone needs them for e.g. picking etc. 40 | var edges; 41 | var faces; 42 | var surfEdges; 43 | var textureHeader; 44 | var mipTextures; 45 | var textureInfos; 46 | var models; 47 | var clipNodes; 48 | 49 | /** Array of Entity objects. @see Entity */ 50 | var entities; 51 | 52 | /** References to the entities that are brush entities. Array of Entity references. */ 53 | var brushEntities; 54 | 55 | // 56 | // Calculated 57 | // 58 | 59 | /** Stores the missing wads for this bsp file */ 60 | var missingWads; 61 | 62 | /** Array (for each face) of arrays (for each vertex of a face) of JSONs holding s and t coordinate. */ 63 | var textureCoordinates; 64 | var lightmapCoordinates; 65 | 66 | /** 67 | * Contains a plan white 1x1 texture to be used, when a texture could not be loaded yet. 68 | */ 69 | var whiteTexture; 70 | 71 | /** 72 | * Stores the texture IDs of the textures for each face. 73 | * Most of them will be dummy textures until they are later loaded from the Wad files. 74 | */ 75 | var textureLookup; 76 | 77 | /** Stores the texture IDs of the lightmaps for each face */ 78 | var lightmapLookup; 79 | 80 | /** Stores a list of missing textures */ 81 | var missingTextures; 82 | 83 | /** An array (for each leaf) of arrays (for each leaf) of booleans. */ 84 | var visLists; 85 | 86 | // 87 | // Buffers 88 | // 89 | var vertexBuffer; 90 | var texCoordBuffer; 91 | var lightmapCoordBuffer; 92 | var normalBuffer; 93 | 94 | /** Holds start index and count of indexes into the buffers for each face. Array of JSONs { start, count } */ 95 | var faceBufferRegions; 96 | 97 | /** If set to true, all resources are ready to render */ 98 | var loaded = false; 99 | }; 100 | 101 | /** 102 | * Returns the leaf that contains the given position 103 | * 104 | * @param pos A Vector3D describing the position to search for. 105 | * @return Returns the leaf index where the position is found or -1 otherwise. 106 | */ 107 | Bsp.prototype.traverseTree = function(pos, nodeIndex) 108 | { 109 | if(nodeIndex == undefined) 110 | nodeIndex = 0; 111 | 112 | var node = this.nodes[nodeIndex]; 113 | 114 | // Run once for each child 115 | for (var i = 0; i < 2; i++) 116 | { 117 | // If the index is positive it is an index into the nodes array 118 | if ((node.children[i]) >= 0) 119 | { 120 | if(pointInBox(pos, this.nodes[node.children[i]].mins, this.nodes[node.children[i]].maxs)) 121 | return this.traverseTree(pos, node.children[i]); 122 | } 123 | // Else, bitwise inversed, it is an index into the leaf array 124 | // Do not test solid leaf 0 125 | else if (~this.nodes[nodeIndex].children[i] != 0) 126 | { 127 | if(pointInBox(pos, this.leaves[~(node.children[i])].mins, this.leaves[~(node.children[i])].maxs)) 128 | return ~(node.children[i]); 129 | } 130 | } 131 | 132 | return -1; 133 | } 134 | 135 | /** 136 | * Renders the complete level. 137 | */ 138 | Bsp.prototype.render = function(cameraPos) 139 | { 140 | // enable/disable the required attribute arrays 141 | gl.enableVertexAttribArray(texCoordLocation); 142 | gl.enableVertexAttribArray(lightmapCoordLocation); 143 | gl.enableVertexAttribArray(normalLocation); 144 | gl.disableVertexAttribArray(colorLocation); 145 | 146 | // Bind the vertex buffer 147 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); 148 | gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); 149 | 150 | // Bind texture coordinate buffer 151 | gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer); 152 | gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); 153 | 154 | // Bind lightmap coordinate buffer 155 | gl.bindBuffer(gl.ARRAY_BUFFER, this.lightmapCoordBuffer); 156 | gl.vertexAttribPointer(lightmapCoordLocation, 2, gl.FLOAT, false, 0, 0); 157 | 158 | // Bind normal coordinate buffer 159 | gl.bindBuffer(gl.ARRAY_BUFFER, this.normalBuffer); 160 | gl.vertexAttribPointer(normalLocation, 3, gl.FLOAT, false, 0, 0); 161 | 162 | // Get the leaf where the camera is in 163 | var cameraLeaf = this.traverseTree(cameraPos); 164 | //console.log("Camera in leaf " + cameraLeaf); 165 | 166 | // Start the render traversal on the static geometry 167 | this.renderNode(0, cameraLeaf, cameraPos); 168 | 169 | // Now render all the entities 170 | for (var i = 0; i < this.brushEntities.length; i++) 171 | this.renderBrushEntity(this.brushEntities[i], cameraPos); 172 | } 173 | 174 | /** 175 | * Renders the given brush entity. 176 | * 177 | * @param entity An instance of Entity which is a brush entity (it must have a property "model"). 178 | * @param cameraPos The current camera position. 179 | */ 180 | Bsp.prototype.renderBrushEntity = function(entity, cameraPos) 181 | { 182 | // Model 183 | var modelIndex = parseInt(entity.properties["model"].substring(1)); 184 | var model = this.models[modelIndex]; 185 | 186 | // Alpha value 187 | var alpha; 188 | var renderamt = entity.properties["renderamt"]; 189 | if(renderamt == undefined) 190 | alpha = 255; 191 | else 192 | alpha = parseInt(renderamt); 193 | 194 | // Rendermode 195 | var renderMode; 196 | var renderModeString = entity.properties["rendermode"]; 197 | if(renderModeString == undefined) 198 | renderMode = RENDER_MODE_NORMAL; 199 | else 200 | renderMode = parseInt(renderModeString); 201 | 202 | // push matrix and translate to model origin 203 | var oldModelviewMatrix = new J3DIMatrix4(modelviewMatrix); 204 | modelviewMatrix.translate(model.origin.x, model.origin.y, model.origin.z); 205 | modelviewMatrix.setUniform(gl, modelviewMatrixLocation, false); 206 | 207 | switch (renderMode) 208 | { 209 | case RENDER_MODE_NORMAL: 210 | break; 211 | case RENDER_MODE_TEXTURE: 212 | gl.uniform1f(alphaLocation, alpha / 255.0); 213 | gl.enable(gl.BLEND); 214 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE); 215 | gl.depthMask(false); // make z buffer readonly 216 | 217 | break; 218 | case RENDER_MODE_SOLID: 219 | gl.uniform1i(alphaTestLocation, 1); 220 | break; 221 | case RENDER_MODE_ADDITIVE: 222 | gl.uniform1f(alphaLocation, alpha / 255.0); 223 | gl.enable(gl.BLEND); 224 | gl.blendFunc(gl.ONE, gl.ONE); 225 | gl.depthMask(false); // make z buffer readonly 226 | 227 | break; 228 | } 229 | 230 | this.renderNode(model.headNodes[0], -1, cameraPos); 231 | 232 | switch (renderMode) 233 | { 234 | case RENDER_MODE_NORMAL: 235 | break; 236 | case RENDER_MODE_TEXTURE: 237 | case RENDER_MODE_ADDITIVE: 238 | gl.uniform1f(alphaLocation, 1.0); 239 | gl.disable(gl.BLEND); 240 | gl.depthMask(true); 241 | 242 | break; 243 | case RENDER_MODE_SOLID: 244 | gl.uniform1i(alphaTestLocation, 0); 245 | break; 246 | } 247 | 248 | // pop matrix 249 | modelviewMatrix = oldModelviewMatrix; 250 | modelviewMatrix.setUniform(gl, modelviewMatrixLocation, false); 251 | } 252 | 253 | /** 254 | * Renders a node of the bsp tree. Called by Bsp.render(). 255 | * 256 | * @param nodeIndex The index of the node to render. 257 | * @param cameraLeaf The leaf the camera is in. Used for PVS. 258 | * @param cameraPos The position of the camera. 259 | */ 260 | Bsp.prototype.renderNode = function(nodeIndex, cameraLeaf, cameraPos) 261 | { 262 | if (nodeIndex < 0) 263 | { 264 | if (nodeIndex == -1) // Solid leaf 0 265 | return; 266 | 267 | // perform vis check 268 | if (cameraLeaf > 0) 269 | if (this.header.lumps[LUMP_VISIBILITY].length != 0 && 270 | this.visLists[cameraLeaf - 1] != null && 271 | !this.visLists[cameraLeaf - 1][~nodeIndex - 1]) 272 | return; 273 | 274 | this.renderLeaf(~nodeIndex); 275 | 276 | return; 277 | } 278 | 279 | var distance; 280 | 281 | var node = this.nodes[nodeIndex]; 282 | var plane = this.planes[node.plane]; 283 | 284 | switch (plane.type) 285 | { 286 | case PLANE_X: 287 | distance = cameraPos.x - plane.dist; 288 | break; 289 | case PLANE_Y: 290 | distance = cameraPos.y - plane.dist; 291 | break; 292 | case PLANE_Z: 293 | distance = cameraPos.z - plane.dist; 294 | break; 295 | default: 296 | distance = dotProduct(plane.normal, cameraPos) - plane.dist; 297 | } 298 | 299 | if (distance > 0.0) 300 | { 301 | this.renderNode(node.children[1], cameraLeaf, cameraPos); 302 | this.renderNode(node.children[0], cameraLeaf, cameraPos); 303 | } 304 | else 305 | { 306 | this.renderNode(node.children[0], cameraLeaf, cameraPos); 307 | this.renderNode(node.children[1], cameraLeaf, cameraPos); 308 | } 309 | } 310 | 311 | /** 312 | * Renders a leaf of the bsp tree. Called by Bsp.renderNode(). 313 | * 314 | * @param leafIndex The index of the leaf to render. 315 | */ 316 | Bsp.prototype.renderLeaf = function(leafIndex) 317 | { 318 | var leaf = this.leaves[leafIndex]; 319 | 320 | // Loop through each face in this leaf 321 | for (var i = 0; i < leaf.markSurfaces; i++) 322 | this.renderFace(this.markSurfaces[leaf.firstMarkSurface + i]); 323 | } 324 | 325 | /** 326 | * Renders a face of the bsp tree. Called by Bsp.renderLeaf(). 327 | * 328 | * @param faceIndex The index of the face to render. 329 | */ 330 | Bsp.prototype.renderFace = function(faceIndex) 331 | { 332 | var face = this.faces[faceIndex]; 333 | var texInfo = this.textureInfos[face.textureInfo]; 334 | 335 | if (face.styles[0] == 0xFF) 336 | return; // Skip sky faces 337 | 338 | // if the light map offset is not -1 and the lightmap lump is not empty, there are lightmaps 339 | var lightmapAvailable = face.lightmapOffset != -1 && this.header.lumps[LUMP_LIGHTING].length > 0; 340 | 341 | gl.activeTexture(gl.TEXTURE0); 342 | gl.bindTexture(gl.TEXTURE_2D, this.textureLookup[texInfo.mipTexture]); 343 | 344 | gl.activeTexture(gl.TEXTURE1); 345 | gl.bindTexture(gl.TEXTURE_2D, this.lightmapLookup[faceIndex]); 346 | 347 | 348 | gl.drawArrays(polygonMode ? gl.LINE_STRIP : gl.TRIANGLE_FAN, this.faceBufferRegions[faceIndex].start, this.faceBufferRegions[faceIndex].count); 349 | } 350 | 351 | /** 352 | * Runs through all faces and generates the OpenGL buffers required for rendering. 353 | */ 354 | Bsp.prototype.preRender = function() 355 | { 356 | var vertices = new Array(); 357 | var texCoords = new Array(); 358 | var lightmapCoords = new Array(); 359 | var normals = new Array(); 360 | 361 | this.faceBufferRegions = new Array(this.faces.length); 362 | var elements = 0; 363 | 364 | // for each face 365 | for(var i = 0; i < this.faces.length; i++) 366 | { 367 | var face = this.faces[i]; 368 | 369 | this.faceBufferRegions[i] = { 370 | start : elements, 371 | count : face.edges 372 | }; 373 | 374 | var texInfo = this.textureInfos[face.textureInfo]; 375 | var plane = this.planes[face.plane]; 376 | 377 | var normal = plane.normal; 378 | 379 | var faceTexCoords = this.textureCoordinates[i]; 380 | var faceLightmapCoords = this.lightmapCoordinates[i]; 381 | 382 | for (var j = 0; j < face.edges; j++) 383 | { 384 | var edgeIndex = this.surfEdges[face.firstEdge + j]; // This gives the index into the edge lump 385 | 386 | var vertexIndex; 387 | if (edgeIndex > 0) 388 | { 389 | var edge = this.edges[edgeIndex]; 390 | vertexIndex = edge.vertices[0]; 391 | } 392 | else 393 | { 394 | edgeIndex *= -1; 395 | var edge = this.edges[edgeIndex]; 396 | vertexIndex = edge.vertices[1]; 397 | } 398 | 399 | var vertex = this.vertices[vertexIndex]; 400 | 401 | var texCoord = faceTexCoords[j]; 402 | var lightmapCoord = faceLightmapCoords[j]; 403 | 404 | // Write to buffers 405 | vertices.push(vertex.x); 406 | vertices.push(vertex.y); 407 | vertices.push(vertex.z); 408 | 409 | texCoords.push(texCoord.s); 410 | texCoords.push(texCoord.t); 411 | 412 | lightmapCoords.push(lightmapCoord.s); 413 | lightmapCoords.push(lightmapCoord.t); 414 | 415 | normals.push(normal.x); 416 | normals.push(normal.y); 417 | normals.push(normal.z); 418 | 419 | elements += 1; 420 | } 421 | } 422 | 423 | // Create ALL the buffers !!! 424 | this.vertexBuffer = gl.createBuffer(); 425 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); 426 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); 427 | 428 | this.texCoordBuffer = gl.createBuffer(); 429 | gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer); 430 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW); 431 | 432 | this.lightmapCoordBuffer = gl.createBuffer(); 433 | gl.bindBuffer(gl.ARRAY_BUFFER, this.lightmapCoordBuffer); 434 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(lightmapCoords), gl.STATIC_DRAW); 435 | 436 | this.normalBuffer = gl.createBuffer(); 437 | gl.bindBuffer(gl.ARRAY_BUFFER, this.normalBuffer); 438 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW); 439 | } 440 | 441 | /** 442 | * Loades the complete level and prepares it for rendering 443 | */ 444 | Bsp.prototype.loadBSP = function(arrayBuffer) 445 | { 446 | console.log('Begin loading bsp'); 447 | this.loaded = false; 448 | 449 | var src = new BinaryFile(arrayBuffer); 450 | 451 | if(!this.readHeader(src)) 452 | return false; 453 | 454 | this.readNodes(src); 455 | this.readLeaves(src); 456 | this.readMarkSurfaces(src); 457 | this.readPlanes(src); 458 | this.readVertices(src); 459 | this.readEdges(src); 460 | this.readFaces(src); 461 | this.readSurfEdges(src); 462 | this.readMipTextures(src); 463 | this.readTextureInfos(src); 464 | this.readModels(src); 465 | this.readClipNodes(src); 466 | 467 | this.loadEntities(src); // muast be loaded before textures 468 | this.loadTextures(src); // plus coordinates 469 | this.loadLightmaps(src); // plus coordinates 470 | this.loadVIS(src); 471 | 472 | // FINALLY create buffers for rendering 473 | this.preRender(); 474 | 475 | console.log('Finished loading BSP'); 476 | this.loaded = true; 477 | 478 | return true; 479 | } 480 | 481 | Bsp.prototype.readHeader = function(src) 482 | { 483 | this.header = new BspHeader(); 484 | 485 | this.header.version = src.readLong(); 486 | 487 | if(this.header.version != 30) 488 | { 489 | console.log('Invalid bsp version: ' + this.header.version + ' Only bsp v30 is supported'); 490 | return false; 491 | } 492 | 493 | this.header.lumps = new Array(); 494 | for(var i = 0; i < HEADER_LUMPS; i++) 495 | { 496 | var lump = new BspLump(); 497 | 498 | lump.offset = src.readLong(); 499 | lump.length = src.readLong(); 500 | 501 | this.header.lumps.push(lump); 502 | } 503 | 504 | console.log('Read ' + this.header.lumps.length + ' lumps'); 505 | 506 | return true; 507 | } 508 | 509 | Bsp.prototype.readNodes = function(src) 510 | { 511 | src.seek(this.header.lumps[LUMP_NODES].offset); 512 | 513 | this.nodes = new Array(); 514 | 515 | for(var i = 0; i < this.header.lumps[LUMP_NODES].length / SIZE_OF_BSPNODE; i++) 516 | { 517 | var node = new BspNode(); 518 | 519 | node.plane = src.readULong(); 520 | 521 | node.children = new Array(); 522 | node.children.push(src.readShort()); 523 | node.children.push(src.readShort()); 524 | 525 | node.mins = new Array(); 526 | node.mins.push(src.readShort()); 527 | node.mins.push(src.readShort()); 528 | node.mins.push(src.readShort()); 529 | 530 | node.maxs = new Array(); 531 | node.maxs.push(src.readShort()); 532 | node.maxs.push(src.readShort()); 533 | node.maxs.push(src.readShort()); 534 | 535 | node.firstFace = src.readUShort(); 536 | node.faces = src.readUShort(); 537 | 538 | this.nodes.push(node); 539 | } 540 | 541 | console.log('Read ' + this.nodes.length + ' Nodes'); 542 | } 543 | 544 | Bsp.prototype.readLeaves = function(src) 545 | { 546 | src.seek(this.header.lumps[LUMP_LEAVES].offset); 547 | 548 | this.leaves = new Array(); 549 | 550 | for(var i = 0; i < this.header.lumps[LUMP_LEAVES].length / SIZE_OF_BSPLEAF; i++) 551 | { 552 | var leaf = new BspNode(); 553 | 554 | leaf.content = src.readLong(); 555 | 556 | leaf.visOffset = src.readLong(); 557 | 558 | leaf.mins = new Array(); 559 | leaf.mins.push(src.readShort()); 560 | leaf.mins.push(src.readShort()); 561 | leaf.mins.push(src.readShort()); 562 | 563 | leaf.maxs = new Array(); 564 | leaf.maxs.push(src.readShort()); 565 | leaf.maxs.push(src.readShort()); 566 | leaf.maxs.push(src.readShort()); 567 | 568 | leaf.firstMarkSurface = src.readUShort(); 569 | 570 | leaf.markSurfaces = src.readUShort(); 571 | 572 | leaf.ambientLevels = new Array(); 573 | leaf.ambientLevels.push(src.readUByte()); 574 | leaf.ambientLevels.push(src.readUByte()); 575 | leaf.ambientLevels.push(src.readUByte()); 576 | leaf.ambientLevels.push(src.readUByte()); 577 | 578 | this.leaves.push(leaf); 579 | } 580 | 581 | console.log('Read ' + this.leaves.length + ' Leaves'); 582 | } 583 | 584 | Bsp.prototype.readMarkSurfaces = function(src) 585 | { 586 | src.seek(this.header.lumps[LUMP_MARKSURFACES].offset); 587 | 588 | this.markSurfaces = new Array(); 589 | 590 | for(var i = 0; i < this.header.lumps[LUMP_MARKSURFACES].length / SIZE_OF_BSPMARKSURFACE; i++) 591 | this.markSurfaces.push(src.readUShort()); 592 | 593 | console.log('Read ' + this.markSurfaces.length + ' MarkSurfaces'); 594 | } 595 | 596 | Bsp.prototype.readPlanes = function(src) 597 | { 598 | src.seek(this.header.lumps[LUMP_PLANES].offset); 599 | 600 | this.planes = new Array(); 601 | 602 | for(var i = 0; i < this.header.lumps[LUMP_PLANES].length / SIZE_OF_BSPPLANE; i++) 603 | { 604 | var plane = new BspPlane(); 605 | 606 | plane.normal = new Vector3D(); 607 | plane.normal.x = src.readFloat(); 608 | plane.normal.y = src.readFloat(); 609 | plane.normal.z = src.readFloat(); 610 | 611 | plane.dist = src.readFloat(); 612 | 613 | plane.type = src.readLong(); 614 | 615 | this.planes.push(plane); 616 | } 617 | 618 | console.log('Read ' + this.planes.length + ' Planes'); 619 | } 620 | 621 | Bsp.prototype.readVertices = function(src) 622 | { 623 | src.seek(this.header.lumps[LUMP_VERTICES].offset); 624 | 625 | this.vertices = new Array(); 626 | 627 | for(var i = 0; i < this.header.lumps[LUMP_VERTICES].length / SIZE_OF_BSPVERTEX; i++) 628 | { 629 | var vertex = new Vector3D(); 630 | 631 | vertex.x = src.readFloat(); 632 | vertex.y = src.readFloat(); 633 | vertex.z = src.readFloat(); 634 | 635 | this.vertices.push(vertex); 636 | } 637 | 638 | console.log('Read ' + this.vertices.length + ' Vertices'); 639 | } 640 | 641 | Bsp.prototype.readEdges = function(src) 642 | { 643 | src.seek(this.header.lumps[LUMP_EDGES].offset); 644 | 645 | this.edges = new Array(); 646 | 647 | for(var i = 0; i < this.header.lumps[LUMP_EDGES].length / SIZE_OF_BSPEDGE; i++) 648 | { 649 | var edge = new BspEdge(); 650 | 651 | edge.vertices = new Array(); 652 | edge.vertices.push(src.readUShort()); 653 | edge.vertices.push(src.readUShort()); 654 | 655 | this.edges.push(edge); 656 | } 657 | 658 | console.log('Read ' + this.edges.length + ' Edges'); 659 | } 660 | 661 | Bsp.prototype.readFaces = function(src) 662 | { 663 | src.seek(this.header.lumps[LUMP_FACES].offset); 664 | 665 | this.faces = new Array(); 666 | 667 | for(var i = 0; i < this.header.lumps[LUMP_FACES].length / SIZE_OF_BSPFACE; i++) 668 | { 669 | var face = new BspEdge(); 670 | 671 | face.plane = src.readUShort(); 672 | 673 | face.planeSide = src.readUShort(); 674 | 675 | face.firstEdge = src.readULong(); 676 | 677 | face.edges = src.readUShort(); 678 | 679 | face.textureInfo = src.readUShort(); 680 | 681 | face.styles = new Array(); 682 | face.styles.push(src.readUByte()); 683 | face.styles.push(src.readUByte()); 684 | face.styles.push(src.readUByte()); 685 | face.styles.push(src.readUByte()); 686 | 687 | face.lightmapOffset = src.readULong(); 688 | 689 | this.faces.push(face); 690 | } 691 | 692 | console.log('Read ' + this.faces.length + ' Faces'); 693 | } 694 | 695 | Bsp.prototype.readSurfEdges = function(src) 696 | { 697 | src.seek(this.header.lumps[LUMP_SURFEDGES].offset); 698 | 699 | this.surfEdges = new Array(); 700 | 701 | for(var i = 0; i < this.header.lumps[LUMP_SURFEDGES].length / SIZE_OF_BSPSURFEDGE; i++) 702 | { 703 | this.surfEdges.push(src.readLong()); 704 | } 705 | 706 | console.log('Read ' + this.surfEdges.length + ' SurfEdges'); 707 | } 708 | 709 | Bsp.prototype.readTextureHeader = function(src) 710 | { 711 | src.seek(this.header.lumps[LUMP_TEXTURES].offset); 712 | 713 | this.textureHeader = new BspTextureHeader(); 714 | 715 | this.textureHeader.textures = src.readULong(); 716 | 717 | this.textureHeader.offsets = new Array(); 718 | for(var i = 0; i < this.textureHeader.textures; i++) 719 | this.textureHeader.offsets.push(src.readLong()); 720 | 721 | console.log('Read TextureHeader. Bsp files references/contains ' + this.textureHeader.textures + ' textures'); 722 | } 723 | 724 | Bsp.prototype.readMipTextures = function(src) 725 | { 726 | this.readTextureHeader(src); 727 | 728 | this.mipTextures = new Array(); 729 | 730 | for(var i = 0; i < this.textureHeader.textures; i++) 731 | { 732 | src.seek(this.header.lumps[LUMP_TEXTURES].offset + this.textureHeader.offsets[i]); 733 | 734 | var miptex = new BspMipTexture(); 735 | 736 | miptex.name = src.readString(MAXTEXTURENAME); 737 | 738 | miptex.width = src.readULong(); 739 | 740 | miptex.height = src.readULong(); 741 | 742 | miptex.offsets = new Array(); 743 | for(var j = 0; j < MIPLEVELS; j++) 744 | miptex.offsets.push(src.readULong()); 745 | 746 | this.mipTextures.push(miptex); 747 | } 748 | } 749 | 750 | Bsp.prototype.readTextureInfos = function(src) 751 | { 752 | src.seek(this.header.lumps[LUMP_TEXINFO].offset); 753 | 754 | this.textureInfos = new Array(); 755 | 756 | for(var i = 0; i < this.header.lumps[LUMP_TEXINFO].length / SIZE_OF_BSPTEXTUREINFO; i++) 757 | { 758 | var texInfo = new BspTextureInfo(); 759 | 760 | texInfo.s = new Vector3D(); 761 | texInfo.s.x = src.readFloat(); 762 | texInfo.s.y = src.readFloat(); 763 | texInfo.s.z = src.readFloat(); 764 | 765 | texInfo.sShift = src.readFloat(); 766 | 767 | texInfo.t = new Vector3D(); 768 | texInfo.t.x = src.readFloat(); 769 | texInfo.t.y = src.readFloat(); 770 | texInfo.t.z = src.readFloat(); 771 | 772 | texInfo.tShift = src.readFloat(); 773 | 774 | texInfo.mipTexture = src.readULong(); 775 | 776 | texInfo.flags = src.readULong(); 777 | 778 | this.textureInfos.push(texInfo); 779 | } 780 | 781 | console.log('Read ' + this.textureInfos.length + ' TextureInfos'); 782 | } 783 | 784 | Bsp.prototype.readModels = function(src) 785 | { 786 | src.seek(this.header.lumps[LUMP_MODELS].offset); 787 | 788 | this.models = new Array(); 789 | 790 | for(var i = 0; i < this.header.lumps[LUMP_MODELS].length / SIZE_OF_BSPMODEL; i++) 791 | { 792 | var model = new BspModel(); 793 | 794 | model.mins = new Array(); 795 | model.mins.push(src.readFloat()); 796 | model.mins.push(src.readFloat()); 797 | model.mins.push(src.readFloat()); 798 | 799 | model.maxs = new Array(); 800 | model.maxs.push(src.readFloat()); 801 | model.maxs.push(src.readFloat()); 802 | model.maxs.push(src.readFloat()); 803 | 804 | model.origin = new Vector3D(); 805 | model.origin.x = src.readFloat(); 806 | model.origin.y = src.readFloat(); 807 | model.origin.z = src.readFloat(); 808 | 809 | model.headNodes = new Array(); 810 | for(var j = 0; j < MAX_MAP_HULLS; j++) 811 | model.headNodes.push(src.readLong()); 812 | 813 | model.visLeafs = src.readLong(); 814 | 815 | model.firstFace = src.readLong(); 816 | 817 | model.faces = src.readLong(); 818 | 819 | this.models.push(model); 820 | } 821 | 822 | console.log('Read ' + this.models.length + ' Models'); 823 | } 824 | 825 | Bsp.prototype.readClipNodes = function(src) 826 | { 827 | src.seek(this.header.lumps[LUMP_CLIPNODES].offset); 828 | 829 | this.clipNodes = new Array(); 830 | 831 | for(var i = 0; i < this.header.lumps[LUMP_CLIPNODES].length / SIZE_OF_BSPCLIPNODE; i++) 832 | { 833 | var clipNode = new BspClipNode(); 834 | 835 | clipNode.plane = src.readLong(); 836 | 837 | clipNode.children = new Array(); 838 | clipNode.children.push(src.readShort()); 839 | clipNode.children.push(src.readShort()); 840 | 841 | this.clipNodes.push(clipNode); 842 | } 843 | 844 | console.log('Read ' + this.clipNodes.length + ' ClipNodes'); 845 | } 846 | 847 | /** 848 | * Returns true if the given entity is a brush entity (an entity, that can be rendered directly as small bsp tree). 849 | */ 850 | Bsp.prototype.isBrushEntity = function(entity) 851 | { 852 | if (entity.properties.model == undefined) 853 | return false; 854 | 855 | if(entity.properties.model.substring(0, 1) != '*') 856 | return false; // external model 857 | 858 | /*var className = entity.classname; 859 | if (className == "func_door_rotating" || 860 | className == "func_door" || 861 | className == "func_illusionary" || 862 | className == "func_wall" || 863 | className == "func_breakable" || 864 | className == "func_button") 865 | return true; 866 | else 867 | return false;*/ 868 | 869 | return true; 870 | } 871 | 872 | /** 873 | * Loads and parses the entities from the entity lump. 874 | */ 875 | Bsp.prototype.loadEntities = function(src) 876 | { 877 | src.seek(this.header.lumps[LUMP_ENTITIES].offset); 878 | 879 | var entityData = src.readString(this.header.lumps[LUMP_ENTITIES].length); 880 | 881 | this.entities = new Array(); 882 | this.brushEntities = new Array(); 883 | 884 | var end = -1; 885 | while(true) 886 | { 887 | var begin = entityData.indexOf('{', end + 1); 888 | if(begin == -1) 889 | break; 890 | 891 | end = entityData.indexOf('}', begin + 1); 892 | 893 | var entityString = entityData.substring(begin + 1, end); 894 | 895 | var entity = new Entity(entityString); 896 | 897 | if(this.isBrushEntity(entity)) 898 | this.brushEntities.push(entity); 899 | 900 | this.entities.push(entity); 901 | } 902 | 903 | console.log('Read ' + this.entities.length + ' Entities (' + this.brushEntities.length + ' Brush Entities)'); 904 | } 905 | 906 | /** 907 | * Finds all entities that match the given classname. 908 | * 909 | * @param name The value of the classname property. 910 | * @return Returns an array of Entity references to the found entities. 911 | */ 912 | Bsp.prototype.findEntities = function(name) 913 | { 914 | var matches = new Array(); 915 | for(var i = 0; i < this.entities.length; i++) 916 | { 917 | var entity = this.entities[i]; 918 | 919 | if(entity.properties.classname == name) 920 | matches.push(entity); 921 | } 922 | 923 | return matches; 924 | } 925 | 926 | /** 927 | * Loads and decompresses the PVS (Potentially Visible Set, or VIS for short) from the bsp file. 928 | */ 929 | Bsp.prototype.loadVIS = function(src) 930 | { 931 | if(this.header.lumps[LUMP_VISIBILITY].length > 0) 932 | { 933 | var visLeaves = this.countVisLeaves(0); 934 | 935 | this.visLists = new Array(visLeaves); 936 | 937 | for (var i = 0; i < visLeaves; i++) 938 | { 939 | if (this.leaves[i + 1].visOffset >= 0) 940 | this.visLists[i] = this.getPVS(src, i + 1, visLeaves); 941 | else 942 | this.visLists[i] = null; 943 | } 944 | } 945 | else 946 | console.log("No VIS found\n"); 947 | } 948 | 949 | /** 950 | * Counts the number of so-called VisLeaves (leafs that have VIS/PVS information) in the bsp file. 951 | */ 952 | Bsp.prototype.countVisLeaves = function(nodeIndex) 953 | { 954 | if (nodeIndex < 0) 955 | { 956 | // leaf 0 957 | if(nodeIndex == -1) 958 | return 0; 959 | 960 | if(this.leaves[~nodeIndex].contents == CONTENTS_SOLID) 961 | return 0; 962 | 963 | return 1; 964 | } 965 | 966 | var node = this.nodes[nodeIndex]; 967 | 968 | return this.countVisLeaves(node.children[0]) + this.countVisLeaves(node.children[1]); 969 | } 970 | 971 | /** 972 | * Retrieves the PVS for the given leaf. 973 | * 974 | * @param src Reference to an instance of BinaryFile representing the bsp file to read from. 975 | * @param leafIndex Index of the leaf to retrieve the PVS for. 976 | * @param visLeaves The number of VisLeaves as returned by Bsp.countVisLeaves(). 977 | * @return Returns an array of boolean values representing the visibility list for the given leaf. 978 | */ 979 | Bsp.prototype.getPVS = function(src, leafIndex, visLeaves) 980 | { 981 | var list = new Array(this.leaves.length - 1); 982 | 983 | for(var i = 0; i < list.length; i++) 984 | list[i] = false; 985 | 986 | var compressed = new Uint8Array(src.buffer, this.header.lumps[LUMP_VISIBILITY].offset + this.leaves[leafIndex].visOffset); 987 | 988 | var writeIndex = 0; // Index that moves through the destination bool array (list) 989 | 990 | for (var curByte = 0; writeIndex < visLeaves; curByte++) 991 | { 992 | // Check for a run of 0s 993 | if (compressed[curByte] == 0) 994 | { 995 | // Advance past this run of 0s 996 | curByte++; 997 | // Move the write pointer the number of compressed 0s 998 | writeIndex += 8 * compressed[curByte]; 999 | } 1000 | else 1001 | { 1002 | // Iterate through this byte with bit shifting till the bit has moved beyond the 8th digit 1003 | for (var mask = 0x01; mask != 0x0100; writeIndex++, mask <<= 1) 1004 | { 1005 | // Test a bit of the compressed PVS with the bit mask 1006 | if ((compressed[curByte] & mask) && (writeIndex < visLeaves)) 1007 | list[writeIndex] = true; 1008 | } 1009 | } 1010 | } 1011 | 1012 | //console.log("List for leaf " + leafIndex + ": " + list); 1013 | 1014 | return list; 1015 | } 1016 | 1017 | /** 1018 | * Tries to load the texture identified by name from the loaded wad files. 1019 | * 1020 | * @return Returns the texture identifier if the texture has been found, otherwise null. 1021 | */ 1022 | Bsp.prototype.loadTextureFromWad = function(name) 1023 | { 1024 | var texture = null; 1025 | for(var k = 0; k < loadedWads.length; k++) 1026 | { 1027 | texture = loadedWads[k].loadTexture(name); 1028 | if(texture != null) 1029 | break; 1030 | } 1031 | 1032 | return texture; 1033 | } 1034 | 1035 | /** 1036 | * Loads all the texture data from the bsp file and generates texture coordinates. 1037 | */ 1038 | Bsp.prototype.loadTextures = function(src) 1039 | { 1040 | this.textureCoordinates = new Array(); 1041 | 1042 | // 1043 | // Texture coordinates 1044 | // 1045 | 1046 | for (var i = 0; i < this.faces.length; i++) 1047 | { 1048 | var face = this.faces[i]; 1049 | var texInfo = this.textureInfos[face.textureInfo]; 1050 | 1051 | var faceCoords = new Array(); 1052 | 1053 | for (var j = 0; j < face.edges; j++) 1054 | { 1055 | var edgeIndex = this.surfEdges[face.firstEdge + j]; 1056 | 1057 | var vertexIndex; 1058 | if (edgeIndex > 0) 1059 | { 1060 | var edge = this.edges[edgeIndex]; 1061 | vertexIndex = edge.vertices[0]; 1062 | } 1063 | else 1064 | { 1065 | edgeIndex *= -1; 1066 | var edge = this.edges[edgeIndex]; 1067 | vertexIndex = edge.vertices[1]; 1068 | } 1069 | 1070 | var vertex = this.vertices[vertexIndex]; 1071 | var mipTexture = this.mipTextures[texInfo.mipTexture]; 1072 | 1073 | var coord = { 1074 | s : (dotProduct(vertex, texInfo.s) + texInfo.sShift) / mipTexture.width, 1075 | t : (dotProduct(vertex, texInfo.t) + texInfo.tShift) / mipTexture.height 1076 | }; 1077 | 1078 | faceCoords.push(coord); 1079 | } 1080 | 1081 | this.textureCoordinates.push(faceCoords); 1082 | } 1083 | 1084 | // 1085 | // Texture images 1086 | // 1087 | 1088 | // Create white texture 1089 | this.whiteTexture = pixelsToTexture(new Array(255, 255, 255), 1, 1, 3, function(texture, image) 1090 | { 1091 | gl.bindTexture(gl.TEXTURE_2D, texture); 1092 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 1093 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR_MIPMAP_LINEAR); 1094 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); 1095 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 1096 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); 1097 | gl.generateMipmap(gl.TEXTURE_2D); 1098 | gl.bindTexture(gl.TEXTURE_2D, null); 1099 | }); 1100 | 1101 | 1102 | this.textureLookup = new Array(this.faces.length); 1103 | this.missingTextures = new Array(); 1104 | 1105 | for(var i = 0; i < this.mipTextures.length; i++) 1106 | { 1107 | var mipTexture = this.mipTextures[i]; 1108 | 1109 | if(mipTexture.offsets[0] == 0) 1110 | { 1111 | // 1112 | // External texture 1113 | // 1114 | 1115 | // search texture in loaded wads 1116 | var texture = this.loadTextureFromWad(mipTexture.name); 1117 | 1118 | if(texture != null) 1119 | { 1120 | // the texture has been found in a loaded wad 1121 | this.textureLookup[i] = texture; 1122 | 1123 | console.log("Texture " + mipTexture.name + " found"); 1124 | } 1125 | else 1126 | { 1127 | // bind simple white texture to do not disturb lightmaps 1128 | this.textureLookup[i] = this.whiteTexture; 1129 | 1130 | // store the name and position of this missing texture, 1131 | // so that it can later be loaded to the right position by calling loadMissingTextures() 1132 | this.missingTextures.push({ name: mipTexture.name, index: i }); 1133 | 1134 | console.log("Texture " + mipTexture.name + " is missing"); 1135 | } 1136 | 1137 | continue; 1138 | } 1139 | else 1140 | { 1141 | // 1142 | // Load internal texture if present 1143 | // 1144 | 1145 | // Calculate offset of the texture in the bsp file 1146 | var offset = this.header.lumps[LUMP_TEXTURES].offset + this.textureHeader.offsets[i]; 1147 | 1148 | // Use the texture loading procedure from the Wad class 1149 | this.textureLookup[i] = Wad.prototype.fetchTextureAtOffset(src, offset); 1150 | 1151 | console.log("Fetched interal texture " + mipTexture.name); 1152 | } 1153 | } 1154 | 1155 | // Now that all dummy texture unit IDs have been created, alert the user to select wads for them 1156 | this.showMissingWads(); 1157 | } 1158 | 1159 | /** 1160 | * Tries to load all missing textures from the currently loaded Wad files. 1161 | */ 1162 | Bsp.prototype.loadMissingTextures = function() 1163 | { 1164 | for(var i = 0; i < this.missingTextures.length; i++) 1165 | { 1166 | var missingTexture = this.missingTextures[i]; 1167 | var texture = this.loadTextureFromWad(missingTexture.name); 1168 | 1169 | if(texture != null) 1170 | { 1171 | // the texture has finally be found, insert its ID 1172 | this.textureLookup[missingTexture.index] = texture; 1173 | 1174 | console.log("Texture " + missingTexture.name + " found (delayed)"); 1175 | 1176 | // and remove the entry 1177 | this.missingTextures.splice(i, 1); 1178 | i--; 1179 | } 1180 | else 1181 | console.log("Texture " + missingTexture.name + " is still missing"); 1182 | } 1183 | } 1184 | 1185 | /** 1186 | * Updates the gui by the wad files needed by the bsp file but currently not loaded. 1187 | */ 1188 | Bsp.prototype.showMissingWads = function() 1189 | { 1190 | this.missingWads = new Array(); 1191 | 1192 | var worldspawn = this.findEntities('worldspawn')[0]; 1193 | var wadString = worldspawn.properties['wad']; 1194 | var wads = wadString.split(';'); 1195 | 1196 | for(var i = 0; i < wads.length; i++) 1197 | { 1198 | var wad = wads[i]; 1199 | if(wad == '') 1200 | continue; 1201 | 1202 | // shorten path 1203 | var pos = wad.lastIndexOf('\\'); 1204 | var file = wad.substring(pos + 1); 1205 | //var dir = wad.substring(wad.lastIndexOf('\\', pos - 1) + 1, pos); 1206 | 1207 | // store the missing wad file 1208 | //this.missingWads.push({ name: file, dir: dir }); 1209 | var found = false; 1210 | for(var j = 0; j < loadedWads.length; j++) 1211 | if(loadedWads[j].name == file) 1212 | found = true; 1213 | 1214 | if(!found) // the wad file hasn't already been added loaded 1215 | this.missingWads.push(file); 1216 | } 1217 | 1218 | if(this.missingWads.length == 0) 1219 | return; 1220 | 1221 | $('#wadmissing p:first-child').html('The bsp file references the following missing Wad files:'); 1222 | 1223 | for(var i = 0; i < this.missingWads.length; i++) 1224 | { 1225 | var name = this.missingWads[i]; 1226 | //var dir = this.missingWads[i].dir; 1227 | 1228 | //if(dir == 'cstrike' || dir == 'valve') 1229 | // caption += ' (' + dir + ')'; 1230 | 1231 | //$('#wadmissing ul').append('
  • ' + caption + '
  • '); 1232 | $('#wadmissing ul').append('
  • ' + name + '
  • '); 1233 | } 1234 | 1235 | setTimeout($('#wadmissing').slideDown(300), 0); 1236 | } 1237 | 1238 | /** 1239 | * Loads all the lightmaps from bsp file, generates textures and texture coordinates. 1240 | */ 1241 | Bsp.prototype.loadLightmaps = function(src) 1242 | { 1243 | this.lightmapCoordinates = new Array(); 1244 | this.lightmapLookup = new Array(this.faces.length); 1245 | 1246 | var loadedData = 0; 1247 | var loadedLightmaps = 0; 1248 | 1249 | for (var i = 0; i < this.faces.length; i++) 1250 | { 1251 | var face = this.faces[i]; 1252 | 1253 | var faceCoords = new Array(); 1254 | 1255 | if (face.styles[0] != 0 || face.lightmapOffset == -1) 1256 | { 1257 | this.lightmapLookup[i] = 0; 1258 | 1259 | // create dummy lightmap coords 1260 | for (var j = 0; j < face.edges; j++) 1261 | faceCoords.push({ s: 0, t : 0}); 1262 | this.lightmapCoordinates.push(faceCoords); 1263 | 1264 | continue; 1265 | } 1266 | 1267 | /* *********** QRAD ********** */ 1268 | 1269 | var minU = 999999.0; 1270 | var minV = 999999.0; 1271 | var maxU = -99999.0; 1272 | var maxV = -99999.0; 1273 | 1274 | var texInfo = this.textureInfos[face.textureInfo]; 1275 | 1276 | for (var j = 0; j < face.edges; j++) 1277 | { 1278 | var edgeIndex = this.surfEdges[face.firstEdge + j]; 1279 | var vertex; 1280 | if (edgeIndex >= 0) 1281 | vertex = this.vertices[this.edges[edgeIndex].vertices[0]]; 1282 | else 1283 | vertex = this.vertices[this.edges[-edgeIndex].vertices[1]]; 1284 | 1285 | var u = Math.round(dotProduct(texInfo.s, vertex) + texInfo.sShift); 1286 | if (u < minU) 1287 | minU = u; 1288 | if (u > maxU) 1289 | maxU = u; 1290 | 1291 | var v = Math.round(dotProduct(texInfo.t, vertex) + texInfo.tShift); 1292 | if (v < minV) 1293 | minV = v; 1294 | if (v > maxV) 1295 | maxV = v; 1296 | } 1297 | 1298 | var texMinU = Math.floor(minU / 16.0); 1299 | var texMinV = Math.floor(minV / 16.0); 1300 | var texMaxU = Math.ceil(maxU / 16.0); 1301 | var texMaxV = Math.ceil(maxV / 16.0); 1302 | 1303 | var width = Math.floor((texMaxU - texMinU) + 1); 1304 | var height = Math.floor((texMaxV - texMinV) + 1); 1305 | 1306 | /* *********** end QRAD ********* */ 1307 | 1308 | /* ********** http://www.gamedev.net/community/forums/topic.asp?topic_id=538713 (last refresh: 20.02.2010) ********** */ 1309 | 1310 | var midPolyU = (minU + maxU) / 2.0; 1311 | var midPolyV = (minV + maxV) / 2.0; 1312 | var midTexU = width / 2.0; 1313 | var midTexV = height / 2.0; 1314 | 1315 | var coord; 1316 | 1317 | for (var j = 0; j < face.edges; ++j) 1318 | { 1319 | var edgeIndex = this.surfEdges[face.firstEdge + j]; 1320 | var vertex; 1321 | if (edgeIndex >= 0) 1322 | vertex = this.vertices[this.edges[edgeIndex].vertices[0]]; 1323 | else 1324 | vertex = this.vertices[this.edges[-edgeIndex].vertices[1]]; 1325 | 1326 | var u = Math.round(dotProduct(texInfo.s, vertex) + texInfo.sShift); 1327 | var v = Math.round(dotProduct(texInfo.t, vertex) + texInfo.tShift); 1328 | 1329 | var lightMapU = midTexU + (u - midPolyU) / 16.0; 1330 | var lightMapV = midTexV + (v - midPolyV) / 16.0; 1331 | 1332 | coord = { 1333 | s : lightMapU / width, 1334 | t : lightMapV / height 1335 | } 1336 | 1337 | faceCoords.push(coord); 1338 | } 1339 | 1340 | /* ********** end http://www.gamedev.net/community/forums/topic.asp?topic_id=538713 ********** */ 1341 | 1342 | var pixels = new Uint8Array(src.buffer, this.header.lumps[LUMP_LIGHTING].offset + face.lightmapOffset, width * height * 3) 1343 | 1344 | var texture = pixelsToTexture(pixels, width, height, 3, function(texture, image) 1345 | { 1346 | gl.bindTexture(gl.TEXTURE_2D, texture); 1347 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 1348 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR_MIPMAP_LINEAR); 1349 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 1350 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 1351 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); 1352 | gl.generateMipmap(gl.TEXTURE_2D); 1353 | gl.bindTexture(gl.TEXTURE_2D, null); 1354 | //$('body').append('Texture (' + image.width + 'x' + image.height + ')').append(image); 1355 | }); 1356 | 1357 | this.lightmapLookup[i] = texture; 1358 | this.lightmapCoordinates.push(faceCoords); 1359 | 1360 | loadedLightmaps++; 1361 | loadedData += width * height * 3; 1362 | } 1363 | 1364 | console.log('Loaded ' + loadedLightmaps + ' lightmaps, lightmapdatadiff: ' + (loadedData - this.header.lumps[LUMP_LIGHTING].length) + ' Bytes '); 1365 | } 1366 | 1367 | /** 1368 | * Unloads all allocated data of the Bsp class. This should be mostly OpenGL releated stuff. 1369 | */ 1370 | Bsp.prototype.unload = function() 1371 | { 1372 | // Free lightmap lookup 1373 | for(var i = 0; i < this.lightmapLookup.length; i++) 1374 | gl.deleteTexture(this.lightmapLookup[i]); 1375 | 1376 | // Free texture lookup 1377 | for(var i = 0; i < this.textureLookup.length; i++) 1378 | gl.deleteTexture(this.textureLookup[i]); 1379 | 1380 | gl.deleteTexture(this.whiteTexture); 1381 | 1382 | gl.deleteBuffer(this.vertexBuffer); 1383 | gl.deleteBuffer(this.texCoordBuffer); 1384 | gl.deleteBuffer(this.lightmapCoordBuffer); 1385 | gl.deleteBuffer(this.normalBuffer); 1386 | }; 1387 | 1388 | // create a global instance of the Bsp class. 1389 | var bsp = new Bsp(); 1390 | -------------------------------------------------------------------------------- /js/bspdef.js: -------------------------------------------------------------------------------- 1 | /* 2 | * bspdef.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | /** 25 | * Contains the standard BSP v30 file definitions. 26 | * For closer information visit my hlbsp project: 27 | * http://hlbsp.sourceforge.net/index.php?content=bspdef 28 | */ 29 | 30 | var MAX_MAP_HULLS = 4; 31 | 32 | var MAX_MAP_MODELS = 400; 33 | var MAX_MAP_BRUSHES = 4096; 34 | var MAX_MAP_ENTITIES = 1024; 35 | var MAX_MAP_ENTSTRING = (128*1024); 36 | 37 | var MAX_MAP_PLANES = 32767; 38 | var MAX_MAP_NODES = 32767; // because negative shorts are leaves 39 | var MAX_MAP_CLIPNODES = 32767; // 40 | var MAX_MAP_LEAFS = 8192; 41 | var MAX_MAP_VERTS = 65535; 42 | var MAX_MAP_FACES = 65535; 43 | var MAX_MAP_MARKSURFACES = 65535; 44 | var MAX_MAP_TEXINFO = 8192; 45 | var MAX_MAP_EDGES = 256000; 46 | var MAX_MAP_SURFEDGES = 512000; 47 | var MAX_MAP_TEXTURES = 512; 48 | var MAX_MAP_MIPTEX = 0x200000; 49 | var MAX_MAP_LIGHTING = 0x200000; 50 | var MAX_MAP_VISIBILITY = 0x200000; 51 | 52 | var MAX_MAP_PORTALS = 65536; 53 | 54 | var MAX_KEY = 32; 55 | var MAX_VALUE = 1024; 56 | 57 | // BSP-30 files contain these lumps 58 | var LUMP_ENTITIES = 0; 59 | var LUMP_PLANES = 1; 60 | var LUMP_TEXTURES = 2; 61 | var LUMP_VERTICES = 3; 62 | var LUMP_VISIBILITY = 4; 63 | var LUMP_NODES = 5; 64 | var LUMP_TEXINFO = 6; 65 | var LUMP_FACES = 7; 66 | var LUMP_LIGHTING = 8; 67 | var LUMP_CLIPNODES = 9; 68 | var LUMP_LEAVES = 10; 69 | var LUMP_MARKSURFACES = 11; 70 | var LUMP_EDGES = 12; 71 | var LUMP_SURFEDGES = 13; 72 | var LUMP_MODELS = 14; 73 | var HEADER_LUMPS = 15; 74 | 75 | // Leaf content values 76 | var CONTENTS_EMPTY = -1; 77 | var CONTENTS_SOLID = -2; 78 | var CONTENTS_WATER = -3; 79 | var CONTENTS_SLIME = -4; 80 | var CONTENTS_LAVA = -5; 81 | var CONTENTS_SKY = -6; 82 | var CONTENTS_ORIGIN = -7; 83 | var CONTENTS_CLIP = -8; 84 | var CONTENTS_CURRENT_0 = -9; 85 | var CONTENTS_CURRENT_90 = -10; 86 | var CONTENTS_CURRENT_180 = -11; 87 | var CONTENTS_CURRENT_270 = -12; 88 | var CONTENTS_CURRENT_UP = -13; 89 | var CONTENTS_CURRENT_DOWN = -14; 90 | var CONTENTS_TRANSLUCENT = -15; 91 | 92 | //Plane types 93 | var PLANE_X = 0; // Plane is perpendicular to given axis 94 | var PLANE_Y = 1; 95 | var PLANE_Z = 2; 96 | var PLANE_ANYX = 3; // Non-axial plane is snapped to the nearest 97 | var PLANE_ANYY = 4; 98 | var PLANE_ANYZ = 5; 99 | 100 | // Render modes 101 | var RENDER_MODE_NORMAL = 0; 102 | var RENDER_MODE_COLOR = 1; 103 | var RENDER_MODE_TEXTURE = 2; 104 | var RENDER_MODE_GLOW = 3; 105 | var RENDER_MODE_SOLID = 4; 106 | var RENDER_MODE_ADDITIVE = 5; 107 | 108 | /* 109 | typedef struct _VECTOR3D 110 | { 111 | float x, y, z; 112 | } VECTOR3D; 113 | */ 114 | // @see mathlib.js Vector3D 115 | 116 | // Describes a lump in the BSP file 117 | /* 118 | typedef struct _BSPLUMP 119 | { 120 | int32_t nOffset; 121 | int32_t nLength; 122 | } BSPLUMP; 123 | */ 124 | function BspLump() 125 | { 126 | var offset; // File offset to data 127 | var length; // Length of data 128 | }; 129 | 130 | // The BSP file header 131 | /* 132 | typedef struct _BSPHEADER 133 | { 134 | int32_t nVersion; 135 | BSPLUMP lump[HEADER_LUMPS]; 136 | } BSPHEADER; 137 | */ 138 | function BspHeader() 139 | { 140 | var version; // Version number, must be 30 for a valid HL BSP file 141 | var lumps; // Stores the directory of lumps as array of BspLump (HEADER_LUMPS elements) 142 | }; 143 | 144 | // Describes a node of the BSP Tree 145 | /* 146 | typedef struct _BSPNODE 147 | { 148 | uint32_t iPlane; 149 | int16_t iChildren[2]; 150 | int16_t nMins[3], nMaxs[3]; 151 | uint16_t iFirstFace, nFaces; 152 | } BSPNODE; 153 | */ 154 | function BspNode() 155 | { 156 | var plane; // Index into pPlanes lump 157 | var children; // If > 0, then indices into Nodes otherwise bitwise inverse indices into Leafs 158 | var mins; // Bounding box 159 | var maxs; 160 | var firstFace; // Index and count into BSPFACES array 161 | var faces; 162 | }; 163 | var SIZE_OF_BSPNODE = 24; 164 | 165 | // Leafs lump contains leaf structures 166 | /* 167 | typedef struct _BSPLEAF 168 | { 169 | int32_t nContents; 170 | int32_t nVisOffset; 171 | int16_t nMins[3], nMaxs[3]; 172 | uint16_t iFirstMarkSurface, nMarkSurfaces; 173 | uint8_t nAmbientLevels[4]; 174 | } BSPLEAF; 175 | */ 176 | function BspLeaf() 177 | { 178 | var content; // Contents enumeration, see vars 179 | var visOffset; // Offset into the compressed visibility lump 180 | var mins; // Bounding box 181 | var maxs; 182 | var firstMarkSurface; // Index and count into BSPMARKSURFACE array 183 | var markSurfaces 184 | var ambientLevels; // Ambient sound levels 185 | }; 186 | var SIZE_OF_BSPLEAF = 28; 187 | 188 | // Leaves index into marksurfaces, which index into pFaces 189 | /* 190 | typedef uint16_t BSPMARKSURFACE; 191 | */ 192 | var SIZE_OF_BSPMARKSURFACE = 2; 193 | 194 | // Planes lump contains plane structures 195 | /* 196 | typedef struct _BSPPLANE 197 | { 198 | VECTOR3D vNormal; 199 | float fDist; 200 | int32_t nType; 201 | } BSPPLANE; 202 | */ 203 | function BspPlane() 204 | { 205 | var normal; // The planes normal vector 206 | var dist; // Plane equation is: vNormal * X = fDist 207 | var type; // Plane type, see vars 208 | }; 209 | var SIZE_OF_BSPPLANE = 20; 210 | 211 | // Vertex lump is an array of float triples (VECTOR3D) 212 | /* 213 | typedef VECTOR3D BSPVERTEX; 214 | */ 215 | var SIZE_OF_BSPVERTEX = 12; 216 | 217 | // Edge struct contains the begining and end vertex for each edge 218 | /* 219 | typedef struct _BSPEDGE 220 | { 221 | uint16_t iVertex[2]; 222 | }; 223 | */ 224 | function BspEdge() 225 | { 226 | var vertices; // Indices into vertex array 227 | } 228 | var SIZE_OF_BSPEDGE = 4; 229 | 230 | // Faces are equal to the polygons that make up the world 231 | /* 232 | typedef struct _BSPFACE 233 | { 234 | uint16_t iPlane; // Index of the plane the face is parallel to 235 | uint16_t nPlaneSide; // Set if different normals orientation 236 | uint32_t iFirstEdge; // Index of the first edge (in the surfedge array) 237 | uint16_t nEdges; // Number of consecutive surfedges 238 | uint16_t iTextureInfo; // Index of the texture info structure 239 | uint8_t nStyles[4]; // Specify lighting styles 240 | // nStyles[0] // type of lighting, for the face 241 | // nStyles[1] // from 0xFF (dark) to 0 (bright) 242 | // nStyles[2], nStyles[3] // two additional light models 243 | uint32_t nLightmapOffset; // Offsets into the raw lightmap data 244 | }; 245 | */ 246 | function BspFace() 247 | { 248 | var plane; // Index of the plane the face is parallel to 249 | var planeSide; // Set if different normals orientation 250 | var firstEdge; // Index of the first edge (in the surfedge array) 251 | var edges; // Number of consecutive surfedges 252 | var textureInfo; // Index of the texture info structure 253 | var styles; // Specify lighting styles 254 | // styles[0] // type of lighting, for the face 255 | // styles[1] // from 0xFF (dark) to 0 (bright) 256 | // styles[2], styles[3] // two additional light models 257 | var lightmapOffset; // Offsets into the raw lightmap data 258 | } 259 | var SIZE_OF_BSPFACE = 20; 260 | 261 | 262 | // Surfedges lump is an array of signed int indices into the edge lump, where a negative index indicates 263 | // using the referenced edge in the opposite direction. Faces index into surfEdges, which index 264 | // into edges, which finally index into vertices. 265 | /* 266 | typedef int32_t BSPSURFEDGE; 267 | */ 268 | var SIZE_OF_BSPSURFEDGE = 4; 269 | 270 | // Textures lump begins with a header, followed by offsets to BSPMIPTEX structures, then BSPMIPTEX structures 271 | /* 272 | typedef struct _BSPTEXTUREHEADER 273 | { 274 | uint32_t nMipTextures; 275 | }; 276 | */ 277 | // 32-bit offsets (within texture lump) to (nMipTextures) BSPMIPTEX structures 278 | /* 279 | typedef int32_t BSPMIPTEXOFFSET; 280 | */ 281 | var SIZE_OF_BSPMIPTEXOFFSET = 4; 282 | function BspTextureHeader() 283 | { 284 | var textures; // Number of BSPMIPTEX structures 285 | var offsets; // Array of offsets to the textures 286 | } 287 | 288 | // BSPMIPTEX structures which defines a texture 289 | var MAXTEXTURENAME = 16 290 | var MIPLEVELS = 4 291 | /* 292 | typedef struct _BSPMIPTEX 293 | { 294 | char szName[MAXTEXTURENAME]; 295 | uint32_t nWidth, nHeight; 296 | uint32_t nOffsets[MIPLEVELS]; 297 | }; 298 | */ 299 | function BspMipTexture() 300 | { 301 | var name; // Name of texture, for reference from external WAD file 302 | var width; // Extends of the texture 303 | var height; 304 | var offsets; // Offsets to MIPLEVELS texture mipmaps, if 0 texture data is stored in an external WAD file 305 | } 306 | 307 | // Texinfo lump contains information about how textures are applied to surfaces 308 | /* 309 | typedef struct _BSPTEXTUREINFO 310 | { 311 | VECTOR3D vS; 312 | float fSShift; 313 | VECTOR3D vT; 314 | float fTShift; 315 | uint32_t iMiptex; 316 | uint32_t nFlags; 317 | }; 318 | */ 319 | function BspTextureInfo() 320 | { 321 | var s; // 1st row of texture matrix 322 | var sShift; // Texture shift in s direction 323 | var t; // 2nd row of texture matrix - multiply 1st and 2nd by vertex to get texture coordinates 324 | var tShift; // Texture shift in t direction 325 | var mipTexture; // Index into mipTextures array 326 | var flags; // Texture flags, seems to always be 0 327 | } 328 | var SIZE_OF_BSPTEXTUREINFO = 40; 329 | 330 | // Smaller bsp models inside the world. Mostly brush entities. 331 | /* 332 | typedef struct _BSPMODEL 333 | { 334 | float nMins[3], nMaxs[3]; 335 | VECTOR3D vOrigin; 336 | int32_t iHeadNodes[MAX_MAP_HULLS]; 337 | int32_t nVisLeafs; 338 | int32_t iFirstFace, nFaces; 339 | }; 340 | */ 341 | function BspModel() 342 | { 343 | var mins; // Defines bounding box 344 | var maxs; 345 | var origin; // Coordinates to move the coordinate system before drawing the model 346 | var headNodes; // Indexes into nodes (first into world nodes, remaining into clip nodes) 347 | var visLeafs; // No idea 348 | var firstFace; // Index and count into face array 349 | var faces; 350 | } 351 | var SIZE_OF_BSPMODEL = 64; 352 | 353 | // Clip nodes are used for collision detection and make up the clipping hull. 354 | /* 355 | typedef struct _BSPCLIPNODE 356 | { 357 | int32_t iPlane; 358 | int16_t iChildren[2]; 359 | }; 360 | */ 361 | function BspClipNode() 362 | { 363 | var plane; // Index into planes 364 | var children; // negative numbers are contents behind and in front of the plane 365 | } 366 | var SIZE_OF_BSPCLIPNODE = 8; -------------------------------------------------------------------------------- /js/camera.js: -------------------------------------------------------------------------------- 1 | /* 2 | * camera.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | /** 25 | * Camera class. 26 | * Responsible for maintaining the current camera position and view as well as setting the appropriate matrixes. 27 | */ 28 | function Camera() 29 | { 30 | /** view */ 31 | this.pitch = 0; 32 | this.yaw = 0; 33 | 34 | /** position */ 35 | this.pos = new Vector3D(); 36 | this.pos.x = 0; 37 | this.pos.y = 0; 38 | this.pos.z = 0; 39 | 40 | /** Look sensitivity. Applies to mouse movement */ 41 | this.lookSens = 0.25; 42 | 43 | /** Move sensitivity. Applies to keyboard movement */ 44 | this.moveSens = 500; 45 | 46 | /** The x position of the mouse of the last frame */ 47 | this.lastX; 48 | 49 | /** The x position of the mouse of the last frame */ 50 | this.lastY; 51 | 52 | /** When set to true, the mouse is tracked by the camera */ 53 | this.captureMouse = false; 54 | } 55 | 56 | /** 57 | * Call this method to begin capturing mouse movements. 58 | */ 59 | Camera.prototype.beginCapture = function() 60 | { 61 | this.captureMouse = true; 62 | this.lastX = mouse.x; 63 | this.lastY = mouse.y; 64 | console.log('begin capture'); 65 | } 66 | 67 | /** 68 | * Call this method to end capturing mouse movements. 69 | */ 70 | Camera.prototype.endCapture = function() 71 | { 72 | if(this.captureMouse) 73 | { 74 | this.captureMouse = false; 75 | console.log('end capture'); 76 | } 77 | } 78 | 79 | /** 80 | * Updates the modelview matrix according to user input and time. 81 | * 82 | * @param interval The time passed since the last frame in seconds. 83 | */ 84 | Camera.prototype.update = function(interval) 85 | { 86 | if(this.captureMouse) 87 | { 88 | var x = mouse.x; 89 | var y = mouse.y; 90 | 91 | // Update rotation based on mouse input 92 | this.yaw += this.lookSens * (this.lastX - x); 93 | 94 | // Correct z angle to interval [0;360] 95 | if(this.yaw >= 360.0) 96 | this.yaw -= 360.0; 97 | 98 | if(this.yaw < 0.0) 99 | this.yaw += 360.0; 100 | 101 | // Update up down view 102 | this.pitch += this.lookSens * (this.lastY - y); 103 | 104 | // Correct x angle to interval [-90;90] 105 | if (this.pitch < -90.0) 106 | this.pitch = -90.0; 107 | 108 | if (this.pitch > 90.0) 109 | this.pitch = 90.0; 110 | 111 | // Reset track point 112 | this.lastX = x; 113 | this.lastY = y; 114 | } 115 | 116 | var moveFactor = this.moveSens * interval; 117 | var oldPos = new Vector3D(this.pos); 118 | var newPos = new Vector3D(this.pos); 119 | 120 | if (keys[16] || keys[32]) // SHIFT or SPACE - UP 121 | { 122 | newPos.z += moveFactor; 123 | } 124 | 125 | if (keys[17]) // CRTL - DOWN 126 | { 127 | newPos.z -= moveFactor; 128 | } 129 | 130 | // If strafing and moving reduce speed to keep total move per frame constant 131 | var strafing = (keys[87] || keys[83]) && (keys[65] || keys[68]); 132 | 133 | if(strafing) 134 | moveFactor = Math.sqrt((moveFactor * moveFactor) / 2.0); 135 | 136 | if (keys[87]) // W - FORWARD 137 | { 138 | newPos.x += Math.cos(degToRad(this.yaw)) * moveFactor; 139 | newPos.y += Math.sin(degToRad(this.yaw)) * moveFactor; 140 | } 141 | 142 | if (keys[83]) // S - BACKWARD 143 | { 144 | newPos.x -= Math.cos(degToRad(this.yaw)) * moveFactor; 145 | newPos.y -= Math.sin(degToRad(this.yaw)) * moveFactor; 146 | } 147 | 148 | if (keys[65]) // A - LEFT 149 | { 150 | newPos.x += Math.cos(degToRad(this.yaw + 90.0)) * moveFactor; 151 | newPos.y += Math.sin(degToRad(this.yaw + 90.0)) * moveFactor; 152 | } 153 | 154 | if (keys[68]) // D - RIGHT 155 | { 156 | newPos.x += Math.cos(degToRad(this.yaw - 90.0)) * moveFactor; 157 | newPos.y += Math.sin(degToRad(this.yaw - 90.0)) * moveFactor; 158 | } 159 | 160 | //oldPos = {x: 0, y: 0, z: 0}; 161 | //newPos = {x: 1000, y: 0, z: 0}; 162 | 163 | if(bsp.loaded) 164 | this.pos = playerMove(oldPos, newPos); 165 | else 166 | this.pos = newPos; 167 | 168 | //console.log('camera.update() pos: ' + this.pos.x + 'x ' + this.pos.y + 'y ' + this.pos.z + 'z pitch: ' + this.pitch + ' yaw: ' + this.yaw); 169 | } 170 | 171 | /** 172 | * Applies the current camera states to OpenGL (modelview matrix). 173 | */ 174 | Camera.prototype.look = function() 175 | { 176 | // In BSP v30 the z axis points up and we start looking parallel to x axis. 177 | 178 | // Look Up/Down 179 | modelviewMatrix.rotate(-this.pitch - 90.0, 1, 0, 0); 180 | 181 | // Look Left/Right 182 | modelviewMatrix.rotate(-this.yaw + 90.0, 0, 0, 1); 183 | 184 | // Move 185 | modelviewMatrix.translate(-this.pos.x, -this.pos.y, -this.pos.z); 186 | 187 | // Upload to shader 188 | modelviewMatrix.setUniform(gl, modelviewMatrixLocation, false); 189 | } 190 | 191 | /** Create a global instance of Camera */ 192 | var camera = new Camera(); -------------------------------------------------------------------------------- /js/entity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * entity.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | /** 25 | * Represents an entity in the bsp file. 26 | */ 27 | function Entity(entityString) 28 | { 29 | var properties; 30 | 31 | this.parseProperties(entityString); 32 | } 33 | 34 | /** 35 | * Parses the string representation of an entity into an associative array. 36 | * @see Bsp.loadEntities(). 37 | */ 38 | Entity.prototype.parseProperties = function(entityString) 39 | { 40 | this.properties = []; 41 | 42 | var end = -1; 43 | while(true) 44 | { 45 | var begin = entityString.indexOf('"', end + 1); 46 | if(begin == -1) 47 | break; 48 | end = entityString.indexOf('"', begin + 1); 49 | 50 | var key = entityString.substring(begin + 1, end); 51 | 52 | begin = entityString.indexOf('"', end + 1); 53 | end = entityString.indexOf('"', begin + 1); 54 | 55 | var value = entityString.substring(begin + 1, end); 56 | 57 | this.properties[key] = value; 58 | } 59 | } -------------------------------------------------------------------------------- /js/events.js: -------------------------------------------------------------------------------- 1 | /* 2 | * events.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | // 25 | // bsp file 26 | // 27 | 28 | /** 29 | * Handles the loading of a bsp file. 30 | * Displays a loading bar and starts the parsing process by calling Bsp.loadBSP(). 31 | * 32 | * @param file A file obtained by a drag/drop event or file selection event. 33 | */ 34 | function handleBspFileLoading(file) 35 | { 36 | // Remove previously loaded date and elements 37 | $('#wadmissing').slideUp(300); 38 | $('#wadmissing > ul > li').remove(); 39 | if(bsp.loaded) 40 | bsp.unload(); 41 | 42 | var reader = new FileReader(); 43 | 44 | reader.onloadstart = function() 45 | { 46 | $('#bsploading p:first-child').html('Loading ' + file.name + ' ...'); 47 | $('#bsploading').slideDown(300); 48 | }; 49 | 50 | reader.onprogress = function(event) 51 | { 52 | if(event.lengthComputable) 53 | { 54 | var value = Math.round(event.loaded / event.total * 100); 55 | $('#bsploading progress')[0].value = value; 56 | $('#bsploading p:last-child').html('Reading ... (' + value + '%)'); 57 | } 58 | }; 59 | 60 | reader.onload = function(event) 61 | { 62 | $('#bsploading progress')[0].value = 100; 63 | $('#bsploading p:last-child').html('Parsing bsp file ...'); 64 | log('Loading bsp file ' + file.name); 65 | if(bsp.loadBSP(event.target.result)) 66 | { 67 | $('#bsploading p:last-child').html('Successfully loaded bsp file'); 68 | setTimeout("$('#bsploading').slideUp(300)", 2000); 69 | } 70 | else 71 | $('#bsploading p:last-child').html('Error parsing bsp file'); 72 | }; 73 | 74 | reader.onerror = function(event) 75 | { 76 | $('#bsploading').slideDown(300); 77 | var error; 78 | for(var member in event.target.error) 79 | if(event.target.error[member] == event.target.error.code) 80 | error = member; 81 | $('#bsploading p:first-child').html('Error reading ' + file.name + ': ' + event.target.error.code + ' - ' + error + ''); 82 | console.log(event.target.error); 83 | setTimeout("$('#bsploading').slideUp(300)", 3500); 84 | }; 85 | 86 | reader.readAsArrayBuffer(file); 87 | } 88 | 89 | /** 90 | * Handles the selection of a bsp file. 91 | * Calls handleBspFileLoading(). 92 | */ 93 | function handleBspFileSelection(event) 94 | { 95 | var file = event.target.files[0]; 96 | handleBspFileLoading(file); 97 | } 98 | 99 | /** 100 | * Handles the drag&drop of a bsp file. 101 | * Calls handleBspFileLoading(). 102 | */ 103 | function handleBspFileDrop(event) 104 | { 105 | event.stopPropagation(); 106 | event.preventDefault(); 107 | 108 | var file = event.dataTransfer.files[0]; // FileList object. 109 | handleBspFileLoading(file); 110 | } 111 | 112 | /** 113 | * Displays a small information window when the user drags a bsp file over the control. 114 | */ 115 | function handleBspFileDragOver(event) 116 | { 117 | event.stopPropagation(); 118 | event.preventDefault(); 119 | event.dataTransfer.dropEffect = 'drop here to load'; 120 | } 121 | 122 | // 123 | // wad file 124 | // 125 | 126 | /** 127 | * Handles the loading of a wad file. 128 | * Displays a loading bar and starts the parsing process by calling Wad.open(). 129 | * 130 | * @param files A FileList object obtained by a drag/drop event or file selection event. 131 | */ 132 | function handleWadFileLoading(files) 133 | { 134 | for (var i = 0, file; file = files[i]; i++) 135 | { 136 | var name = file.name; 137 | 138 | // Create a new control window for this loading process 139 | var control = $('
  • ' + 140 | '

    ' + 141 | '' + 142 | '

    ' + 143 | '
  • ').hide(); 144 | 145 | $('#wads').after(control); 146 | 147 | 148 | 149 | 150 | var reader = new FileReader(); 151 | 152 | reader.onloadstart = function() 153 | { 154 | control.find('p:first-child').html('Loading ' + name + ' ...') 155 | control.slideDown(300); 156 | //$('.wadloading[name="' + name + '"] p:first-child').html('Loading ' + name + ' ...').slideDown(300); 157 | }; 158 | 159 | reader.onprogress = function(event) 160 | { 161 | if(event.lengthComputable) 162 | { 163 | var value = Math.round(event.loaded / event.total * 100); 164 | 165 | control.find('progress')[0].value = value; 166 | //$('.wadloading[name="' + name + '"] progress')[0].value = value; 167 | control.find('p:last-child').html('Reading ... (' + value + '%)'); 168 | //$('.wadloading[name="' + name + '"] p:last-child').html('Loading ... (' + value + '%)'); 169 | } 170 | }; 171 | 172 | reader.onload = function(event) 173 | { 174 | control.find('progress')[0].value = 100; 175 | //$('.wadloading[name="' + name + '"] progress')[0].value = 100; 176 | control.find('p:last-child').html('Parsing wad file ...'); 177 | //$('.wadloading[name="' + name + '"] p:last-child').html('Parsing wad file ...'); 178 | 179 | log('Loading wad file ' + name); 180 | var wad = new Wad(); 181 | wad.name = name; 182 | if(wad.open(event.target.result)) 183 | { 184 | loadedWads.push(wad); 185 | 186 | control.find('p:last-child').html('Successfully loaded wad file.'); 187 | //$('.wadloading[name="' + name + ' p:last-child').html('Successfully loaded wad file'); 188 | setTimeout("$('.wadloading').filter(function() { if($(this).attr('data-name') == '" + name + "') return true; else return false; }).slideUp(300)", 2000); 189 | 190 | // remove missing wad from bsp and from control panel 191 | if(bsp.loaded) 192 | { 193 | for(var j = 0; j < bsp.missingWads.length; j++) 194 | { 195 | if(bsp.missingWads[j] == name) 196 | { 197 | bsp.missingWads.splice(j, 1); 198 | break; 199 | } 200 | } 201 | 202 | $('#wadmissing > ul > li').filter(function() { if($(this).attr('data-name') == name) return true; else return false; }).remove(); 203 | 204 | if($('#wadmissing > ul > li').size() == 0) 205 | setTimeout("$('#wadmissing').slideUp(300);", 2000); 206 | } 207 | 208 | // add this wad to the loaded ones in the control panel 209 | $('#wads > ul').append('
  • ' + name + '
  • '); 210 | 211 | // try to load missing textures of the bsp file 212 | if(bsp.loaded) 213 | bsp.loadMissingTextures(); 214 | } 215 | else 216 | control.find('p:last-child').html('Error loading wad file'); 217 | }; 218 | 219 | reader.onerror = function(event) 220 | { 221 | control.slideDown(300); 222 | var error; 223 | for(var member in event.target.error) 224 | if(event.target.error[member] == event.target.error.code) 225 | error = member; 226 | control.find('p:first-child').html('Error reading ' + file.name + ': ' + event.target.error.code + ' - ' + error + ''); 227 | console.log(event.target.error); 228 | setTimeout("$('.wadloading').filter(function() { if($(this).attr('data-name') == '" + name + "') return true; else return false; }).slideUp(300)", 3500); 229 | }; 230 | 231 | reader.readAsArrayBuffer(file); 232 | } 233 | } 234 | 235 | /** 236 | * Handles the selection of a wad file. 237 | * Calls handleWadFileLoading(). 238 | */ 239 | function handleWadFileSelection(event) 240 | { 241 | var files = event.target.files; 242 | handleWadFileLoading(files); 243 | } 244 | 245 | /** 246 | * Handles the drag&drop of a wad file. 247 | * Calls handleWadFileLoading(). 248 | */ 249 | function handleWadFileDrop(event) 250 | { 251 | event.stopPropagation(); 252 | event.preventDefault(); 253 | 254 | var files = event.dataTransfer.files; // FileList object. 255 | 256 | handleWadFileLoading(files); 257 | } 258 | 259 | /** 260 | * Displays a small information window when the user drags a bsp file over the control. 261 | */ 262 | function handleWadDragOver(event) 263 | { 264 | event.stopPropagation(); 265 | event.preventDefault(); 266 | event.dataTransfer.dropEffect = 'drop here to load'; 267 | } 268 | 269 | // 270 | // SOME GLOBAL EVENT VARS 271 | // 272 | 273 | /** If set to true, polyons will be rendered wireframe */ 274 | var polygonMode = false; 275 | 276 | /** If set to true, the coordinate system is shown */ 277 | var showCoordSystem = false; 278 | 279 | /** If set to true, textures (not lightmaps) will be rendered */ 280 | var renderTextures = true; 281 | 282 | /** If set to true, lightmaps will be rendered */ 283 | var renderLightmaps = true; 284 | 285 | /** 286 | * This function is called when the document has finished loading and 287 | * binds all event handlers to their corresponding objects. 288 | */ 289 | function setEventHandlers() 290 | { 291 | // Event handler for updating the current key states on key press. 292 | document.onkeydown = function(event) 293 | { 294 | keys[event.keyCode] = true; 295 | 296 | switch(event.keyCode) 297 | { 298 | case 67: // C 299 | showCoordSystem = !showCoordSystem; 300 | console.log('Coordsystem ' + (showCoordSystem ? 'enabled' : 'disabled')); 301 | break; 302 | case 76: // L 303 | renderLightmaps = !renderLightmaps; 304 | gl.uniform1i(lightmapsEnabledLocation, renderLightmaps ? 1 : 0); 305 | console.log((renderLightmaps ? 'Enabled' : 'Disabled') + ' rendering lightmaps'); 306 | break; 307 | case 80: // P 308 | polygonMode = !polygonMode; 309 | console.log('Polygonmode: ' + (polygonMode ? 'Wireframe' : 'Fill')); 310 | break; 311 | case 84: // T 312 | renderTextures = !renderTextures; 313 | gl.uniform1i(texturesEnabledLocation, renderTextures ? 1 : 0); 314 | console.log((renderTextures ? 'Enabled' : 'Disabled') + ' rendering textures'); 315 | break; 316 | } 317 | }; 318 | 319 | // Event handler for updating the current key states on key release. 320 | document.onkeyup = function(event) 321 | { 322 | keys[event.keyCode] = false; 323 | }; 324 | 325 | // Event handler for updating the current mouse position in camera. 326 | document.onmousemove = function(event) 327 | { 328 | mouse.x = event.pageX; 329 | mouse.y = event.pageY; 330 | }; 331 | 332 | // Event handler for mouse down to enable mouse tracking. 333 | canvas.onmousedown = function() 334 | { 335 | camera.beginCapture(); 336 | } 337 | 338 | // Event handler for mouse up to stop mouse tracking. 339 | document.onmouseup = function(event) 340 | { 341 | camera.endCapture(); 342 | } 343 | 344 | document.getElementById('bspfile').addEventListener('change', handleBspFileSelection, false); 345 | document.getElementById('bspselection').addEventListener('dragover', handleBspFileDragOver, false); 346 | document.getElementById('bspselection').addEventListener('drop', handleBspFileDrop, false); 347 | document.getElementById('wadfiles').addEventListener('change', handleWadFileSelection, false); 348 | document.getElementById('wads').addEventListener('dragover', handleWadDragOver, false); 349 | document.getElementById('wads').addEventListener('drop', handleWadFileDrop, false); 350 | } 351 | 352 | // register events when the document has finished loading 353 | window.addEventListener('load', setEventHandlers, false); -------------------------------------------------------------------------------- /js/jDataView.js: -------------------------------------------------------------------------------- 1 | // 2 | // jDataView by Vjeux - Jan 2010 3 | // 4 | // A unique way to read a binary file in the browser 5 | // http://github.com/vjeux/jDataView 6 | // http://blog.vjeux.com/ 7 | // 8 | 9 | (function (global) { 10 | 11 | var compatibility = { 12 | ArrayBuffer: typeof ArrayBuffer !== 'undefined', 13 | DataView: typeof DataView !== 'undefined' && 'getFloat64' in DataView.prototype, 14 | NodeBuffer: typeof Buffer !== 'undefined', 15 | // 0.6.0 -> readInt8LE(offset) 16 | NodeBufferFull: typeof Buffer !== 'undefined' && 'readInt8LE' in Buffer, 17 | // 0.5.0 -> readInt8(offset, endian) 18 | NodeBufferEndian: typeof Buffer !== 'undefined' && 'readInt8' in Buffer 19 | }; 20 | 21 | var jDataView = function (buffer, byteOffset, byteLength, littleEndian) { 22 | if (!(this instanceof arguments.callee)) { 23 | throw new Error("Constructor may not be called as a function"); 24 | } 25 | 26 | this.buffer = buffer; 27 | 28 | // Handle Type Errors 29 | if (!(compatibility.NodeBuffer && buffer instanceof Buffer) && 30 | !(compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) && 31 | typeof buffer !== 'string') { 32 | throw new TypeError('Type error'); 33 | } 34 | 35 | // Check parameters and existing functionnalities 36 | this._isArrayBuffer = compatibility.ArrayBuffer && buffer instanceof ArrayBuffer; 37 | this._isDataView = compatibility.DataView && this._isArrayBuffer; 38 | this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer; 39 | 40 | // Default Values 41 | this._littleEndian = littleEndian === undefined ? true : littleEndian; 42 | 43 | var bufferLength = this._isArrayBuffer ? buffer.byteLength : buffer.length; 44 | if (byteOffset === undefined) { 45 | byteOffset = 0; 46 | } 47 | this.byteOffset = byteOffset; 48 | 49 | if (byteLength === undefined) { 50 | byteLength = bufferLength - byteOffset; 51 | } 52 | this.byteLength = byteLength; 53 | 54 | if (!this._isDataView) { 55 | // Do additional checks to simulate DataView 56 | if (typeof byteOffset !== 'number') { 57 | throw new TypeError('Type error'); 58 | } 59 | if (typeof byteLength !== 'number') { 60 | throw new TypeError('Type error'); 61 | } 62 | if (typeof byteOffset < 0) { 63 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1'); 64 | } 65 | if (typeof byteLength < 0) { 66 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1'); 67 | } 68 | } 69 | 70 | // Instanciate 71 | if (this._isDataView) { 72 | this._view = new DataView(buffer, byteOffset, byteLength); 73 | this._start = 0; 74 | } 75 | this._start = byteOffset; 76 | if (byteOffset + byteLength > bufferLength) { 77 | throw new Error("INDEX_SIZE_ERR: DOM Exception 1"); 78 | } 79 | 80 | this._offset = 0; 81 | }; 82 | 83 | jDataView.createBuffer = function () { 84 | if (compatibility.NodeBuffer) { 85 | var buffer = new Buffer(arguments.length); 86 | for (var i = 0; i < arguments.length; ++i) { 87 | buffer[i] = arguments[i]; 88 | } 89 | return buffer; 90 | } 91 | if (compatibility.ArrayBuffer) { 92 | var buffer = new ArrayBuffer(arguments.length); 93 | var view = new Int8Array(buffer); 94 | for (var i = 0; i < arguments.length; ++i) { 95 | view[i] = arguments[i]; 96 | } 97 | return buffer; 98 | } 99 | 100 | return String.fromCharCode.apply(null, arguments); 101 | }; 102 | 103 | jDataView.prototype = { 104 | 105 | // Helpers 106 | 107 | getString: function (length, byteOffset) { 108 | var value; 109 | 110 | // Handle the lack of byteOffset 111 | if (byteOffset === undefined) { 112 | byteOffset = this._offset; 113 | } 114 | 115 | // Error Checking 116 | if (typeof byteOffset !== 'number') { 117 | throw new TypeError('Type error'); 118 | } 119 | if (length < 0 || byteOffset + length > this.byteLength) { 120 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1'); 121 | } 122 | 123 | if (this._isNodeBuffer) { 124 | value = this.buffer.toString('ascii', this._start + byteOffset, this._start + byteOffset + length); 125 | } 126 | else { 127 | value = ''; 128 | for (var i = 0; i < length; ++i) { 129 | var chr = this.getUint8(byteOffset + i); 130 | value += String.fromCharCode(chr > 127 ? 65533 : chr); 131 | } 132 | } 133 | 134 | this._offset = byteOffset + length; 135 | return value; 136 | }, 137 | 138 | getChar: function (byteOffset) { 139 | return this.getString(1, byteOffset); 140 | }, 141 | 142 | tell: function () { 143 | return this._offset; 144 | }, 145 | 146 | seek: function (byteOffset) { 147 | if (typeof byteOffset !== 'number') { 148 | throw new TypeError('Type error'); 149 | } 150 | if (byteOffset < 0 || byteOffset > this.byteLength) { 151 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1'); 152 | } 153 | 154 | return this._offset = byteOffset; 155 | }, 156 | 157 | // Compatibility functions on a String Buffer 158 | 159 | _endianness: function (byteOffset, pos, max, littleEndian) { 160 | return byteOffset + (littleEndian ? max - pos - 1 : pos); 161 | }, 162 | 163 | _getFloat64: function (byteOffset, littleEndian) { 164 | var b0 = this._getUint8(this._endianness(byteOffset, 0, 8, littleEndian)), 165 | b1 = this._getUint8(this._endianness(byteOffset, 1, 8, littleEndian)), 166 | b2 = this._getUint8(this._endianness(byteOffset, 2, 8, littleEndian)), 167 | b3 = this._getUint8(this._endianness(byteOffset, 3, 8, littleEndian)), 168 | b4 = this._getUint8(this._endianness(byteOffset, 4, 8, littleEndian)), 169 | b5 = this._getUint8(this._endianness(byteOffset, 5, 8, littleEndian)), 170 | b6 = this._getUint8(this._endianness(byteOffset, 6, 8, littleEndian)), 171 | b7 = this._getUint8(this._endianness(byteOffset, 7, 8, littleEndian)), 172 | 173 | sign = 1 - (2 * (b0 >> 7)), 174 | exponent = ((((b0 << 1) & 0xff) << 3) | (b1 >> 4)) - (Math.pow(2, 10) - 1), 175 | 176 | // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead 177 | mantissa = ((b1 & 0x0f) * Math.pow(2, 48)) + (b2 * Math.pow(2, 40)) + (b3 * Math.pow(2, 32)) + 178 | (b4 * Math.pow(2, 24)) + (b5 * Math.pow(2, 16)) + (b6 * Math.pow(2, 8)) + b7; 179 | 180 | if (exponent === 1024) { 181 | if (mantissa !== 0) { 182 | return NaN; 183 | } else { 184 | return sign * Infinity; 185 | } 186 | } 187 | 188 | if (exponent === -1023) { // Denormalized 189 | return sign * mantissa * Math.pow(2, -1022 - 52); 190 | } 191 | 192 | return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent); 193 | }, 194 | 195 | _getFloat32: function (byteOffset, littleEndian) { 196 | var b0 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)), 197 | b1 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)), 198 | b2 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)), 199 | b3 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian)), 200 | 201 | sign = 1 - (2 * (b0 >> 7)), 202 | exponent = (((b0 << 1) & 0xff) | (b1 >> 7)) - 127, 203 | mantissa = ((b1 & 0x7f) << 16) | (b2 << 8) | b3; 204 | 205 | if (exponent === 128) { 206 | if (mantissa !== 0) { 207 | return NaN; 208 | } else { 209 | return sign * Infinity; 210 | } 211 | } 212 | 213 | if (exponent === -127) { // Denormalized 214 | return sign * mantissa * Math.pow(2, -126 - 23); 215 | } 216 | 217 | return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent); 218 | }, 219 | 220 | _getInt32: function (byteOffset, littleEndian) { 221 | var b = this._getUint32(byteOffset, littleEndian); 222 | return b > Math.pow(2, 31) - 1 ? b - Math.pow(2, 32) : b; 223 | }, 224 | 225 | _getUint32: function (byteOffset, littleEndian) { 226 | var b3 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)), 227 | b2 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)), 228 | b1 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)), 229 | b0 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian)); 230 | 231 | return (b3 * Math.pow(2, 24)) + (b2 << 16) + (b1 << 8) + b0; 232 | }, 233 | 234 | _getInt16: function (byteOffset, littleEndian) { 235 | var b = this._getUint16(byteOffset, littleEndian); 236 | return b > Math.pow(2, 15) - 1 ? b - Math.pow(2, 16) : b; 237 | }, 238 | 239 | _getUint16: function (byteOffset, littleEndian) { 240 | var b1 = this._getUint8(this._endianness(byteOffset, 0, 2, littleEndian)), 241 | b0 = this._getUint8(this._endianness(byteOffset, 1, 2, littleEndian)); 242 | 243 | return (b1 << 8) + b0; 244 | }, 245 | 246 | _getInt8: function (byteOffset) { 247 | var b = this._getUint8(byteOffset); 248 | return b > Math.pow(2, 7) - 1 ? b - Math.pow(2, 8) : b; 249 | }, 250 | 251 | _getUint8: function (byteOffset) { 252 | if (this._isArrayBuffer) { 253 | return new Uint8Array(this.buffer, byteOffset, 1)[0]; 254 | } 255 | else if (this._isNodeBuffer) { 256 | return this.buffer[byteOffset]; 257 | } else { 258 | return this.buffer.charCodeAt(byteOffset) & 0xff; 259 | } 260 | } 261 | }; 262 | 263 | // Create wrappers 264 | 265 | var dataTypes = { 266 | 'Int8': 1, 267 | 'Int16': 2, 268 | 'Int32': 4, 269 | 'Uint8': 1, 270 | 'Uint16': 2, 271 | 'Uint32': 4, 272 | 'Float32': 4, 273 | 'Float64': 8 274 | }; 275 | var nodeNaming = { 276 | 'Int8': 'Int8', 277 | 'Int16': 'Int16', 278 | 'Int32': 'Int32', 279 | 'Uint8': 'UInt8', 280 | 'Uint16': 'UInt16', 281 | 'Uint32': 'UInt32', 282 | 'Float32': 'Float', 283 | 'Float64': 'Double' 284 | }; 285 | 286 | for (var type in dataTypes) { 287 | if (!dataTypes.hasOwnProperty(type)) { 288 | continue; 289 | } 290 | 291 | // Bind the variable type 292 | (function (type) { 293 | var size = dataTypes[type]; 294 | 295 | // Create the function 296 | jDataView.prototype['get' + type] = 297 | function (byteOffset, littleEndian) { 298 | var value; 299 | 300 | // Handle the lack of endianness 301 | if (littleEndian === undefined) { 302 | littleEndian = this._littleEndian; 303 | } 304 | 305 | // Handle the lack of byteOffset 306 | if (byteOffset === undefined) { 307 | byteOffset = this._offset; 308 | } 309 | 310 | // Dispatch on the good method 311 | if (this._isDataView) { 312 | // DataView: we use the direct method 313 | value = this._view['get' + type](byteOffset, littleEndian); 314 | } 315 | // ArrayBuffer: we use a typed array of size 1 if the alignment is good 316 | // ArrayBuffer does not support endianess flag (for size > 1) 317 | else if (this._isArrayBuffer && (this._start + byteOffset) % size === 0 && (size === 1 || littleEndian)) { 318 | value = new global[type + 'Array'](this.buffer, this._start + byteOffset, 1)[0]; 319 | } 320 | // NodeJS Buffer 321 | else if (this._isNodeBuffer && compatibility.NodeBufferFull) { 322 | if (littleEndian) { 323 | value = this.buffer['read' + nodeNaming[type] + 'LE'](this._start + byteOffset); 324 | } else { 325 | value = this.buffer['read' + nodeNaming[type] + 'BE'](this._start + byteOffset); 326 | } 327 | } else if (this._isNodeBuffer && compatibility.NodeBufferEndian) { 328 | value = this.buffer['read' + nodeNaming[type]](this._start + byteOffset, littleEndian); 329 | } 330 | else { 331 | // Error Checking 332 | if (typeof byteOffset !== 'number') { 333 | throw new TypeError('Type error'); 334 | } 335 | if (byteOffset + size > this.byteLength) { 336 | throw new Error('INDEX_SIZE_ERR: DOM Exception 1'); 337 | } 338 | value = this['_get' + type](this._start + byteOffset, littleEndian); 339 | } 340 | 341 | // Move the internal offset forward 342 | this._offset = byteOffset + size; 343 | 344 | return value; 345 | }; 346 | })(type); 347 | } 348 | 349 | if (typeof jQuery !== 'undefined' && jQuery.fn.jquery >= "1.6.2") { 350 | var convertResponseBodyToText = function (byteArray) { 351 | // http://jsperf.com/vbscript-binary-download/6 352 | var scrambledStr; 353 | try { 354 | scrambledStr = IEBinaryToArray_ByteStr(byteArray); 355 | } catch (e) { 356 | // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie 357 | // http://miskun.com/javascript/internet-explorer-and-binary-files-data-access/ 358 | var IEBinaryToArray_ByteStr_Script = 359 | "Function IEBinaryToArray_ByteStr(Binary)\r\n"+ 360 | " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+ 361 | "End Function\r\n"+ 362 | "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+ 363 | " Dim lastIndex\r\n"+ 364 | " lastIndex = LenB(Binary)\r\n"+ 365 | " if lastIndex mod 2 Then\r\n"+ 366 | " IEBinaryToArray_ByteStr_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+ 367 | " Else\r\n"+ 368 | " IEBinaryToArray_ByteStr_Last = -1\r\n"+ 369 | " End If\r\n"+ 370 | "End Function\r\n"; 371 | 372 | // http://msdn.microsoft.com/en-us/library/ms536420(v=vs.85).aspx 373 | // proprietary IE function 374 | window.execScript(IEBinaryToArray_ByteStr_Script, 'vbscript'); 375 | 376 | scrambledStr = IEBinaryToArray_ByteStr(byteArray); 377 | } 378 | 379 | var lastChr = IEBinaryToArray_ByteStr_Last(byteArray), 380 | result = "", 381 | i = 0, 382 | l = scrambledStr.length % 8, 383 | thischar; 384 | while (i < l) { 385 | thischar = scrambledStr.charCodeAt(i++); 386 | result += String.fromCharCode(thischar & 0xff, thischar >> 8); 387 | } 388 | l = scrambledStr.length 389 | while (i < l) { 390 | result += String.fromCharCode( 391 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 392 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 393 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 394 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 395 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 396 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 397 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 398 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8); 399 | } 400 | if (lastChr > -1) { 401 | result += String.fromCharCode(lastChr); 402 | } 403 | return result; 404 | }; 405 | 406 | jQuery.ajaxSetup({ 407 | converters: { 408 | '* dataview': function(data) { 409 | return new jDataView(data); 410 | } 411 | }, 412 | accepts: { 413 | dataview: "text/plain; charset=x-user-defined" 414 | }, 415 | responseHandler: { 416 | dataview: function (responses, options, xhr) { 417 | // Array Buffer Firefox 418 | if ('mozResponseArrayBuffer' in xhr) { 419 | responses.text = xhr.mozResponseArrayBuffer; 420 | } 421 | // Array Buffer Chrome 422 | else if ('responseType' in xhr && xhr.responseType === 'arraybuffer' && xhr.response) { 423 | responses.text = xhr.response; 424 | } 425 | // Internet Explorer (Byte array accessible through VBScript -- convert to text) 426 | else if ('responseBody' in xhr) { 427 | responses.text = convertResponseBodyToText(xhr.responseBody); 428 | } 429 | // Older Browsers 430 | else { 431 | responses.text = xhr.responseText; 432 | } 433 | } 434 | } 435 | }); 436 | 437 | jQuery.ajaxPrefilter('dataview', function(options, originalOptions, jqXHR) { 438 | // trying to set the responseType on IE 6 causes an error 439 | if (jQuery.support.ajaxResponseType) { 440 | if (!options.hasOwnProperty('xhrFields')) { 441 | options.xhrFields = {}; 442 | } 443 | options.xhrFields.responseType = 'arraybuffer'; 444 | } 445 | options.mimeType = 'text/plain; charset=x-user-defined'; 446 | }); 447 | } 448 | 449 | global.jDataView = (global.module || {}).exports = jDataView; 450 | 451 | })(this); 452 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * main.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | // ================================================================================================ 25 | // = Globals = 26 | // ================================================================================================ 27 | 28 | /** stores the time of the beginning of execution */ 29 | var startTime; 30 | 31 | /** Global handle to the gl context of the canvas */ 32 | var gl = null; 33 | 34 | /** Number of log messages posted in the log control */ 35 | var logcount = 0; 36 | 37 | /** 38 | * Enumeration type for the different types of log messages. 39 | */ 40 | var LogType = 41 | { 42 | 'NORMAL' : 'normal', 43 | 'SUCCESS' : 'success', 44 | 'WARNING' : 'warning', 45 | 'ERROR' : 'error' 46 | }; 47 | 48 | /** The projection matrix */ 49 | var projectionMatrix = new J3DIMatrix4(); 50 | 51 | /** The modelview matrix */ 52 | var modelviewMatrix = new J3DIMatrix4(); 53 | 54 | /** Handle to the canvas element where the scene is draw */ 55 | var canvas; 56 | 57 | /** Stores the global key states */ 58 | var keys = new Array(256); 59 | 60 | for(var i = 0; i < 256; i++) 61 | keys[i] = false; 62 | 63 | /** Current mouse position */ 64 | var mouse = 65 | { 66 | x : 0, 67 | y : 0 68 | } 69 | 70 | // ================================================================================================ 71 | // = Functions = 72 | // ================================================================================================ 73 | 74 | /** 75 | * Logs a message in the log window. 76 | * 77 | * @param message The message to show in the log window. 78 | * @param type The type of message to log. @see LogType. 79 | */ 80 | function log(message, type) 81 | { 82 | if(type == undefined) 83 | type = LogType.NORMAL; 84 | 85 | logcount++; 86 | 87 | var currentTime = new Date() 88 | 89 | var html = '' + logcount + '' + (currentTime.getTime() - startTime.getTime()) + 'ms' + '' + message + ''; 90 | 91 | $('#log tbody').append(html); 92 | 93 | console.log("LOG: " + message); 94 | } 95 | 96 | /** 97 | * Checks the support of necessary APIs. 98 | */ 99 | function checkRequiredAPI() 100 | { 101 | if (window.File && window.FileReader && window.FileList) 102 | log('FileReader API is supported', LogType.SUCCESS); 103 | else 104 | { 105 | log('FileReader API is not supported. No BSP files can be loaded!', LogType.ERROR); 106 | return false; 107 | } 108 | 109 | if (window.ArrayBuffer) 110 | log('ArrayBuffer API is supported', LogType.SUCCESS); 111 | else 112 | { 113 | log('ArrayBuffer API is not supported. No BSP files can be loaded!', LogType.ERROR); 114 | return false; 115 | } 116 | 117 | // This is VERY new 118 | // Khronos Editor's Draft 08 December 2011 119 | // http://www.khronos.org/registry/typedarray/specs/latest/#8 120 | if (window.DataView) 121 | log('DataView API is supported', LogType.SUCCESS); 122 | else 123 | log('DataView API is not supported. Parsing files may take longer!', LogType.WARNING); 124 | 125 | return true; 126 | } 127 | 128 | /** 129 | * Actives the WebGL context on the canvas. Sets var gl to the context handle. 130 | */ 131 | function initWebGL() 132 | { 133 | var context; 134 | 135 | canvas = $('canvas')[0]; 136 | 137 | try 138 | { 139 | // Try to grab the standard context 140 | log('Trying to get webgl context'); 141 | context = 'webgl'; 142 | gl = canvas.getContext(context); 143 | } 144 | catch(e) 145 | { 146 | log('Failed to grab webgl context: ' + e, LogType.ERROR); 147 | } 148 | 149 | if(!gl) 150 | { 151 | try 152 | { 153 | // Try to grab the experimental context 154 | log('Trying to get experimental-webgl context'); 155 | context = 'experimental-webgl'; 156 | gl = canvas.getContext(context); 157 | } 158 | catch(e) 159 | { 160 | log('Failed to grab experimental-webgl context: ' + e, LogType.ERROR); 161 | } 162 | } 163 | 164 | // If we don't have a GL context, give up now 165 | if (gl) 166 | { 167 | log('Initialized ' + context + ' context', LogType.SUCCESS); 168 | return true; 169 | } 170 | else 171 | { 172 | log('Sorry: Your browser does not support WebGL!', LogType.ERROR); 173 | return false; 174 | } 175 | } 176 | 177 | /** 178 | * Reads a shader from the given DOM element's id. 179 | * 180 | * @param gl Handle to the OpenGL context. 181 | * @param id DOM id of element where the shader's source is stored. 182 | * @return Returns the OpenGL identifier of the shader obtained by calling createShader(). 183 | */ 184 | function getShader(gl, id) 185 | { 186 | var shaderScript, theSource, currentChild, shader; 187 | 188 | var shaderScript = document.getElementById(id); 189 | 190 | if (!shaderScript) { 191 | return null; 192 | } 193 | 194 | theSource = ""; 195 | currentChild = shaderScript.firstChild; 196 | 197 | while(currentChild) { 198 | if (currentChild.nodeType == currentChild.TEXT_NODE) { 199 | theSource += currentChild.textContent; 200 | } 201 | 202 | currentChild = currentChild.nextSibling; 203 | } 204 | if (shaderScript.type == "x-shader/x-fragment") 205 | shader = gl.createShader(gl.FRAGMENT_SHADER); 206 | else if (shaderScript.type == "x-shader/x-vertex") 207 | shader = gl.createShader(gl.VERTEX_SHADER); 208 | else 209 | return null; 210 | 211 | gl.shaderSource(shader, theSource); 212 | 213 | gl.compileShader(shader); 214 | 215 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) 216 | { 217 | log('Error compiling shader ' + id + ': ' + gl.getShaderInfoLog(shader), LogType.ERROR); 218 | return null; 219 | } 220 | else 221 | { 222 | log('Successfully compiled shader ' + id, LogType.SUCCESS); 223 | return shader; 224 | } 225 | } 226 | 227 | var projectionMatrixLocation; 228 | var modelviewMatrixLocation; 229 | 230 | var positionLocation; 231 | var texCoordLocation; 232 | var lightmapCoordLocation; 233 | var normalLocation; 234 | var colorLocation; 235 | 236 | var samplerTextureLocation; 237 | var samplerLightmapLocation; 238 | 239 | var texturesEnabledLocation; 240 | var lightmapsEnabledLocation; 241 | 242 | var useColorLocation; 243 | 244 | var alphaTestLocation; 245 | 246 | var alphaLocation; 247 | 248 | function initShaders() 249 | { 250 | var vertexShader = getShader(gl, "shader-vs"); 251 | var fragmentShader = getShader(gl, "shader-fs"); 252 | 253 | // Create the shader program 254 | 255 | var shaderProgram = gl.createProgram(); 256 | gl.attachShader(shaderProgram, vertexShader); 257 | gl.attachShader(shaderProgram, fragmentShader); 258 | gl.linkProgram(shaderProgram); 259 | 260 | // If creating the shader program failed, alert 261 | 262 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) 263 | { 264 | log('Linking the shader program failed', LogType.ERROR); 265 | return false; 266 | } 267 | 268 | gl.useProgram(shaderProgram); 269 | log('Successfully linked shader program', LogType.SUCCESS); 270 | 271 | // Get variable locations 272 | projectionMatrixLocation = gl.getUniformLocation(shaderProgram, 'pMatrix'); 273 | modelviewMatrixLocation = gl.getUniformLocation(shaderProgram, 'mvMatrix'); 274 | 275 | 276 | positionLocation = gl.getAttribLocation(shaderProgram, "attribPosition"); 277 | texCoordLocation = gl.getAttribLocation(shaderProgram, "attribTexCoord"); 278 | lightmapCoordLocation = gl.getAttribLocation(shaderProgram, "attribLightmapCoord"); 279 | normalLocation = gl.getAttribLocation(shaderProgram, "attribNormal"); 280 | colorLocation = gl.getAttribLocation(shaderProgram, "attribColor"); 281 | 282 | samplerTextureLocation = gl.getUniformLocation(shaderProgram, "uniSamplerTexture"); 283 | gl.uniform1i(samplerTextureLocation, 0); 284 | samplerLightmapLocation = gl.getUniformLocation(shaderProgram, "uniSamplerLightmap"); 285 | gl.uniform1i(samplerLightmapLocation, 1); 286 | 287 | texturesEnabledLocation = gl.getUniformLocation(shaderProgram, "texturesEnabled"); 288 | gl.uniform1i(texturesEnabledLocation, 1); 289 | lightmapsEnabledLocation = gl.getUniformLocation(shaderProgram, "lightmapsEnabled"); 290 | gl.uniform1i(lightmapsEnabledLocation, 1); 291 | 292 | useColorLocation = gl.getUniformLocation(shaderProgram, "useColor"); 293 | 294 | alphaTestLocation = gl.getUniformLocation(shaderProgram, "alphaTest"); 295 | 296 | alphaLocation = gl.getUniformLocation(shaderProgram, "alpha"); 297 | 298 | // 299 | // Init some of them 300 | // 301 | 302 | gl.enableVertexAttribArray(positionLocation); // We will always need vertices 303 | 304 | gl.uniform1f(alphaLocation, 1.0); 305 | 306 | return true; 307 | } 308 | 309 | var coordSystem = 310 | { 311 | vertexBuffer : undefined, 312 | colorBuffer : undefined 313 | }; 314 | 315 | function initBuffers() 316 | { 317 | /** 318 | * Coord system 319 | */ 320 | coordSystem.vertexBuffer = gl.createBuffer(); 321 | gl.bindBuffer(gl.ARRAY_BUFFER, coordSystem.vertexBuffer); 322 | 323 | var coordVertices = 324 | [ 325 | 0.0, 0.0, 0.0, 326 | 4000.0, 0.0, 0.0, 327 | 0.0, 0.0, 0.0, 328 | 0.0, 4000.0, 0.0, 329 | 0.0, 0.0, 0.0, 330 | 0.0, 0.0, 4000.0, 331 | 332 | 0.0, 0.0, 0.0, 333 | -4000.0, 0.0, 0.0, 334 | 0.0, 0.0, 0.0, 335 | 0.0, -4000.0, 0.0, 336 | 0.0, 0.0, 0.0, 337 | 0.0, 0.0, -4000.0, 338 | ]; 339 | 340 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(coordVertices), gl.STATIC_DRAW); 341 | 342 | coordSystem.colorBuffer = gl.createBuffer(); 343 | gl.bindBuffer(gl.ARRAY_BUFFER, coordSystem.colorBuffer); 344 | 345 | var coordColors = 346 | [ 347 | 1.0, 0.0, 0.0, 1.0, 348 | 1.0, 0.0, 0.0, 1.0, 349 | 0.0, 1.0, 0.0, 1.0, 350 | 0.0, 1.0, 0.0, 1.0, 351 | 0.0, 0.0, 1.0, 1.0, 352 | 0.0, 0.0, 1.0, 1.0, 353 | 354 | 0.3, 0.0, 0.0, 1.0, 355 | 0.3, 0.0, 0.0, 1.0, 356 | 0.0, 0.3, 0.0, 1.0, 357 | 0.0, 0.3, 0.0, 1.0, 358 | 0.0, 0.0, 0.3, 1.0, 359 | 0.0, 0.0, 0.3, 1.0, 360 | ]; 361 | 362 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(coordColors), gl.STATIC_DRAW); 363 | 364 | return true; 365 | } 366 | 367 | function setStates() 368 | { 369 | log('Setting states ...'); 370 | 371 | gl.clearColor(0, 0, 0, 1); 372 | gl.enable(gl.DEPTH_TEST); 373 | 374 | gl.cullFace(gl.FRONT); 375 | gl.enable(gl.CULL_FACE); 376 | } 377 | 378 | function resize() 379 | { 380 | var width = canvas.clientWidth; 381 | var height = canvas.clientHeight; 382 | 383 | log('Resizing to ' + width + ' x ' + height); 384 | 385 | projectionMatrix.perspective(60.0, width / height, 8.0, 6000.0); 386 | gl.viewport(0, 0, width, height); 387 | 388 | projectionMatrix.setUniform(gl, projectionMatrixLocation, false); 389 | } 390 | 391 | /** 392 | * Updates the scene. 393 | * 394 | * @param interval Number seconds passed since the last frame. 395 | */ 396 | function update(interval) 397 | { 398 | camera.update(interval); 399 | } 400 | 401 | function render() 402 | { 403 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 404 | 405 | modelviewMatrix.makeIdentity(); 406 | camera.look(); 407 | //modelviewMatrix.translate(0,0,-10); 408 | //modelviewMatrix.setUniform(gl, modelviewMatrixLocation, false); 409 | 410 | if(bsp.loaded) 411 | bsp.render(camera.pos); 412 | 413 | gl.uniform1i(useColorLocation, 1); // use colors for rendering 414 | 415 | // enable/disable the required attribute arrays 416 | gl.disableVertexAttribArray(texCoordLocation); 417 | gl.disableVertexAttribArray(lightmapCoordLocation); 418 | gl.disableVertexAttribArray(normalLocation); 419 | gl.enableVertexAttribArray(colorLocation); 420 | 421 | if(showCoordSystem) 422 | { 423 | gl.bindBuffer(gl.ARRAY_BUFFER, coordSystem.vertexBuffer); 424 | gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); 425 | gl.bindBuffer(gl.ARRAY_BUFFER, coordSystem.colorBuffer); 426 | gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 0, 0); 427 | 428 | gl.drawArrays(gl.LINES, 0, 12); 429 | } 430 | 431 | gl.uniform1i(useColorLocation, 0); 432 | } 433 | 434 | var lastTime = new Date().getTime(); 435 | var fps; 436 | var fpsCounter = 0; 437 | var lastFpsUpdate = 0; 438 | 439 | function mainloop() 440 | { 441 | // Calculate time since the last frame 442 | var time = new Date().getTime(); 443 | var interval = time - lastTime; 444 | 445 | // Update FPS 446 | fpsCounter++; 447 | if(time - lastFpsUpdate >= 1000) 448 | { 449 | // Update of fps is longer than a second ago 450 | fps = fpsCounter; 451 | fpsCounter = 0; 452 | lastFpsUpdate = time; 453 | $('#info p:first-child').html('Rendering at ' + fps + ' FPS'); 454 | } 455 | 456 | lastTime = time; 457 | 458 | // Update the scene based on the passed time 459 | update(interval / 1000.0); 460 | 461 | // Send all geometry to the renderer 462 | render(); 463 | 464 | // Start next frame 465 | setTimeout(mainloop, 0); 466 | } 467 | 468 | function main() 469 | { 470 | startTime = new Date(); 471 | log('<< STARTUP >>'); 472 | 473 | if(!initWebGL()) 474 | return; 475 | 476 | // Show WebGL information 477 | $('#info img') 478 | .after('

    Vendor: ' + gl.getParameter(gl.VENDOR) + '

    ') 479 | .after('

    Renderer: ' + gl.getParameter(gl.RENDERER) + '

    ') 480 | .after('

    Version: ' + gl.getParameter(gl.VERSION) + '

    '); 481 | 482 | if(!checkRequiredAPI()) 483 | return; 484 | if(!initShaders()) 485 | return; 486 | if(!initBuffers()) 487 | return; 488 | 489 | setStates(); 490 | 491 | resize(); 492 | 493 | camera.z = 0; 494 | 495 | log('Starting mainloop'); 496 | mainloop(); 497 | } 498 | 499 | // GET THE BALL ROLLING 500 | window.addEventListener('load', main, false); 501 | -------------------------------------------------------------------------------- /js/mathlib.js: -------------------------------------------------------------------------------- 1 | /* 2 | * mathlib.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | // 25 | // Provides basic mathematical routines for vector processing. 26 | // 27 | 28 | /** Defines the epsilon used for collision detection */ 29 | var EPSILON = 0.03125 // 1/32 30 | 31 | /** 32 | * Structure for representing a point (or vector) in 3D space. 33 | * 34 | * @param v Optional. An instance of Vector3D to copy. 35 | */ 36 | function Vector3D(v) 37 | { 38 | var x; 39 | var y; 40 | var z; 41 | 42 | if(v != undefined) 43 | { 44 | this.x = v.x; 45 | this.y = v.y; 46 | this.z = v.z; 47 | } 48 | } 49 | 50 | /** 51 | * Vector addition. 52 | */ 53 | function vectorAdd(a, b) 54 | { 55 | var result = new Vector3D(); 56 | result.x = a.x + b.x; 57 | result.y = a.y + b.y; 58 | result.z = a.z + b.z; 59 | return result; 60 | } 61 | 62 | /** 63 | * Vector subtraction. 64 | */ 65 | function vectorSub(a, b) 66 | { 67 | var result = new Vector3D(); 68 | result.x = a.x - b.x; 69 | result.y = a.y - b.y; 70 | result.z = a.z - b.z; 71 | return result; 72 | } 73 | 74 | /** 75 | * Multiplies a vector with a scalar. 76 | */ 77 | function vectorMul(v, s) 78 | { 79 | var result = new Vector3D(v); 80 | result.x *= s; 81 | result.y *= s; 82 | result.z *= s; 83 | return result; 84 | } 85 | 86 | /** 87 | * Checks whether or not two vectors are equal. 88 | */ 89 | function vectorEquals(a, b) 90 | { 91 | return (a.x == b.x) && (a.y == b.y) && (a.z == b.z); 92 | } 93 | 94 | /** 95 | * Returns the dot product of two vectors. 96 | */ 97 | function dotProduct(a, b) 98 | { 99 | return (a.x * b.x) + (a.y * b.y) + (a.z * b.z); 100 | } 101 | 102 | /** 103 | * Converts an angle in degrees to radians. 104 | * 105 | * @param degree An angle in degrees. 106 | */ 107 | function degToRad(degree) 108 | { 109 | return degree / 180.0 * Math.PI; 110 | } 111 | 112 | /** 113 | * Tests whether or not the given point is in the axis aligned bounding box spaned by mins and maxs. 114 | * 115 | * @return Returns true when the point is inside or directly on the box surface. 116 | */ 117 | function pointInBox(point, mins, maxs) 118 | { 119 | if((mins[0] <= point.x && point.x <= maxs[0] && 120 | mins[1] <= point.y && point.y <= maxs[1] && 121 | mins[2] <= point.z && point.z <= maxs[2]) || 122 | (mins[0] >= point.x && point.x >= maxs[0] && 123 | mins[1] >= point.y && point.y >= maxs[1] && 124 | mins[2] >= point.z && point.z >= maxs[2])) 125 | return true; 126 | else 127 | return false; 128 | } 129 | -------------------------------------------------------------------------------- /js/movement.js: -------------------------------------------------------------------------------- 1 | /* 2 | * movement.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | var hull; 25 | 26 | /** 27 | * Calculates a complete move through the bsp tree from start to end. 28 | * 29 | * @param start The start position of the movement. An object having at least an x, y, and z component. 30 | * @param end The end position of the movement. An object having at least an x, y, and z component. 31 | * @param collisionHull The hull of the bsp tree to use for collision detecting. 32 | * @return Returns the final position of the movement as Vector3D. @see Vector3D 33 | */ 34 | function playerMove(start, end, collisionHull) 35 | { 36 | if(collisionHull == undefined) 37 | collisionHull = 0; 38 | hull = collisionHull; 39 | 40 | return traceLine(start, end); 41 | } 42 | 43 | // 44 | // The following code has been taken from mainly from the Quake 1 source code. 45 | // 46 | 47 | /** 48 | * Structure containing trace related information used during the tracing process. 49 | */ 50 | function Trace() 51 | { 52 | /** */ 53 | var allsolid; 54 | 55 | /** The BspPlane the trace has collided with */ 56 | var plane; 57 | 58 | /** The ratio between 0.0 and 1.0 how far the trace from start to end was successful */ 59 | var ratio; 60 | } 61 | 62 | /** 63 | * Traces a line inside the current clipping hull. 64 | * @param start The start position of the movement. An object having at least an x, y, and z component. 65 | * @param end The end position of the movement. An object having at least an x, y, and z component. 66 | * @return Returns the final position of the movement as Vector3D. @see Vector3D 67 | */ 68 | function traceLine(start, end) 69 | { 70 | if(vectorEquals(start, end)) 71 | return end; // no move 72 | 73 | // create a default trace 74 | var trace = new Trace(); 75 | trace.allsolid = true; 76 | trace.plane = null; 77 | trace.ratio = 1.0; 78 | 79 | // We start with the first node (0), setting our start and end ratio to 0 and 1. 80 | // We will recursively go through all of the clipnodes and try to find collisions with their planes. 81 | recursiveHullCheck(bsp.models[0].headNodes[hull], 0.0, 1.0, start, end, trace); 82 | 83 | // If the trace ratio is STILL 1.0, then we never collided and just return our end position 84 | if(trace.ratio == 1.0) 85 | return end; 86 | else // else COLLISION!!!! 87 | { 88 | // Set our new position to a position that is right up to the plane we collided with 89 | var newPosition = vectorAdd(start, vectorMul(vectorSub(end, start), trace.ratio)); 90 | 91 | // Get the distance vector from the wanted end point to the actual new position 92 | // The distance we have to travel backward from the original end position to the collision point) 93 | var missingMove = vectorSub(end, newPosition); 94 | 95 | // Get the distance we need to travel backwards from the end position to the new slide position. 96 | // (The position we arrive when we collide with a plane and slide along it with the remaining momentum) 97 | // This is a distance along the normal of the plane we collided with. 98 | var distance = dotProduct(missingMove, trace.plane.normal); 99 | 100 | // Get the final end position that we will end up (after collision and sliding along the plane). 101 | var endPosition = vectorSub(end, vectorMul(trace.plane.normal, distance)); 102 | 103 | // Since we got a new position after sliding, we need to make sure 104 | // that the new sliding position doesn't collide with anything else. 105 | newPosition = traceLine(newPosition, endPosition); 106 | 107 | // Return the new position 108 | return newPosition; 109 | } 110 | } 111 | 112 | /** 113 | * Traverses the clipping hull down to the content (see defines in bspdef). 114 | * @param The clipnodes index to get the content at a position. 115 | * @param The point to get the content for. 116 | * @return Returns the content at the given point. (see defines in bspdef) 117 | */ 118 | function hullPointContents(nodeIndex, point) 119 | { 120 | while (nodeIndex >= 0) 121 | { 122 | var node = bsp.clipNodes[nodeIndex]; 123 | var plane = bsp.planes[node.plane]; 124 | 125 | var d = dotProduct(plane.normal, point) - plane.dist; 126 | 127 | if (d < 0) 128 | nodeIndex = node.children[1]; 129 | else 130 | nodeIndex = node.children[0]; 131 | } 132 | 133 | return nodeIndex; 134 | } 135 | 136 | /** 137 | * Recursively checks a part of the traced line against the collision hull. 138 | * 139 | * @param nodeIndex The index of the clipnode currently checked. 140 | * @param startFraction The start fraction for this node between 0.0 and 1.0 on the currently traced line. 141 | * @param endFraction The end fraction for this node between 0.0 and 1.0 on the currently traced line. 142 | * @param startPoint The start point for this node on the currently traced line. 143 | * @param endPoint The end point for this node on the currently traced line. 144 | * @param trace Reference to the current trace data. 145 | * @return Returns true, if the current node index is not a clipnode but a content identifier (see defines in bspdef), 146 | * meaning a leaf of the clipnode tree has been reached. 147 | */ 148 | function recursiveHullCheck(nodeIndex, startFraction, endFraction, startPoint, endPoint, trace) 149 | { 150 | // check for empty 151 | if (nodeIndex < 0) 152 | { 153 | if (nodeIndex != CONTENTS_SOLID) 154 | trace.allsolid = false; 155 | 156 | return true; // empty 157 | } 158 | 159 | // find the point distances 160 | var node = bsp.clipNodes[nodeIndex]; 161 | var plane = bsp.planes[node.plane]; 162 | 163 | var t1, t2; 164 | 165 | t1 = dotProduct(plane.normal, startPoint) - plane.dist; 166 | t2 = dotProduct(plane.normal, endPoint) - plane.dist; 167 | 168 | if (t1 >= 0.0 && t2 >= 0.0) 169 | return recursiveHullCheck(node.children[0], startFraction, endFraction, startPoint, endPoint, trace); 170 | if (t1 < 0.0 && t2 < 0.0) 171 | return recursiveHullCheck(node.children[1], startFraction, endFraction, startPoint, endPoint, trace); 172 | 173 | // put the crosspoint EPSILON pixels on the near side 174 | var frac; 175 | 176 | if (t1 < 0.0) 177 | frac = (t1 + EPSILON) / (t1 - t2); 178 | else 179 | frac = (t1 - EPSILON) / (t1 - t2); 180 | if (frac < 0.0) 181 | frac = 0.0; 182 | if (frac > 1.0) 183 | frac = 1.0; 184 | 185 | var midf = startFraction + (endFraction - startFraction) * frac; 186 | var midPoint = vectorAdd(startPoint, vectorMul(vectorSub(endPoint, startPoint), frac)); 187 | 188 | var side = (t1 < 0) ? 1 : 0; 189 | 190 | // move up to the node 191 | if (!recursiveHullCheck(node.children[side], startFraction, midf, startPoint, midPoint, trace)) 192 | return false; 193 | 194 | if (hullPointContents(node.children[side ^ 1], midPoint) != CONTENTS_SOLID) 195 | // go past the node 196 | return recursiveHullCheck (node.children[side ^ 1], midf, endFraction, midPoint, endPoint, trace); 197 | 198 | if (trace.allsolid) 199 | return false; // never got out of the solid area 200 | 201 | // the other side of the node is solid, this is the impact point 202 | if (!side) 203 | trace.plane = plane; 204 | else 205 | { 206 | trace.plane = new BspPlane(); 207 | trace.plane.normal = vectorMul(plane.normal, -1); 208 | trace.plane.dist = -plane.dist; 209 | } 210 | 211 | while(hullPointContents(bsp.models[0].headNodes[hull], midPoint) == CONTENTS_SOLID) 212 | { 213 | // shouldn't really happen, but does occasionally 214 | frac -= 0.1; 215 | if (frac < 0) 216 | { 217 | trace.ratio = midf; 218 | return false; 219 | } 220 | midf = startFraction + (endFraction - startFraction) * frac; 221 | midPoint = vectorAdd(startPoint, vectorMul(vectorSub(endPoint, startPoint), frac)); 222 | } 223 | 224 | trace.ratio = midf; 225 | 226 | return false; 227 | } 228 | -------------------------------------------------------------------------------- /js/texutils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * texutils.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | /** 25 | * Returns true, if the given value is a power of two. 26 | */ 27 | function isPowerOfTwo(x) 28 | { 29 | return (x & (x - 1)) == 0; 30 | } 31 | 32 | /** 33 | * Gets the next highest power of two for a given integer. 34 | */ 35 | function nextHighestPowerOfTwo(x) 36 | { 37 | --x; 38 | for (var i = 1; i < 32; i <<= 1) 39 | x = x | x >> i; 40 | return x + 1; 41 | } 42 | 43 | /** Canvas used for the image conversion and scaling in pixelsToImage() */ 44 | var conversionCanvas = document.createElement("canvas"); 45 | 46 | /** Context to conversionCanvas */ 47 | var conversionCtx = conversionCanvas.getContext("2d"); 48 | 49 | /** 50 | * Converts a raw pixel array into an Image object whose dimensions are powers of two. 51 | * 52 | * @param pixelArray An array (or equivalent, must support operator[]) of bytes (e.g. RGBRGBRGB ...) 53 | * @param width The with of the image. 54 | * @param height The height of the image. 55 | * @param channels The number of channels. Must be 3 (RGB) or 4 (RGBA). 56 | * @return Returns a new Image object containing the given data. 57 | */ 58 | function pixelsToTexture(pixelArray, width, height, channels, callback) 59 | { 60 | conversionCanvas.width = width; 61 | conversionCanvas.height = height; 62 | //var ctx = conversionCanvas.getContext("2d"); 63 | 64 | var texture = gl.createTexture(); 65 | 66 | // 67 | // Convert 68 | // 69 | 70 | var imgData = conversionCtx.createImageData(width, height); 71 | for (var x = 0; x < width; x++) 72 | { 73 | for (var y = 0; y < height; y++) 74 | { 75 | var dataIndex = (x + y * width) * 4; 76 | var pixelIndex = (x + y * width) * channels; 77 | imgData.data[dataIndex + 0] = pixelArray[pixelIndex + 0]; 78 | imgData.data[dataIndex + 1] = pixelArray[pixelIndex + 1]; 79 | imgData.data[dataIndex + 2] = pixelArray[pixelIndex + 2]; 80 | if(channels == 4) 81 | imgData.data[dataIndex + 3] = pixelArray[pixelIndex + 3]; 82 | else 83 | imgData.data[dataIndex + 3] = 255; 84 | } 85 | } 86 | conversionCtx.putImageData(imgData, 0, 0); 87 | 88 | var img = new Image(); 89 | img.width = width; 90 | img.height = height; 91 | 92 | img.onload = function(img) 93 | { 94 | return function() 95 | { 96 | // 97 | // Scale 98 | // 99 | 100 | if (!isPowerOfTwo(img.width) || !isPowerOfTwo(img.height)) 101 | { 102 | // Scale up the texture to the next highest power of two dimensions. 103 | conversionCanvas.width = nextHighestPowerOfTwo(img.width); 104 | conversionCanvas.height = nextHighestPowerOfTwo(img.height); 105 | //var ctx = conversionCanvas.getContext("2d"); 106 | conversionCtx.drawImage(img, 0, 0, conversionCanvas.width, conversionCanvas.height); 107 | 108 | img = new Image(); 109 | img.width = conversionCanvas.width; 110 | img.height = conversionCanvas.height; 111 | 112 | img.onload = function(img) 113 | { 114 | return function() 115 | { 116 | callback(texture, img); 117 | }; 118 | }(img); 119 | 120 | img.src = conversionCanvas.toDataURL(); 121 | } 122 | else 123 | callback(texture, img); 124 | }; 125 | }(img); 126 | 127 | img.src = conversionCanvas.toDataURL(); 128 | //$('body').append('Texture (' + img.width + 'x' + img.height + ')').append(img); 129 | 130 | 131 | //$('body').append('Texture (' + img.width + 'x' + img.height + ')').append(img); 132 | 133 | return texture; 134 | } -------------------------------------------------------------------------------- /js/wad.js: -------------------------------------------------------------------------------- 1 | /* 2 | * wad.js 3 | * 4 | * Copyright (c) 2012, Bernhard Manfred Gruber. All rights reserved. 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | 'use strict'; 23 | 24 | /* 25 | typedef struct 26 | { 27 | char szMagic[4]; 28 | int32_t nDir; 29 | int32_t nDirOffset; 30 | } WADHEADER; 31 | */ 32 | function WadHeader() 33 | { 34 | var magic; // should be WAD2/WAD3 35 | var dirs; // number of directory entries 36 | var dirOffset; // offset into directory 37 | } 38 | var SIZE_OF_WADHEADER = 12; 39 | 40 | // Directory entry structure 41 | /* 42 | typedef struct _WADDIRENTRY 43 | { 44 | int32_t nFilePos; 45 | int32_t nDiskSize; 46 | int32_t nSize; 47 | int8_t nType; 48 | bool bCompression; 49 | int16_t nDummy; 50 | char szName[MAXTEXTURENAME]; 51 | } WADDIRENTRY; 52 | */ 53 | 54 | function WadDirEntry() 55 | { 56 | var offset; // offset in WAD 57 | var compressedSize; // size in file 58 | var size; // uncompressed size 59 | var type; // type of entry 60 | var compressed; // 0 if none 61 | var name; // must be null terminated 62 | } 63 | 64 | /** 65 | * Wad class. 66 | * Represents a wad archiev and offers methods for extracting textures from it. 67 | */ 68 | function Wad() 69 | { 70 | /** Identifies the wad */ 71 | var name; 72 | 73 | var src; 74 | 75 | /** Wad file header */ 76 | var header; 77 | 78 | /** Array of directory entries */ 79 | var entries; 80 | }; 81 | 82 | /** 83 | * Opens the wad file and loads it's directory for texture searching. 84 | */ 85 | Wad.prototype.open = function(buffer) 86 | { 87 | console.log('Begin loading wad'); 88 | 89 | this.src = new BinaryFile(buffer); 90 | 91 | var src = this.src; 92 | 93 | var header = new WadHeader(); 94 | header.magic = src.readString(4); 95 | header.dirs = src.readLong(); 96 | header.dirOffset = src.readLong(); 97 | 98 | console.log('Header: ' + header.magic + ' (' + header.dirs + ' contained objects)'); 99 | 100 | if(header.magic != 'WAD2' && header.magic != 'WAD3') 101 | return false; 102 | 103 | this.header = header; 104 | this.entries = new Array(); 105 | 106 | src.seek(header.dirOffset); 107 | 108 | for(var i = 0; i < header.dirs; i++) 109 | { 110 | var entry = new WadDirEntry(); 111 | 112 | entry.offset = src.readLong(); 113 | entry.compressedSize = src.readLong(); 114 | entry.size = src.readLong(); 115 | entry.type = src.readByte(); 116 | entry.compressed = (src.readUByte() ? true : false); 117 | src.readUShort(); 118 | entry.name = src.readString(MAXTEXTURENAME); 119 | 120 | console.log('Texture #' + i + ' name: ' + entry.name); 121 | 122 | this.entries.push(entry); 123 | } 124 | 125 | console.log('Finished loading wad'); 126 | 127 | return true; 128 | } 129 | 130 | /** 131 | * Finds and loads a texture from the wad file. 132 | * 133 | * @param texName The name of the texture to find. 134 | * @return Returns the OpenGL identifier for the loaded textrue obtained by calling gl.createTexture() 135 | * or null if the texture could not be found. 136 | */ 137 | Wad.prototype.loadTexture = function(texName) 138 | { 139 | // Find cooresponding directory entry 140 | for(var i = 0; i < this.entries.length; i++) 141 | { 142 | var entry = this.entries[i]; 143 | if(entry.name.toLowerCase() == texName.toLowerCase()) 144 | return this.fetchTextureAtOffset(this.src, entry.offset); 145 | } 146 | 147 | return null; 148 | } 149 | 150 | /** 151 | * Static method for fetching a texture at a given offset from a byte buffer. 152 | * Reads the texture header and decodes the indexed image into a rgba image. 153 | * Finally creates a OpenGL texture. 154 | * 155 | * @param src A BinaryFile object used to read data from. 156 | * @param offset The offset in the binary file to start reading. 157 | * @return Returns a WebGLTexture obtained by calling createTexture(). 158 | */ 159 | Wad.prototype.fetchTextureAtOffset = function(src, offset) 160 | { 161 | // Seek to the texture beginning 162 | src.seek(offset); 163 | 164 | // Load texture header 165 | var mipTex = new BspMipTexture(); 166 | mipTex.name = src.readString(MAXTEXTURENAME); 167 | mipTex.width = src.readULong(); 168 | mipTex.height = src.readULong(); 169 | mipTex.offsets = new Array(); 170 | for(var i = 0; i < MIPLEVELS; i++) 171 | mipTex.offsets.push(src.readULong()); 172 | 173 | // Fetch color palette 174 | var paletteOffset = mipTex.offsets[MIPLEVELS - 1] + ((mipTex.width / 8) * (mipTex.height / 8)) + 2; 175 | 176 | var palette = new Uint8Array(src.buffer, offset + paletteOffset, 256 * 3); 177 | 178 | // Generate texture 179 | //var texture = gl.createTexture(); 180 | //gl.bindTexture(gl.TEXTURE_2D, texture); 181 | 182 | //for (var i = 0; i < MIPLEVELS; i++) // ONLY LOAD FIRST MIPLEVEL !!! 183 | { 184 | // Width and height shrink to half for every level 185 | var width = mipTex.width; //>> i; 186 | var height = mipTex.height; // >> i; 187 | 188 | // Fetch the indexed texture 189 | var textureIndexes = new Uint8Array(src.buffer, offset + mipTex.offsets[0 /*i*/], width * height); 190 | 191 | // Allocate storage for the rgba texture 192 | var textureData = new Array(width * height * 4); 193 | 194 | // Translate the texture from indexes to rgba 195 | for (var j = 0; j < width * height; j++) 196 | { 197 | var paletteIndex = textureIndexes[j] * 3; 198 | 199 | textureData[j * 4] = palette[paletteIndex]; 200 | textureData[j * 4 + 1] = palette[paletteIndex + 1]; 201 | textureData[j * 4 + 2] = palette[paletteIndex + 2]; 202 | textureData[j * 4 + 3] = 255; //every pixel is totally opaque 203 | } 204 | 205 | if(mipTex.name.substring(0, 1) == "{") // this is an alpha texture 206 | { 207 | console.log(mipTex.name + " is an alpha texture"); 208 | // Transfere alpha key color to actual alpha values 209 | this.applyAlphaSections(textureData, width, height, palette[255 * 3 + 0], palette[255 * 3 + 1], palette[255 * 3 + 2]); 210 | } 211 | 212 | // Upload the data to OpenGL 213 | //var img = pixelsToImage(textureData, width, height, 4); 214 | 215 | //$('body').append('Texture (' + img.width + 'x' + img.height + ')').append(img); 216 | 217 | //gl.texImage2D(gl.TEXTURE_2D, 0 /*i*/, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); 218 | } 219 | 220 | var texture = pixelsToTexture(textureData, width, height, 4, function(texture, image) 221 | { 222 | gl.bindTexture(gl.TEXTURE_2D, texture); 223 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 224 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR_MIPMAP_LINEAR); 225 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); 226 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); 227 | gl.texImage2D(gl.TEXTURE_2D, 0 , gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); 228 | gl.generateMipmap(gl.TEXTURE_2D); 229 | gl.bindTexture(gl.TEXTURE_2D, null); 230 | }); 231 | 232 | 233 | 234 | return texture; 235 | } 236 | 237 | /** 238 | * Translates the transparent regions of an image given by a selected "transparency color" 239 | * into actual alpha values. 240 | * Also performs color interpolation at the edges of transparent regions to prevent that the 241 | * "transparency color" can be seen on the final texture. 242 | * 243 | * @param pixels An array of pixels (rgba) which will be altered. 244 | * @param width The width of the image hold by pixels. 245 | * @param height The height of the image hold by pixels. 246 | * @param key* The "transparency color". 247 | */ 248 | Wad.prototype.applyAlphaSections = function(pixels, width, height, keyR, keyG, keyB) 249 | { 250 | // Create an equally sized pixel buffer initialized to the key color 251 | var rgbBuffer = new Array(width * height * 3); 252 | 253 | for(var y = 0; y < height; y++) 254 | { 255 | for(var x = 0; x < width; x++) 256 | { 257 | var bufIndex = (y * width + x) * 3; 258 | rgbBuffer[bufIndex + 0] = keyR; 259 | rgbBuffer[bufIndex + 1] = keyG; 260 | rgbBuffer[bufIndex + 2] = keyB; 261 | } 262 | } 263 | 264 | // The key color signifies a transparent portion of the texture. Zero alpha for blending and 265 | // to get rid of key colored edges choose the average color of the nearest non key pixels. 266 | 267 | // Interpolate colors for transparent pixels 268 | for(var y = 0; y < height; y++) 269 | { 270 | for(var x = 0; x < width; x++) 271 | { 272 | var index = (y * width + x) * 4; 273 | 274 | if ((pixels[index + 0] == keyR) && 275 | (pixels[index + 1] == keyG) && 276 | (pixels[index + 2] == keyB)) 277 | { 278 | // This is a pixel which should be transparent 279 | 280 | pixels[index + 3] = 0; 281 | 282 | var count = 0; 283 | var colorSum = new Array(3); 284 | colorSum[0] = 0; 285 | colorSum[1] = 0; 286 | colorSum[2] = 0; 287 | 288 | // left above pixel 289 | if((x > 0) && (y > 0)) 290 | { 291 | var pixelIndex = ((y - 1) * width + (x - 1)) * 4; 292 | if (pixels[pixelIndex + 0] != keyR || 293 | pixels[pixelIndex + 1] != keyG || 294 | pixels[pixelIndex + 2] != keyB) 295 | { 296 | colorSum[0] += pixels[pixelIndex + 0] * Math.SQRT2; 297 | colorSum[1] += pixels[pixelIndex + 1] * Math.SQRT2; 298 | colorSum[2] += pixels[pixelIndex + 2] * Math.SQRT2; 299 | count++; 300 | } 301 | } 302 | 303 | // above pixel 304 | if((x >= 0) && (y > 0)) 305 | { 306 | var pixelIndex = ((y - 1) * width + x) * 4; 307 | if (pixels[pixelIndex + 0] != keyR || 308 | pixels[pixelIndex + 1] != keyG || 309 | pixels[pixelIndex + 2] != keyB) 310 | { 311 | colorSum[0] += pixels[pixelIndex + 0]; 312 | colorSum[1] += pixels[pixelIndex + 1]; 313 | colorSum[2] += pixels[pixelIndex + 2]; 314 | count++; 315 | } 316 | } 317 | 318 | // right above pixel 319 | if((x < width - 1) && (y > 0)) 320 | { 321 | var pixelIndex = ((y - 1) * width + (x + 1)) * 4; 322 | if (pixels[pixelIndex + 0] != keyR || 323 | pixels[pixelIndex + 1] != keyG || 324 | pixels[pixelIndex + 2] != keyB) 325 | { 326 | colorSum[0] += pixels[pixelIndex + 0] * Math.SQRT2; 327 | colorSum[1] += pixels[pixelIndex + 1] * Math.SQRT2; 328 | colorSum[2] += pixels[pixelIndex + 2] * Math.SQRT2; 329 | count++; 330 | } 331 | } 332 | 333 | // left pixel 334 | if(x > 0) 335 | { 336 | var pixelIndex = (y * width + (x - 1)) * 4; 337 | if (pixels[pixelIndex + 0] != keyR || 338 | pixels[pixelIndex + 1] != keyG || 339 | pixels[pixelIndex + 2] != keyB) 340 | { 341 | colorSum[0] += pixels[pixelIndex + 0]; 342 | colorSum[1] += pixels[pixelIndex + 1]; 343 | colorSum[2] += pixels[pixelIndex + 2]; 344 | count++; 345 | } 346 | } 347 | 348 | // right pixel 349 | if(x < width - 1) 350 | { 351 | var pixelIndex = (y * width + (x + 1)) * 4; 352 | if (pixels[pixelIndex + 0] != keyR || 353 | pixels[pixelIndex + 1] != keyG || 354 | pixels[pixelIndex + 2] != keyB) 355 | { 356 | colorSum[0] += pixels[pixelIndex + 0]; 357 | colorSum[1] += pixels[pixelIndex + 1]; 358 | colorSum[2] += pixels[pixelIndex + 2]; 359 | count++; 360 | } 361 | } 362 | 363 | // left underneath pixel 364 | if((x > 0) && (y < height - 1)) 365 | { 366 | var pixelIndex = ((y + 1) * width + (x - 1)) * 4; 367 | if (pixels[pixelIndex + 0] != keyR || 368 | pixels[pixelIndex + 1] != keyG || 369 | pixels[pixelIndex + 2] != keyB) 370 | { 371 | colorSum[0] += pixels[pixelIndex + 0] * Math.SQRT2; 372 | colorSum[1] += pixels[pixelIndex + 1] * Math.SQRT2; 373 | colorSum[2] += pixels[pixelIndex + 2] * Math.SQRT2; 374 | count++; 375 | } 376 | } 377 | 378 | // underneath pixel 379 | if((x >= 0) && (y < height - 1)) 380 | { 381 | var pixelIndex = ((y + 1) * width + x) * 4; 382 | if (pixels[pixelIndex + 0] != keyR || 383 | pixels[pixelIndex + 1] != keyG || 384 | pixels[pixelIndex + 2] != keyB) 385 | { 386 | colorSum[0] += pixels[pixelIndex + 0]; 387 | colorSum[1] += pixels[pixelIndex + 1]; 388 | colorSum[2] += pixels[pixelIndex + 2]; 389 | count++; 390 | } 391 | } 392 | 393 | // right underneath pixel 394 | if((x < width - 1) && (y < height - 1)) 395 | { 396 | var pixelIndex = ((y + 1) * width + (x + 1)) * 4; 397 | if (pixels[pixelIndex + 0] != keyR || 398 | pixels[pixelIndex + 1] != keyG || 399 | pixels[pixelIndex + 2] != keyB) 400 | { 401 | colorSum[0] += pixels[pixelIndex + 0] * Math.SQRT2; 402 | colorSum[1] += pixels[pixelIndex + 1] * Math.SQRT2; 403 | colorSum[2] += pixels[pixelIndex + 2] * Math.SQRT2; 404 | count++; 405 | } 406 | } 407 | 408 | if (count > 0) 409 | { 410 | colorSum[0] /= count; 411 | colorSum[1] /= count; 412 | colorSum[2] /= count; 413 | 414 | var bufIndex = (y * width + x) * 3; 415 | rgbBuffer[bufIndex + 0] = Math.round(colorSum[0]); 416 | rgbBuffer[bufIndex + 1] = Math.round(colorSum[1]); 417 | rgbBuffer[bufIndex + 2] = Math.round(colorSum[2]); 418 | } 419 | } 420 | } 421 | } 422 | 423 | // Transfer interpolated colors to the texture 424 | for(var y = 0; y < height; y++) 425 | { 426 | for(var x = 0; x < width; x++) 427 | { 428 | var index = (y * width + x) * 4; 429 | var bufindex = (y * width + x) * 3; 430 | 431 | if ((rgbBuffer[bufindex + 0] != keyR) || 432 | (rgbBuffer[bufindex + 1] != keyG) || 433 | (rgbBuffer[bufindex + 2] != keyB)) 434 | { 435 | pixels[index + 0] = rgbBuffer[bufindex + 0]; 436 | pixels[index + 1] = rgbBuffer[bufindex + 1]; 437 | pixels[index + 2] = rgbBuffer[bufindex + 2]; 438 | } 439 | } 440 | } 441 | } 442 | 443 | /** Stores all loaded wads */ 444 | var loadedWads = new Array(); -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /readme: -------------------------------------------------------------------------------- 1 | === hlbsp viewer === 2 | 3 | Last edited: January 11th 2010 4 | 5 | 6 | = Author = 7 | The author of this project is Bernhard Manfred Gruber, 8 | a 20 year old student at the 9 | Upper Austrian University of Applied Science in Hagenberg (Austria). 10 | Feel free to send me a mail at: dixxi1@gmx.net 11 | 12 | 13 | = About = 14 | The hlbsp viewer is a client side html 5 application using JavaScript and 15 | WebGL to render bsp files. 16 | 17 | 18 | = Reasons = 19 | This project mainly targets the need of a free implementation of the .bsp 20 | (short for binary space partitioning) file format version 30 (used by the 21 | well-known HalfLife/GoldSrc engine), which has already been done by my old 22 | hlbsp project written in C/C++. 23 | You can find it here: http://sourceforge.net/projects/hlbsp/ 24 | During the work on this "reimplementation" I could refactor and improve some 25 | parts of the code as well as port the existing OpenGL implementation using 26 | mostly OpenGL 1.1 features (glBegin etc.) to meet the WebGL standard (which 27 | is basically OpenGL ES 2.0) by using advanced features like Buffer Objects. 28 | 29 | The second aspect of this code is to show an examplary WebGL implementation 30 | rendering something "more advanced" than cubes (as mostly used in WebGL 31 | tutorials) or anything similar. Additionally, it shows how to deal with binary 32 | data. Currently only in a very bleeding edge way (the working draft from 33 | Khronos specifying the DataView class has been released some weeks ago), but it 34 | should give developers an idea how to treat this issue. 35 | 36 | Finally I had to do some sort of project over the christmas holidays for my 37 | eb Design course at university, so this topic came in handy. 38 | 39 | 40 | = Usage = 41 | When the page has been opened in a supported web browser, you should see a 42 | black region in the middle. This is the element used to rener the 43 | scene. On the right there are several controls. The first one shows the 44 | current frames per second (FPS) as well as some WebGL related stuff. 45 | The second one is to select a bsp file from your local file system. You can 46 | either select a file or drag&drop it right on the control. The bsp file should 47 | be loaded, which might take some time (It takes my browser (Opera 12.00 alpha) 48 | roughly half a minute to load a bigger bsp file as parsing the binary file into 49 | an ArrayBuffer object currently takes very, very long. 50 | After loading, the file is parsed (calculating texture/lightmap coordinates, 51 | loading lightmaps, prepare the buffers etc.) before it is finally displayed. 52 | You will usually only see the lightmaps, as most of the textures are stored 53 | externally in so-called wad files. The wad files referenced by the bsp file 54 | should be listed after the loading process. Please select them from your local 55 | file system or drag&drop them on the control. You can also load wad files 56 | before loading a bsp file and loaded wads will not be unloaded when changing 57 | the loaded bsp file. 58 | The last control on the right is the log window showing some information at 59 | startup and also reports errors. 60 | 61 | = Controls = 62 | Mouse: You can use the mouse for looking around in the scene by pressing 63 | the left mouse button and dragging the cursor inside the canvas. 64 | W Move forward 65 | S Move backward 66 | A Move sidewards left (strafe) 67 | D Move sidewards right (strafe) 68 | Space/Shift Move up 69 | Ctrl Move down 70 | 71 | T Enable/Disable texture rendering 72 | L Enable/Disable lightmap rendering 73 | P Toggle wireframe mode 74 | C Toggle coordinate system 75 | 76 | 77 | = Implementation = 78 | The implementation covers the following aspects: 79 | > Initializing a WebGL context. 80 | > Reading a binary file. 81 | > Reading all data structurs the bsp file contains. 82 | > Computing the texture coordinates which are NOT stored in the bsp file. 83 | > Loading textures direclty stored in the bsp file. 84 | > Computing the lightmap coordinates and dimensions and fetch them from the raw 85 | lightmap data lump of the bsp file. 86 | > Reading and decompressing the Potentially Visible Set 87 | (PVS or often called VIS). 88 | > Parsing all the geometry into buffer objects for later rendering. 89 | > Rendering the whole scene using the bsp tree for organisation and the PVS to 90 | reduce the polygon count. 91 | > Reading textures from a wad archive. 92 | > Using shaders to render colors, textures, mix texture colors and perfom alpha 93 | testing. 94 | > Providing an example of how to navigate through a 3D scene with keyboard and 95 | mouse. 96 | > Collision detection. 97 | 98 | 99 | = Known issues = 100 | > Opera does not support drag&drop. 101 | > When using Chrome, you have to start the browser with the 102 | --allow-file-access-from-files 103 | command line flag in order to allow reading local files. 104 | > Internet Explorer is NOT supported (does not support WebGL). 105 | > As textures are loaded on the onload event of their images, they might not be 106 | visible yet the bsp file has finished parsing. Be patient, this may take 107 | several seconds for the onload threads (may be several thousands) to 108 | finish. --------------------------------------------------------------------------------