├── README.md ├── doc └── poster.pdf ├── include ├── J3DIMath.js ├── mersenne-twister.js └── webgl-ggc.js ├── main.html └── src └── slam.js /README.md: -------------------------------------------------------------------------------- 1 | # DP SLAM in JavaScript 2 | A JavaScript implementation of the basic 3 | [DP Slam](https://users.cs.duke.edu/~parr/dpslam/) algorithm. 4 | 5 | ## Features 6 | 7 | ### Modular Design 8 | A "modular" design that allows different map, motion, and sensor models. We 9 | first provide an implementation of a basic particle filter. We then use this 10 | particle filter to construct a generic implementation of the DP SLAM algorithm 11 | as a JavaScript class. 12 | 13 | Abstract concepts are described for map, motion, and sensor model objects. The 14 | DP SLAM object may then be instantiated with any parameters satisfying the 15 | necessary concepts. 16 | 17 | Concrete map, motion, and sensor model classes are provided that implement the 18 | ideas in the 19 | [DP SLAM 1.0 paper](http://people.ee.duke.edu/~lcarin/Lihan4.21.06a.pdf). 20 | 21 | ### Simulation 22 | A very basic simulation of a robot with a laser range sensor is provided in 23 | the main.html file. Walls may be added by clicking twice on the canvas. The 24 | simulation may be started and paused by pressing space. A particle is sampled 25 | from the DP SLAM particle filter at fixed time intervals. The grid shows what 26 | the current sampled particle predicts the environment to be. The green line shows the 27 | actual position and orientation of the robot. The blue line shows the 28 | what the current sampled particle predicts the robot's position and orientation 29 | to be. 30 | 31 | ### Funding 32 | This project was produced during an NSF funded research assistantship for the the 33 | ITEST research project (see [robotmoose.com](https://robotmoose.com/intro/)). 34 | -------------------------------------------------------------------------------- /doc/poster.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/permutationlock/slam_js/6c6d1625dfb5111fab3b410b5f75d98469de6acb/doc/poster.pdf -------------------------------------------------------------------------------- /include/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 | -------------------------------------------------------------------------------- /include/mersenne-twister.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | I've wrapped Makoto Matsumoto and Takuji Nishimura's code in a namespace 4 | so it's better encapsulated. Now you can have multiple random number generators 5 | and they won't stomp all over eachother's state. 6 | 7 | If you want to use this as a substitute for Math.random(), use the random() 8 | method like so: 9 | 10 | var m = new MersenneTwister(); 11 | var randomNumber = m.random(); 12 | 13 | You can also call the other genrand_{foo}() methods on the instance. 14 | 15 | If you want to use a specific seed in order to get a repeatable random 16 | sequence, pass an integer into the constructor: 17 | 18 | var m = new MersenneTwister(123); 19 | 20 | and that will always produce the same random sequence. 21 | 22 | Sean McCullough (banksean@gmail.com) 23 | */ 24 | 25 | /* 26 | A C-program for MT19937, with initialization improved 2002/1/26. 27 | Coded by Takuji Nishimura and Makoto Matsumoto. 28 | 29 | Before using, initialize the state by using init_genrand(seed) 30 | or init_by_array(init_key, key_length). 31 | 32 | Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, 33 | All rights reserved. 34 | 35 | Redistribution and use in source and binary forms, with or without 36 | modification, are permitted provided that the following conditions 37 | are met: 38 | 39 | 1. Redistributions of source code must retain the above copyright 40 | notice, this list of conditions and the following disclaimer. 41 | 42 | 2. Redistributions in binary form must reproduce the above copyright 43 | notice, this list of conditions and the following disclaimer in the 44 | documentation and/or other materials provided with the distribution. 45 | 46 | 3. The names of its contributors may not be used to endorse or promote 47 | products derived from this software without specific prior written 48 | permission. 49 | 50 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 51 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 52 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 53 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 54 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 55 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 58 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 59 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 | 62 | 63 | Any feedback is very welcome. 64 | http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html 65 | email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) 66 | */ 67 | 68 | var MersenneTwister = function(seed) { 69 | if (seed == undefined) { 70 | seed = new Date().getTime(); 71 | } 72 | /* Period parameters */ 73 | this.N = 624; 74 | this.M = 397; 75 | this.MATRIX_A = 0x9908b0df; /* constant vector a */ 76 | this.UPPER_MASK = 0x80000000; /* most significant w-r bits */ 77 | this.LOWER_MASK = 0x7fffffff; /* least significant r bits */ 78 | 79 | this.mt = new Array(this.N); /* the array for the state vector */ 80 | this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */ 81 | 82 | this.init_genrand(seed); 83 | } 84 | 85 | /* initializes mt[N] with a seed */ 86 | MersenneTwister.prototype.init_genrand = function(s) { 87 | this.mt[0] = s >>> 0; 88 | for (this.mti=1; this.mti>> 30); 90 | this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253) 91 | + this.mti; 92 | /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ 93 | /* In the previous versions, MSBs of the seed affect */ 94 | /* only MSBs of the array mt[]. */ 95 | /* 2002/01/09 modified by Makoto Matsumoto */ 96 | this.mt[this.mti] >>>= 0; 97 | /* for >32 bit machines */ 98 | } 99 | } 100 | 101 | /* initialize by an array with array-length */ 102 | /* init_key is the array for initializing keys */ 103 | /* key_length is its length */ 104 | /* slight change for C++, 2004/2/26 */ 105 | MersenneTwister.prototype.init_by_array = function(init_key, key_length) { 106 | var i, j, k; 107 | this.init_genrand(19650218); 108 | i=1; j=0; 109 | k = (this.N>key_length ? this.N : key_length); 110 | for (; k; k--) { 111 | var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30) 112 | this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525))) 113 | + init_key[j] + j; /* non linear */ 114 | this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ 115 | i++; j++; 116 | if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; } 117 | if (j>=key_length) j=0; 118 | } 119 | for (k=this.N-1; k; k--) { 120 | var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30); 121 | this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941)) 122 | - i; /* non linear */ 123 | this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ 124 | i++; 125 | if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; } 126 | } 127 | 128 | this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */ 129 | } 130 | 131 | /* generates a random number on [0,0xffffffff]-interval */ 132 | MersenneTwister.prototype.genrand_int32 = function() { 133 | var y; 134 | var mag01 = new Array(0x0, this.MATRIX_A); 135 | /* mag01[x] = x * MATRIX_A for x=0,1 */ 136 | 137 | if (this.mti >= this.N) { /* generate N words at one time */ 138 | var kk; 139 | 140 | if (this.mti == this.N+1) /* if init_genrand() has not been called, */ 141 | this.init_genrand(5489); /* a default initial seed is used */ 142 | 143 | for (kk=0;kk>> 1) ^ mag01[y & 0x1]; 146 | } 147 | for (;kk>> 1) ^ mag01[y & 0x1]; 150 | } 151 | y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK); 152 | this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1]; 153 | 154 | this.mti = 0; 155 | } 156 | 157 | y = this.mt[this.mti++]; 158 | 159 | /* Tempering */ 160 | y ^= (y >>> 11); 161 | y ^= (y << 7) & 0x9d2c5680; 162 | y ^= (y << 15) & 0xefc60000; 163 | y ^= (y >>> 18); 164 | 165 | return y >>> 0; 166 | } 167 | 168 | /* generates a random number on [0,0x7fffffff]-interval */ 169 | MersenneTwister.prototype.genrand_int31 = function() { 170 | return (this.genrand_int32()>>>1); 171 | } 172 | 173 | /* generates a random number on [0,1]-real-interval */ 174 | MersenneTwister.prototype.genrand_real1 = function() { 175 | return this.genrand_int32()*(1.0/4294967295.0); 176 | /* divided by 2^32-1 */ 177 | } 178 | 179 | /* generates a random number on [0,1)-real-interval */ 180 | MersenneTwister.prototype.random = function() { 181 | return this.genrand_int32()*(1.0/4294967296.0); 182 | /* divided by 2^32 */ 183 | } 184 | 185 | /* generates a random number on (0,1)-real-interval */ 186 | MersenneTwister.prototype.genrand_real3 = function() { 187 | return (this.genrand_int32() + 0.5)*(1.0/4294967296.0); 188 | /* divided by 2^32 */ 189 | } 190 | 191 | /* generates a random number on [0,1) with 53-bit resolution*/ 192 | MersenneTwister.prototype.genrand_res53 = function() { 193 | var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6; 194 | return(a*67108864.0+b)*(1.0/9007199254740992.0); 195 | } 196 | 197 | /* These real versions are due to Isaku Wada, 2002/01/09 added */ 198 | -------------------------------------------------------------------------------- /include/webgl-ggc.js: -------------------------------------------------------------------------------- 1 | // webgl-ggc.js 2 | // VERSION 0.6 3 | // Glenn G. Chappell 4 | // 4 Dec 2013 5 | // 6 | // For CS 381 Fall 2013 7 | // GGC's WebGL Utilities 8 | // Uses J3DIMath.js for matrix type (J3DIMatrix4) 9 | // 10 | // History: 11 | // - v0.1-0.4: Miscellaneous foolishness. 12 | // - v0.5: Write drawSquare, drawCube, drawSphere, drawCylinder. 13 | // Add optional alpha param to drawXxx functions. 14 | // - v0.6: Fix incorrect texture coordinates in shapes. 15 | 16 | // Internal-use items have names ending with '_internal_'. All others 17 | // are available for external use. 18 | 19 | // Assumptions about WebGL contexts: 20 | // - A context passed to any of the functions in this file will have 21 | // members pMatrix, mvMatrix, and tMatrix, which are J3DIMatrix4 22 | // objects. (The idea is that these represent the projection matrix, 23 | // model/view matrix, and texture matrix, respectively.) 24 | // Note that function getGlContext creates these members in the returned 25 | // context, and sets them to identity matrices. 26 | 27 | // Assumptions about shaders: 28 | // - Vertex coordinates, color, normal vector, texture coordinates, and 29 | // tangent vector, IF THESE ARE USED, are the respective variables as 30 | // follows: 31 | // 32 | // attribute vec4 vertex_attr; 33 | // attribute vec4 color_attr; 34 | // attribute vec3 normal_attr; 35 | // attribute vec4 texcoord_attr; 36 | // attribute vec3 tangent_attr; 37 | // 38 | // - The projection matrix, model/view matrix, normal matrix, and 39 | // texture matrix, IF THESE ARE USED, are the respective variables as 40 | // follows: 41 | // 42 | // uniform mat4 projectionMatrix; 43 | // uniform mat4 modelViewMatrix; 44 | // uniform mat3 normalMatrix; 45 | // uniform mat4 textureMatrix; 46 | // 47 | // Note that, if a variable is not used in a shader, then it need not be 48 | // declared. 49 | 50 | 51 | // errOut 52 | // Given string, attempts to print it as an error message. 53 | // - First tries to find HTML element with Id = 'err'. If found, appends 54 | // message to its content. 55 | // - Otherwise, tries to log message to window console. 56 | // - If all the above fail, then does nothing. 57 | function errOut(msg) 58 | { 59 | var f = arguments.callee; // Current function 60 | if (!f.inited) 61 | { 62 | f.inited = true; 63 | f.which = 0; 64 | } 65 | ++f.which; 66 | var fullmsg = '[APPLICATION ERROR] ' + msg; 67 | 68 | var e = document.getElementById('err'); 69 | if (e) 70 | { 71 | e.innerHTML += 72 | encodeStringForHTMLText_internal_(fullmsg) + '
'; 73 | return; 74 | } 75 | 76 | if ('console' in window && 77 | 'error' in window.console && 78 | typeof window.console.error == 'function') 79 | { 80 | window.console.error(fullmsg); 81 | return; 82 | } 83 | } 84 | 85 | 86 | // encodeStringForHTMLText_internal_ 87 | // Given an ASCII string, encode it so that it can be included as 88 | // viewable text in an HTML document. Returns encoded string. 89 | function encodeStringForHTMLText_internal_(str) 90 | { 91 | return str 92 | .replace(/\&/g, '&') 93 | .replace(/\/g, '>'); 95 | } 96 | 97 | 98 | // getElapsedTime 99 | // Returns number of seconds (to nearest thousandth?) since previous 100 | // call. Returns 0.0 on first call. If max is given, limits returned 101 | // values to at most max. 102 | function getElapsedTime(max) 103 | { 104 | var f = arguments.callee; // Current function 105 | if (!f.inited) 106 | { 107 | f.inited = true; 108 | f.savetime = new Date(); 109 | return 0.; 110 | } 111 | else 112 | { 113 | var oldtime = f.savetime; 114 | f.savetime = new Date(); 115 | var etime = (f.savetime - oldtime) / 1000.; 116 | if (typeof max != 'undefined' && etime > max) 117 | return max; 118 | return etime; 119 | } 120 | } 121 | 122 | 123 | // getMousePos 124 | // Given a canvas and an event object, return JS object holding mouse 125 | // pos in canvas coordinates (x & y members of object). 126 | function getMousePos(canvas, evt) 127 | { 128 | if (!canvas || !canvas.getBoundingClientRect) 129 | { 130 | errOut(arguments.callee.name + ': ' + 131 | 'No canvas.getBoundingClientRect'); 132 | return null; 133 | } 134 | if (!evt || !evt.clientX || !evt.clientY) 135 | { 136 | errOut(arguments.callee.name + ': ' + 137 | 'No event passed'); 138 | return null; 139 | } 140 | 141 | var rect = canvas.getBoundingClientRect(); 142 | return { 143 | x: evt.clientX-rect.left, 144 | y: evt.clientY-rect.top 145 | }; 146 | } 147 | 148 | 149 | // whereAmI 150 | // Given matrix cammat, finds the point that cammat takes to (the 151 | // homogeneous form of) the origin. Thus, if cammat is the camera- 152 | // transformation matrix, then we return the camera position in world 153 | // coordinates. 154 | // 155 | // Return value is JS object with camera-position coordinates in x, y, z 156 | // members. 157 | function whereAmI(cammat) 158 | { 159 | var mat = new J3DIMatrix4(cammat); 160 | mat.invert(); 161 | var arr = mat.getAsFloat32Array(); 162 | return { 163 | x: arr[12]/arr[15], 164 | y: arr[13]/arr[15], 165 | z: arr[14]/arr[15] 166 | }; 167 | } 168 | 169 | 170 | // checkContext_internal_ 171 | // Checks whether ctx is a valid WebGL context. If not, signals error 172 | // using given function name. 173 | function checkContext_internal_(ctx, funcname) 174 | { 175 | if (typeof ctx != 'object' || !('clearColor' in ctx)) 176 | { 177 | errOut(funcname + ': ' + 178 | 'Called without valid WebGL context'); 179 | return false; 180 | } 181 | return true; 182 | } 183 | 184 | 185 | // getProgram_internal_ 186 | // Given WebGL context, returns active shader program object, or null if 187 | // none. If none, signals error using given function name. 188 | function getProgram_internal_(ctx, funcname) 189 | { 190 | if (!checkContext_internal_(ctx, funcname)) 191 | return null; 192 | var shaderProgram = ctx.getParameter(ctx.CURRENT_PROGRAM); 193 | if (!shaderProgram) 194 | { 195 | errOut(funcname + ': ' + 196 | 'Called with context having no active shaders'); 197 | return null; 198 | } 199 | return shaderProgram; 200 | } 201 | 202 | 203 | // getAttribLocs 204 | // Given WebGL context, returns locations of standard attribute 205 | // variables in an array. Each array item is attribute location or -1 if 206 | // attribute not found in shaders. Attributes + their indices are as 207 | // follows: 208 | // 209 | // Index Attribute 210 | // 0 vertex_attr 211 | // 1 color_attr 212 | // 2 normal_attr 213 | // 3 texcooord_attr 214 | // 4 tangent_attr 215 | // 216 | // If no active shaders, signals error, returns null. 217 | function getAttribLocs(ctx) 218 | { 219 | var shaderProgram = 220 | getProgram_internal_(ctx, arguments.callee.name); 221 | if (!shaderProgram) 222 | return null; 223 | 224 | return [ 225 | ctx.getAttribLocation(shaderProgram, 'vertex_attr'), 226 | ctx.getAttribLocation(shaderProgram, 'color_attr'), 227 | ctx.getAttribLocation(shaderProgram, 'normal_attr'), 228 | ctx.getAttribLocation(shaderProgram, 'texcoord_attr'), 229 | ctx.getAttribLocation(shaderProgram, 'tangent_attr') 230 | ]; 231 | } 232 | 233 | 234 | // drawSquare 235 | // Draw solid square with given side length, centered at origin, in 236 | // x,y-plane, axis-aligned. 237 | // 238 | // ctx is WebGL context. r, g, b, a are optional arguments giving color. 239 | // Uses standard attribute, uniform shader variable names; see comments 240 | // @ beginning of this file. 241 | function drawSquare(ctx, 242 | size, 243 | r, g, b, a) 244 | { 245 | // Get attribute locations 246 | var attriblocs = getAttribLocs(ctx); 247 | if (!attriblocs) 248 | { 249 | errOut(arguments.callee.name + ': ' + 250 | 'Could not get attribute locations'); 251 | return; 252 | } 253 | 254 | // Set up parameters 255 | if (typeof r != 'number') r = 0.7; 256 | if (typeof g != 'number') g = 0.7; 257 | if (typeof b != 'number') b = 0.7; 258 | if (typeof a != 'number') a = 1.0; 259 | 260 | // Create VBOs 261 | var buffs = new Array(5); 262 | var datas = new Array(5); 263 | for (var i = 0; i < 5; ++i) 264 | { 265 | buffs[i] = ctx.createBuffer(); 266 | var components = (i == 2 || i == 4) ? 3 : 4; 267 | datas[i] = new Float32Array(components*4); 268 | } 269 | for (var i = 0; i < 4; ++i) 270 | { 271 | var x = (i == 1 || i == 2) ? 1. : 0.; 272 | var y = (i == 2 || i == 3) ? 1. : 0.; 273 | 274 | var b4 = 4*i; // Base for indices 275 | var b3 = 3*i; 276 | 277 | // vertex coords 278 | datas[0][b4+0] = (x-0.5) * size; 279 | datas[0][b4+1] = (y-0.5) * size; 280 | datas[0][b4+2] = 0.; 281 | datas[0][b4+3] = 1.; 282 | 283 | // color 284 | datas[1][b4+0] = r; 285 | datas[1][b4+1] = g; 286 | datas[1][b4+2] = b; 287 | datas[1][b4+3] = a; 288 | 289 | // normal 290 | datas[2][b3+0] = 0.; 291 | datas[2][b3+1] = 0.; 292 | datas[2][b3+2] = 1.; 293 | 294 | // texture coords 295 | datas[3][b4+0] = x; 296 | datas[3][b4+1] = y; 297 | datas[3][b4+2] = 0.; 298 | datas[3][b4+3] = 1.; 299 | 300 | // tangent 301 | datas[4][b3+0] = 1.; 302 | datas[4][b3+1] = 0.; 303 | datas[4][b3+2] = 0.; 304 | } 305 | for (var i in attriblocs) 306 | { 307 | if (attriblocs[i] == -1) 308 | continue; 309 | var components = (i == 2 || i == 4) ? 3 : 4; 310 | ctx.bindBuffer(ctx.ARRAY_BUFFER, buffs[i]); 311 | ctx.bufferData( 312 | ctx.ARRAY_BUFFER, datas[i], ctx.STATIC_DRAW); 313 | ctx.vertexAttribPointer( 314 | attriblocs[i], components, ctx.FLOAT, false, 0, 0); 315 | } 316 | 317 | // Set up uniforms, enable attributes 318 | sendMatrices(ctx); 319 | for (var i in attriblocs) 320 | if (attriblocs[i] != -1) 321 | ctx.enableVertexAttribArray(attriblocs[i]); 322 | 323 | // Draw with VBO 324 | ctx.drawArrays(ctx.TRIANGLE_FAN, 0, 4); 325 | 326 | // Disable attributes 327 | for (var i in attriblocs) 328 | if (attriblocs[i] != -1) 329 | ctx.disableVertexAttribArray(attriblocs[i]); 330 | 331 | // Delete buffer objects 332 | for (i in buffs) 333 | ctx.deleteBuffer(buffs[i]); 334 | } 335 | 336 | 337 | // drawCube 338 | // Draw solid cube -- like glutSolidCube. 339 | // 340 | // ctx is WebGL context. r, g, b, a are optional arguments giving color. 341 | // Uses standard attribute, uniform shader variable names; see comments 342 | // @ beginning of this file. 343 | function drawCube(ctx, 344 | size, 345 | r, g, b, a) 346 | { 347 | // Set up parameters 348 | if (typeof r != 'number') r = 0.7; 349 | if (typeof g != 'number') g = 0.7; 350 | if (typeof b != 'number') b = 0.7; 351 | if (typeof a != 'number') a = 1.0; 352 | 353 | // +x face 354 | pushMvMatrix(ctx); 355 | ctx.mvMatrix.rotate(90., 0.,1.,0.); 356 | ctx.mvMatrix.translate(0., 0., size/2.); 357 | drawSquare(ctx, size, r, g, b, a); 358 | popMvMatrix(ctx); 359 | 360 | // -x face 361 | pushMvMatrix(ctx); 362 | ctx.mvMatrix.rotate(-90., 0.,1.,0.); 363 | ctx.mvMatrix.translate(0., 0., size/2.); 364 | drawSquare(ctx, size, r, g, b, a); 365 | popMvMatrix(ctx); 366 | 367 | // +y face 368 | pushMvMatrix(ctx); 369 | ctx.mvMatrix.rotate(-90., 1.,0.,0.); 370 | ctx.mvMatrix.translate(0., 0., size/2.); 371 | drawSquare(ctx, size, r, g, b, a); 372 | popMvMatrix(ctx); 373 | 374 | // -y face 375 | pushMvMatrix(ctx); 376 | ctx.mvMatrix.rotate(90., 1.,0.,0.); 377 | ctx.mvMatrix.translate(0., 0., size/2.); 378 | drawSquare(ctx, size, r, g, b, a); 379 | popMvMatrix(ctx); 380 | 381 | // +z face 382 | pushMvMatrix(ctx); 383 | ctx.mvMatrix.translate(0., 0., size/2.); 384 | drawSquare(ctx, size, r, g, b, a); 385 | popMvMatrix(ctx); 386 | 387 | // -z face 388 | pushMvMatrix(ctx); 389 | ctx.mvMatrix.rotate(180., 0.,1.,0.); 390 | ctx.mvMatrix.translate(0., 0., size/2.); 391 | drawSquare(ctx, size, r, g, b, a); 392 | popMvMatrix(ctx); 393 | } 394 | 395 | 396 | // drawSphere 397 | // Draw solid sphere -- like glutSolidSphere. 398 | // 399 | // ctx is WebGL context. r, g, b, a are optional arguments giving color. 400 | // Uses standard attribute, uniform shader variable names; see comments 401 | // @ beginning of this file. 402 | function drawSphere(ctx, 403 | radius, slices, stacks, 404 | r, g, b, a) 405 | { 406 | // Get attribute locations 407 | var attriblocs = getAttribLocs(ctx); 408 | if (!attriblocs) 409 | { 410 | errOut(arguments.callee.name + ': ' + 411 | 'Could not get attribute locations'); 412 | return; 413 | } 414 | 415 | // Set up parameters 416 | if (typeof r != 'number') r = 0.7; 417 | if (typeof g != 'number') g = 0.7; 418 | if (typeof b != 'number') b = 0.7; 419 | if (typeof a != 'number') a = 1.0; 420 | 421 | // Create VBOs 422 | var buffs = new Array(5); 423 | var datas = new Array(5); 424 | for (var i = 0; i < 5; ++i) 425 | { 426 | buffs[i] = ctx.createBuffer(); 427 | var components = (i == 2 || i == 4) ? 3 : 4; 428 | datas[i] = new Float32Array(components*(slices+1)*(stacks+1)); 429 | } 430 | for (var i1 = 0; i1 <= stacks; ++i1) 431 | { 432 | var tc1 = i1/stacks; 433 | var ang1 = tc1 * Math.PI; 434 | for (var i2 = 0; i2 <= slices; ++i2) 435 | { 436 | var tc2 = i2/slices; 437 | var ang2 = tc2 * 2.*Math.PI; 438 | 439 | var nx = Math.cos(ang2)*Math.sin(ang1); 440 | var ny = Math.sin(ang2)*Math.sin(ang1); 441 | var nz = Math.cos(ang1); 442 | 443 | var b4 = 4*(i1*(slices+1) + i2); // Base for indices 444 | var b3 = 3*(i1*(slices+1) + i2); 445 | 446 | // vertex coords 447 | datas[0][b4+0] = radius * nx; 448 | datas[0][b4+1] = radius * ny; 449 | datas[0][b4+2] = radius * nz; 450 | datas[0][b4+3] = 1.; 451 | 452 | // color 453 | datas[1][b4+0] = r; 454 | datas[1][b4+1] = g; 455 | datas[1][b4+2] = b; 456 | datas[1][b4+3] = a; 457 | 458 | // normal 459 | datas[2][b3+0] = nx 460 | datas[2][b3+1] = ny 461 | datas[2][b3+2] = nz 462 | 463 | // texture coords 464 | datas[3][b4+0] = tc1; 465 | datas[3][b4+1] = tc2; 466 | datas[3][b4+2] = 0.; 467 | datas[3][b4+3] = 1.; 468 | 469 | // tangent 470 | datas[4][b3+0] = Math.cos(ang2)*Math.cos(ang1); 471 | datas[4][b3+1] = Math.sin(ang2)*Math.cos(ang1); 472 | datas[4][b3+2] = -Math.sin(ang1); 473 | } 474 | } 475 | for (var i in attriblocs) 476 | { 477 | if (attriblocs[i] == -1) 478 | continue; 479 | var components = (i == 2 || i == 4) ? 3 : 4; 480 | ctx.bindBuffer(ctx.ARRAY_BUFFER, buffs[i]); 481 | ctx.bufferData( 482 | ctx.ARRAY_BUFFER, datas[i], ctx.STATIC_DRAW); 483 | ctx.vertexAttribPointer( 484 | attriblocs[i], components, ctx.FLOAT, false, 0, 0); 485 | } 486 | 487 | // Set up uniforms, enable attributes 488 | sendMatrices(ctx); 489 | for (var i in attriblocs) 490 | if (attriblocs[i] != -1) 491 | ctx.enableVertexAttribArray(attriblocs[i]); 492 | 493 | // Draw with EBO 494 | var ebuff = ctx.createBuffer(); 495 | var edata = new Uint16Array(2*(slices+1)); 496 | for (var i1 = 0; i1 < stacks; ++i1) 497 | { 498 | for (var i2 = 0; i2 <= slices; ++i2) 499 | { 500 | edata[2*i2+0] = i1*(slices+1) + i2; 501 | edata[2*i2+1] = (i1+1)*(slices+1) + i2; 502 | } 503 | 504 | ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, ebuff); 505 | ctx.bufferData( 506 | ctx.ELEMENT_ARRAY_BUFFER, edata, ctx.DYNAMIC_DRAW); 507 | ctx.drawElements( 508 | ctx.TRIANGLE_STRIP, 2*(slices+1), ctx.UNSIGNED_SHORT, 0); 509 | } 510 | 511 | // Disable attributes 512 | for (var i in attriblocs) 513 | if (attriblocs[i] != -1) 514 | ctx.disableVertexAttribArray(attriblocs[i]); 515 | 516 | // Delete buffer objects 517 | for (i in buffs) 518 | ctx.deleteBuffer(buffs[i]); 519 | ctx.deleteBuffer(ebuff); 520 | } 521 | 522 | 523 | // drawTorus 524 | // Draw solid torus -- like glutSolidTorus. 525 | // 526 | // ctx is WebGL context. r, g, b, a are optional arguments giving color. 527 | // Uses standard attribute, uniform shader variable names; see comments 528 | // @ beginning of this file. 529 | function drawTorus(ctx, 530 | smrad, bigrad, smsubdivs, bigsubdivs, 531 | r, g, b, a) 532 | { 533 | // Get attribute locations 534 | var attriblocs = getAttribLocs(ctx); 535 | if (!attriblocs) 536 | { 537 | errOut(arguments.callee.name + ': ' + 538 | 'Could not get attribute locations'); 539 | return; 540 | } 541 | 542 | // Set up parameters 543 | if (typeof r != 'number') r = 0.7; 544 | if (typeof g != 'number') g = 0.7; 545 | if (typeof b != 'number') b = 0.7; 546 | if (typeof a != 'number') a = 1.0; 547 | 548 | // Create VBOs 549 | var buffs = new Array(5); 550 | var datas = new Array(5); 551 | for (var i = 0; i < 5; ++i) 552 | { 553 | buffs[i] = ctx.createBuffer(); 554 | var components = (i == 2 || i == 4) ? 3 : 4; 555 | datas[i] = new Float32Array(components*(smsubdivs+1)*(bigsubdivs+1)); 556 | } 557 | for (var i1 = 0; i1 <= bigsubdivs; ++i1) 558 | { 559 | var tc1 = i1/bigsubdivs; 560 | var ang1 = tc1 * 2.0*Math.PI; 561 | for (var i2 = 0; i2 <= smsubdivs; ++i2) 562 | { 563 | var tc2 = i2/smsubdivs; 564 | var ang2 = tc2 * 2.0*Math.PI; 565 | 566 | var b4 = 4*(i1*(smsubdivs+1) + i2); // Base for indices 567 | var b3 = 3*(i1*(smsubdivs+1) + i2); 568 | 569 | // vertex coords 570 | datas[0][b4+0] = 571 | (bigrad+smrad*Math.cos(ang2))*Math.cos(ang1); 572 | datas[0][b4+1] = 573 | (bigrad+smrad*Math.cos(ang2))*Math.sin(ang1); 574 | datas[0][b4+2] = 575 | smrad*Math.sin(ang2); 576 | datas[0][b4+3] = 577 | 1.; 578 | 579 | // color 580 | datas[1][b4+0] = r; 581 | datas[1][b4+1] = g; 582 | datas[1][b4+2] = b; 583 | datas[1][b4+3] = a; 584 | 585 | datas[2][b3+0] = Math.cos(ang2)*Math.cos(ang1); 586 | datas[2][b3+1] = Math.cos(ang2)*Math.sin(ang1); 587 | datas[2][b3+2] = Math.sin(ang2); 588 | 589 | // texture coords 590 | datas[3][b4+0] = tc1; 591 | datas[3][b4+1] = tc2; 592 | datas[3][b4+2] = 0.; 593 | datas[3][b4+3] = 1.; 594 | 595 | // tangent 596 | datas[4][b3+0] = -Math.sin(ang1); 597 | datas[4][b3+1] = Math.cos(ang1); 598 | datas[4][b3+2] = 0.; 599 | } 600 | } 601 | for (var i in attriblocs) 602 | { 603 | if (attriblocs[i] == -1) 604 | continue; 605 | var components = (i == 2 || i == 4) ? 3 : 4; 606 | ctx.bindBuffer(ctx.ARRAY_BUFFER, buffs[i]); 607 | ctx.bufferData( 608 | ctx.ARRAY_BUFFER, datas[i], ctx.STATIC_DRAW); 609 | ctx.vertexAttribPointer( 610 | attriblocs[i], components, ctx.FLOAT, false, 0, 0); 611 | } 612 | 613 | // Set up uniforms, enable attributes 614 | sendMatrices(ctx); 615 | for (var i in attriblocs) 616 | if (attriblocs[i] != -1) 617 | ctx.enableVertexAttribArray(attriblocs[i]); 618 | 619 | // Draw with EBO 620 | var ebuff = ctx.createBuffer(); 621 | var edata = new Uint16Array(2*(smsubdivs+1)); 622 | for (var i1 = 0; i1 < bigsubdivs; ++i1) 623 | { 624 | for (var i2 = 0; i2 <= smsubdivs; ++i2) 625 | { 626 | edata[2*i2+0] = 627 | i1*(smsubdivs+1) + i2; 628 | edata[2*i2+1] = 629 | (i1+1)*(smsubdivs+1) + i2; 630 | } 631 | 632 | ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, ebuff); 633 | ctx.bufferData( 634 | ctx.ELEMENT_ARRAY_BUFFER, edata, ctx.DYNAMIC_DRAW); 635 | ctx.drawElements( 636 | ctx.TRIANGLE_STRIP, 2*(smsubdivs+1), ctx.UNSIGNED_SHORT, 0); 637 | } 638 | 639 | // Disable attributes 640 | for (var i in attriblocs) 641 | if (attriblocs[i] != -1) 642 | ctx.disableVertexAttribArray(attriblocs[i]); 643 | 644 | // Delete buffer objects 645 | for (i in buffs) 646 | ctx.deleteBuffer(buffs[i]); 647 | ctx.deleteBuffer(ebuff); 648 | } 649 | 650 | 651 | // drawCylinder 652 | // Draw a cylinder, radius 1, length 2, centered at origin, around 653 | // x-axis. Uses given number of subdivisions. 654 | // 655 | // ctx is WebGL context. r, g, b, a are optional arguments giving color. 656 | // Uses standard attribute, uniform shader variable names; see comments 657 | // @ beginning of this file. 658 | function drawCylinder(ctx, 659 | subdivs, 660 | r, g, b, a) 661 | { 662 | // Get attribute locations 663 | var attriblocs = getAttribLocs(ctx); 664 | if (!attriblocs) 665 | { 666 | errOut(arguments.callee.name + ': ' + 667 | 'Could not get attribute locations'); 668 | return; 669 | } 670 | 671 | // Set up parameters 672 | if (typeof r != 'number') r = 0.7; 673 | if (typeof g != 'number') g = 0.7; 674 | if (typeof b != 'number') b = 0.7; 675 | if (typeof a != 'number') a = 1.0; 676 | var halflen = 1.; // Half of cylinder's length 677 | var radius = 1.; // Cylinder's radius 678 | var sd1 = subdivs; // Subdivisions along cylinder 679 | var sd2 = 4*subdivs; // Subdivisions around cylinder 680 | 681 | // Create VBOs 682 | 683 | var buffs = new Array(5); 684 | var datas = new Array(5); 685 | for (var i = 0; i < 5; ++i) 686 | { 687 | buffs[i] = ctx.createBuffer(); 688 | var components = (i == 2 || i == 4) ? 3 : 4; 689 | datas[i] = new Float32Array(components*(sd2+1)*(sd1+1)); 690 | } 691 | 692 | for (var i1 = 0; i1 <= sd1; ++i1) 693 | { 694 | var tc1 = i1/sd1; 695 | var x = halflen - tc1 * 2.*halflen; 696 | for (var i2 = 0; i2 <= sd2; ++i2) 697 | { 698 | var tc2 = i2/sd2; 699 | var ang2 = tc2 * 2.*Math.PI; 700 | var ny = Math.cos(ang2); 701 | var nz = Math.sin(ang2); 702 | 703 | var b4 = 4*(i1*(sd2+1) + i2); // Base for indices 704 | var b3 = 3*(i1*(sd2+1) + i2); 705 | 706 | // vertex coords 707 | datas[0][b4+0] = x; 708 | datas[0][b4+1] = radius * ny; 709 | datas[0][b4+2] = radius * nz; 710 | datas[0][b4+3] = 1.; 711 | 712 | // color 713 | datas[1][b4+0] = r; 714 | datas[1][b4+1] = g; 715 | datas[1][b4+2] = b; 716 | datas[1][b4+3] = a; 717 | 718 | // normal 719 | datas[2][b3+0] = 0.; 720 | datas[2][b3+1] = ny; 721 | datas[2][b3+2] = nz; 722 | 723 | // texture coords 724 | datas[3][b4+0] = tc1; 725 | datas[3][b4+1] = tc2; 726 | datas[3][b4+2] = 0.; 727 | datas[3][b4+3] = 1.; 728 | 729 | // tangent 730 | datas[4][b3+0] = 1.; 731 | datas[4][b3+1] = 0.; 732 | datas[4][b3+2] = 0.; 733 | } 734 | } 735 | for (var i in attriblocs) 736 | { 737 | if (attriblocs[i] == -1) 738 | continue; 739 | var components = (i == 2 || i == 4) ? 3 : 4; 740 | ctx.bindBuffer(ctx.ARRAY_BUFFER, buffs[i]); 741 | ctx.bufferData( 742 | ctx.ARRAY_BUFFER, datas[i], ctx.STATIC_DRAW); 743 | ctx.vertexAttribPointer( 744 | attriblocs[i], components, ctx.FLOAT, false, 0, 0); 745 | } 746 | 747 | // Set up uniforms, enable attributes 748 | sendMatrices(ctx); 749 | for (var i in attriblocs) 750 | if (attriblocs[i] != -1) 751 | ctx.enableVertexAttribArray(attriblocs[i]); 752 | 753 | // Draw with EBO 754 | var ebuff = ctx.createBuffer(); 755 | var edata = new Uint16Array(2*(sd2+1)); 756 | for (var i1 = 0; i1 < sd1; ++i1) 757 | { 758 | for (var i2 = 0; i2 <= sd2; ++i2) 759 | { 760 | edata[2*i2+0] = i1*(sd2+1) + i2; 761 | edata[2*i2+1] = (i1+1)*(sd2+1) + i2; 762 | } 763 | 764 | ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, ebuff); 765 | ctx.bufferData( 766 | ctx.ELEMENT_ARRAY_BUFFER, edata, ctx.DYNAMIC_DRAW); 767 | ctx.drawElements( 768 | ctx.TRIANGLE_STRIP, 2*(sd2+1), ctx.UNSIGNED_SHORT, 0); 769 | } 770 | 771 | // Disable attributes 772 | for (var i in attriblocs) 773 | if (attriblocs[i] != -1) 774 | ctx.disableVertexAttribArray(attriblocs[i]); 775 | 776 | // Delete buffer objects 777 | for (i in buffs) 778 | ctx.deleteBuffer(buffs[i]); 779 | ctx.deleteBuffer(ebuff); 780 | } 781 | 782 | 783 | // normalMatrixAsFloat32Array_internal_ 784 | // Computer normal matrix (as Float32Array of 9 items) based on given 785 | // model/view matrix. 786 | function normalMatrixAsFloat32Array_internal_(mvMat) 787 | { 788 | // Find transpose of inverse of model/view 789 | var inverseMat = new J3DIMatrix4(); 790 | inverseMat.load(mvMat); 791 | inverseMat.invert(); 792 | 793 | // Normal matrix is transpose of upper-left 3x3 of above 794 | var nm4 = inverseMat.getAsFloat32Array(); 795 | var nm3 = new Float32Array([ 796 | nm4[0], nm4[4], nm4[8], 797 | nm4[1], nm4[5], nm4[9], 798 | nm4[2], nm4[6], nm4[10]]); 799 | 800 | return nm3; 801 | } 802 | 803 | // sendMatrices 804 | // Assumes ctx has members pMatrix, mvMatrix, tMatrix that are 805 | // J3DIMatrix4 objects. Sends the three matrices to current active 806 | // shaders: uniform mat4 variables with names projectionMatrix, 807 | // modelViewMatrix, and textureMatrix -- if these exist in the shaders. 808 | // Computes normal matrix and sends this to uniform mat3 variable with 809 | // name normalMatrix -- if this exists in the shaders. 810 | function sendMatrices(ctx) 811 | { 812 | var shaderProgram = 813 | getProgram_internal_(ctx, arguments.callee.name); 814 | if (!shaderProgram) 815 | return; 816 | 817 | var loc; // Location of vars in shaders 818 | var mvMatrixErrorPrinted = false; 819 | // True if no-mvMatrix-member error msg has been printed 820 | 821 | // Projection matrix 822 | loc = ctx.getUniformLocation(shaderProgram, 'projectionMatrix'); 823 | if (loc != -1) 824 | { 825 | if (!ctx.pMatrix) 826 | errOut(arguments.callee.name + ': ' + 827 | 'No pMatrix member in WebGL context'); 828 | else 829 | ctx.pMatrix.setUniform(ctx, loc, false); 830 | } 831 | 832 | // Model/view matrix 833 | loc = ctx.getUniformLocation(shaderProgram, 'modelViewMatrix'); 834 | if (loc != -1) 835 | { 836 | if (!ctx.mvMatrix) 837 | { 838 | errOut(arguments.callee.name + ': ' + 839 | 'No mvMatrix member in WebGL context'); 840 | mvMatrixErrorPrinted = true; 841 | } 842 | else 843 | ctx.mvMatrix.setUniform(ctx, loc, false); 844 | } 845 | 846 | // Normal matrix 847 | loc = ctx.getUniformLocation(shaderProgram, 'normalMatrix'); 848 | if (loc != -1) 849 | { 850 | if (!ctx.mvMatrix && !mvMatrixErrorPrinted) 851 | errOut(arguments.callee.name + ': ' + 852 | 'No mvMatrix member in WebGL context'); 853 | else 854 | { 855 | // Compute normal matrix 856 | var nMatArray = normalMatrixAsFloat32Array_internal_( 857 | ctx.mvMatrix); 858 | 859 | // Send it to the shaders 860 | ctx.uniformMatrix3fv(loc, false, nMatArray); 861 | } 862 | } 863 | 864 | // Texture matrix 865 | loc = ctx.getUniformLocation(shaderProgram, 'textureMatrix'); 866 | if (loc != -1) 867 | { 868 | if (!ctx.tMatrix) 869 | errOut(arguments.callee.name + ': ' + 870 | 'No tMatrix member in WebGL context'); 871 | else 872 | ctx.tMatrix.setUniform(ctx, loc, false); 873 | } 874 | } 875 | 876 | 877 | // makeShaderObject_internal_ 878 | // Given WebGL context, string holding GLSL source for shader, and 879 | // Boolean indicating vertex (true) or fragment (false) shader, 880 | // returns shader object, or null on failure. 881 | function makeShaderObject_internal_(ctx, shaderText, isVert) 882 | { 883 | var shaderType = isVert ? ctx.VERTEX_SHADER : ctx.FRAGMENT_SHADER; 884 | var shaderStr = isVert ? 'vertex' : 'fragment'; 885 | 886 | // Create shader object 887 | var shader = ctx.createShader(shaderType); 888 | if (!shader) 889 | { 890 | errOut(arguments.callee.name + ': ' + 891 | 'Cannot create ' + shaderStr + ' shader object'); 892 | return null; 893 | } 894 | 895 | // Load, compile shader source 896 | ctx.shaderSource(shader, shaderText); 897 | ctx.compileShader(shader); 898 | 899 | // Check compile status 900 | var compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS); 901 | if (!compiled) 902 | { 903 | var error = ctx.getShaderInfoLog(shader); 904 | errOut(arguments.callee.name + ': ' + 905 | 'Error compiling ' + shaderStr + ' shader, msg ='); 906 | errOut(error); 907 | ctx.deleteShader(shader); 908 | return null; 909 | } 910 | 911 | return shader; 912 | } 913 | 914 | 915 | // makeProgramObject 916 | // Given WebGL context, strings holding source for GLSL vertex, 917 | // fragment shaders, returns program object, or null on failure. 918 | function makeProgramObject(ctx, vShaderText, fShaderText) 919 | { 920 | if (!checkContext_internal_(ctx, arguments.callee.name)) 921 | return null; 922 | 923 | // Make shaders 924 | var vShader = makeShaderObject_internal_(ctx, vShaderText, true); 925 | if (!vShader) 926 | return null; 927 | var fShader = makeShaderObject_internal_(ctx, fShaderText, false); 928 | if (!fShader) 929 | return null; 930 | 931 | // Make program object 932 | var shaderProgram = ctx.createProgram(); 933 | if (!shaderProgram) 934 | return null; 935 | 936 | // Attach shaders to program object 937 | ctx.attachShader(shaderProgram, vShader); 938 | ctx.attachShader(shaderProgram, fShader); 939 | 940 | // Link shaders 941 | ctx.linkProgram(shaderProgram); 942 | 943 | // Check link status 944 | var linked = 945 | ctx.getProgramParameter(shaderProgram, ctx.LINK_STATUS); 946 | if (!linked) 947 | { 948 | var error = ctx.getProgramInfoLog(shaderProgram); 949 | errOut(arguments.callee.name + ': ' + 950 | 'shader linking error, msg ='); 951 | errOut(error); 952 | 953 | ctx.deleteProgram(shaderProgram); 954 | ctx.deleteProgram(fShader); 955 | ctx.deleteProgram(vShader); 956 | 957 | return null; 958 | } 959 | 960 | return shaderProgram; 961 | } 962 | 963 | 964 | // makeProgramObjectFromIds 965 | // Given WebGL context, strings holding element Ids for vertex, fragment 966 | // shaders, returns program object, or null on failure. 967 | function makeProgramObjectFromIds(ctx, vShaderId, fShaderId) 968 | { 969 | if (!checkContext_internal_(ctx, arguments.callee.name)) 970 | return null; 971 | if (typeof vShaderId != 'string') 972 | { 973 | errOut(arguments.callee.name + ': ' + 974 | 'Vertex shader Id not given'); 975 | return null; 976 | } 977 | if (typeof fShaderId != 'string') 978 | { 979 | errOut(arguments.callee.name + ': ' + 980 | 'Fragment shader Id not given'); 981 | return null; 982 | } 983 | 984 | var vShaderScript = document.getElementById(vShaderId); 985 | if (!vShaderScript) 986 | { 987 | errOut(arguments.callee.name + ': ' + 988 | 'Vertex shader script [' + vShaderId + '] not found'); 989 | return null; 990 | } 991 | if (vShaderScript.nodeName.toLowerCase() != 'script' || 992 | !vShaderScript.type || 993 | vShaderScript.type != 'x-shader/x-vertex') 994 | { 995 | errOut(arguments.callee.name + ': ' + 996 | 'Script [' + vShaderId + '] is not a vertex shader'); 997 | return null; 998 | } 999 | 1000 | var fShaderScript = document.getElementById(fShaderId); 1001 | if (!fShaderScript) 1002 | { 1003 | errOut(arguments.callee.name + ': ' + 1004 | 'Vertex shader script [' + fShaderId + '] not found'); 1005 | return null; 1006 | } 1007 | if (fShaderScript.nodeName.toLowerCase() != 'script' || 1008 | !fShaderScript.type || 1009 | fShaderScript.type != 'x-shader/x-fragment') 1010 | { 1011 | errOut(arguments.callee.name + ': ' + 1012 | 'Script [' + fShaderId + '] is not a fragment shader'); 1013 | return null; 1014 | } 1015 | 1016 | return makeProgramObject(ctx, 1017 | vShaderScript.text, fShaderScript.text); 1018 | } 1019 | 1020 | 1021 | // getCanvas 1022 | // Given Id of canvas element, returns canvas object, or null if 1023 | // failure. 1024 | function getCanvas(canvasId) 1025 | { 1026 | if (typeof canvasId != 'string') 1027 | { 1028 | errOut(arguments.callee.name + ': ' + 1029 | 'Canvas Id not given'); 1030 | return null; 1031 | } 1032 | 1033 | var canvas = document.getElementById(canvasId); 1034 | if (!canvas) 1035 | { 1036 | errOut(arguments.callee.name + ': ' + 1037 | 'Canvas [' + canvasId + '] not found'); 1038 | return null; 1039 | } 1040 | if (canvas.nodeName.toLowerCase() != 'canvas') 1041 | { 1042 | errOut(arguments.callee.name + ': ' + 1043 | 'Elements [' + canvasId + '] is not a canvas'); 1044 | return null; 1045 | } 1046 | 1047 | return canvas; 1048 | } 1049 | 1050 | 1051 | // pushPMatrix, pushMvMatrix, pushTMatrix 1052 | // In the given context, push the appropriate matrix (pMatrix, mvMatrix, 1053 | // or tMatrix) on the associated stack. 1054 | // Assumes context has members pMatrix and pStack_internal_ (and similar 1055 | // members for mv, t). 1056 | function pushPMatrix(ctx) 1057 | { 1058 | ctx.pStack_internal_.push(new J3DIMatrix4(ctx.pMatrix)); 1059 | } 1060 | 1061 | function pushMvMatrix(ctx) 1062 | { 1063 | ctx.mvStack_internal_.push(new J3DIMatrix4(ctx.mvMatrix)); 1064 | } 1065 | 1066 | function pushTMatrix(ctx) 1067 | { 1068 | ctx.tStack_internal_.push(new J3DIMatrix4(ctx.tMatrix)); 1069 | } 1070 | 1071 | 1072 | // popPMatrix, popMvMatrix, popTMatrix 1073 | // In the given context, pop the appropriate matrix (pMatrix, mvMatrix, 1074 | // or tMatrix) off the associated stack. Gives error message if called 1075 | // with empty stack. 1076 | // Assumes context has members pMatrix and pStack_internal_ (and similar 1077 | // members for mv, t). 1078 | function popPMatrix(ctx) 1079 | { 1080 | if (ctx.pStack_internal_.length == 0) 1081 | { 1082 | errOut(arguments.callee.name + ': ' + 1083 | 'Projection matrix stack popped when empty'); 1084 | return; 1085 | } 1086 | ctx.pMatrix.load(ctx.pStack_internal_.pop()); 1087 | } 1088 | 1089 | function popMvMatrix(ctx) 1090 | { 1091 | if (ctx.mvStack_internal_.length == 0) 1092 | { 1093 | errOut(arguments.callee.name + ': ' + 1094 | 'Model/view matrix stack popped when empty'); 1095 | return; 1096 | } 1097 | ctx.mvMatrix.load(ctx.mvStack_internal_.pop()); 1098 | } 1099 | 1100 | function popTMatrix(ctx) 1101 | { 1102 | if (ctx.tStack_internal_.length == 0) 1103 | { 1104 | errOut(arguments.callee.name + ': ' + 1105 | 'Texture matrix stack popped when empty'); 1106 | return; 1107 | } 1108 | ctx.tMatrix.load(ctx.tStack_internal_.pop()); 1109 | } 1110 | 1111 | 1112 | // getGlContext 1113 | // Given canvas object, returns WebGL context, or null on failure. 1114 | // Returned context has the following members added: 1115 | // - J3DIMatrix4 objects: 1116 | // - mvMatrix, pMatrix, tMatrix 1117 | // - Array objects (accessed via push, pop functions): 1118 | // - pStack_internal_, mvStack_internal_, tStack_internal_ 1119 | function getGlContext(canvas) 1120 | { 1121 | if (typeof canvas != 'object' || 1122 | !('nodeName' in canvas) || 1123 | typeof canvas.nodeName != 'string' || 1124 | canvas.nodeName.toLowerCase() != 'canvas') 1125 | { 1126 | errOut(arguments.callee.name + ': ' + 1127 | 'Canvas object not given'); 1128 | return null; 1129 | } 1130 | 1131 | var ctx = null; 1132 | try 1133 | { 1134 | // Below based on webgl-utils.js from Google 1135 | var webglnames = ['webgl', 1136 | 'experimental-webgl', 1137 | 'webkit-3d', 1138 | 'moz-webgl']; 1139 | for (var i in webglnames) 1140 | { 1141 | ctx = canvas.getContext(webglnames[i]); 1142 | if (ctx) 1143 | break; 1144 | } 1145 | } 1146 | catch (e) {} 1147 | 1148 | if (!ctx) 1149 | { 1150 | var errmsg = 'Could not initialize WebGL'; 1151 | document.write(errmsg); 1152 | errOut(arguments.callee.name + ': ' + 1153 | errmsg); 1154 | return null; 1155 | } 1156 | 1157 | // Create projection, model/view, texture matrices 1158 | ctx.pMatrix = new J3DIMatrix4(); 1159 | ctx.pMatrix.makeIdentity(); 1160 | ctx.mvMatrix = new J3DIMatrix4(); 1161 | ctx.mvMatrix.makeIdentity(); 1162 | ctx.tMatrix = new J3DIMatrix4(); 1163 | ctx.tMatrix.makeIdentity(); 1164 | 1165 | // Create projection, model/view, texture stacks 1166 | ctx.pStack_internal_ = new Array(); 1167 | ctx.mvStack_internal_ = new Array(); 1168 | ctx.tStack_internal_ = new Array(); 1169 | 1170 | return ctx; 1171 | } 1172 | 1173 | 1174 | // requestAnimFrame_internal_ 1175 | // Returns function window.requestAnimationFrame, or, if that does not 1176 | // exist, a function that does much the same thing. 1177 | // Based on webgl-utils.js from Google. 1178 | requestAnimFrame_internal_ = (function() 1179 | { 1180 | return window.requestAnimationFrame || 1181 | window.webkitRequestAnimationFrame || 1182 | window.mozRequestAnimationFrame || 1183 | window.oRequestAnimationFrame || 1184 | window.msRequestAnimationFrame || 1185 | function(callback, element) 1186 | { 1187 | window.setTimeout(callback, 1000/60); 1188 | }; 1189 | })(); 1190 | 1191 | 1192 | // animate 1193 | // Given function to call, does repeated animation frames, calling the 1194 | // given function at each frame. 1195 | function animate(func) 1196 | { 1197 | // Are we given a function? 1198 | if (typeof func != 'function') 1199 | { 1200 | errOut(arguments.callee.name + ': ' + 1201 | 'Argument is not a function'); 1202 | return; 1203 | } 1204 | 1205 | // Set up next frame: call this function with same argument. 1206 | var thisfunc = arguments.callee; 1207 | requestAnimFrame_internal_(function() { thisfunc(func); }); 1208 | 1209 | // Call given function 1210 | func(); 1211 | } 1212 | 1213 | -------------------------------------------------------------------------------- /main.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | Wildfire Simulation 11 | 12 | 17 | 18 | 20 | 21 |
22 |
23 | 25 |
26 | 27 | 50 | 51 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 779 | 780 | 781 | -------------------------------------------------------------------------------- /src/slam.js: -------------------------------------------------------------------------------- 1 | /* 2 | * slam.js 3 | * Author: Aven Bross (dabross@alaska.edu) 4 | * 5 | * Particle filter occupancy grid SLAM with distributed particle maps. 6 | */ 7 | 8 | /* 9 | * sample_normal 10 | * Sample a random point from a normal distribution with the given parameters. 11 | */ 12 | function sample_normal(mean = 0.0, variance = 1.0) { 13 | var u = 1 - Math.random(); // Subtraction to flip [0, 1) to (0, 1]. 14 | var v = 1 - Math.random(); 15 | return mean + Math.sqrt( -2.0 * variance * Math.log( u ) ) 16 | * Math.cos( 2.0 * Math.PI * v ); 17 | } 18 | 19 | /* 20 | * prob_normal 21 | * Computes the probability density function of a normal distribution with the 22 | * given parameters at the given evaluation value. 23 | */ 24 | function prob_normal(value, mean = 0.0, variance = 1.0) { 25 | return (1.0 / Math.sqrt(2.0 * Math.PI * variance)) 26 | * Math.exp(-0.5 * Math.pow(value - mean, 2) / variance); 27 | } 28 | 29 | /* 30 | * location_t 31 | * Stores coordinates and orientation of an object. Allows addition, 32 | * and comparison of locations. 33 | */ 34 | var location_t = function(x, y, angle) { 35 | var _this = this; 36 | _this.x = x; 37 | _this.y = y; 38 | _this.angle = angle; 39 | 40 | _this.add = function(r, angle) { 41 | _this.angle += angle; 42 | _this.x += Math.cos(_this.angle) * r; 43 | _this.y += Math.sin(_this.angle) * r; 44 | }; 45 | 46 | _this.equals = function(location) { 47 | return ( 48 | _this.x == location.x && 49 | _this.y == location.y && 50 | _this.angle == location.angle 51 | ); 52 | }; 53 | 54 | _this.distance = function(location) { 55 | return Math.sqrt( 56 | Math.pow(_this.x - location.x, 2) + 57 | Math.pow(_this.y - location.y, 2) 58 | ); 59 | }; 60 | }; 61 | 62 | /* 63 | * copy_location 64 | * Construct a location_t from another. 65 | */ 66 | function copy_location(location) { 67 | return new location_t(location.x, location.y, location.angle); 68 | } 69 | 70 | /* 71 | * control_t 72 | * Stores odometry information between two timesteps. 73 | */ 74 | var control_t = function(current_location, old_location) { 75 | var _this = this; 76 | _this.current = current_location; 77 | _this.last = old_location; 78 | 79 | _this.update = function(location) { 80 | _this.last = _this.current; 81 | _this.current = location; 82 | }; 83 | 84 | _this.still = function() { 85 | return _this.current.equals(_this.last); 86 | }; 87 | }; 88 | 89 | /* 90 | * odometry_motion_model_t 91 | * Takes variance parameters a1, a2, a3, and a4. Provides function to predict 92 | * new locations from a given location based on odometry controls. 93 | */ 94 | var odometry_motion_model_t = function(a1, a2, a3, a4) { 95 | var _this = this; 96 | _this.a1 = a1; 97 | _this.a2 = a2; 98 | _this.a3 = a3; 99 | _this.a4 = a4; 100 | 101 | _this.sample = function(control, location) { 102 | if(control.still()) return location; 103 | 104 | var dx_bar = control.current.x - control.last.x; 105 | var dy_bar = control.current.y - control.last.y; 106 | 107 | var delta_r1 = Math.atan2(dy_bar, dx_bar) - control.last.angle; 108 | var delta_trans = Math.sqrt(Math.pow(dx_bar, 2) + Math.pow(dy_bar, 2)); 109 | var delta_r2 = control.current.angle - control.last.angle - delta_r1; 110 | 111 | var dhat_r1 = sample_normal( 112 | delta_r1, 113 | _this.a1 * Math.pow(delta_r1, 2) 114 | + _this.a2 * Math.pow(delta_trans, 2) 115 | ); 116 | var dhat_trans = sample_normal( 117 | delta_trans, 118 | _this.a3 * Math.pow(delta_trans, 2) 119 | + _this.a4 * Math.pow(delta_r1, 2) 120 | + _this.a4 * Math.pow(delta_r2, 2) 121 | ); 122 | var dhat_r2 = sample_normal( 123 | delta_r2, 124 | _this.a1 * Math.pow(delta_r2, 2) 125 | + _this.a2 * Math.pow(delta_trans, 2) 126 | ); 127 | 128 | var new_location = copy_location(location); 129 | new_location.add(dhat_trans, dhat_r1); 130 | new_location.add(0.0, dhat_r2); 131 | 132 | return new_location; 133 | }; 134 | }; 135 | 136 | /* 137 | * ray_trace 138 | * Trace a rasterized line from start_location to end_location, calling 139 | * evalute_cell on each cell. The evaluate_cell function must take x and y 140 | * coordinates, and n the number of cells remaining on the line. We also 141 | * compute a distance_per_cell value to approximately handle the fact that 142 | * diagonal traces will hit more cells than horizontal traces. 143 | */ 144 | function ray_trace(start_location, end_location, evaluate_cell) { 145 | var x0 = start_location.x, y0 = start_location.y; 146 | var x1 = end_location.x, y1 = end_location.y; 147 | var dx = Math.abs(x1 - x0), dy = Math.abs(y1 - y0); 148 | 149 | var x = Math.floor(x0), y = Math.floor(y0); 150 | var dt_dx = 1.0 / dx, dt_dy = 1.0 / dy; 151 | 152 | var t = 0, t_next_vertical, t_next_horizontal; 153 | var n = 1, x_inc, y_inc; 154 | 155 | if (dx == 0) { 156 | x_inc = 0; 157 | t_next_horizontal = dt_dx; // infinity 158 | } 159 | else if (x1 > x0) { 160 | x_inc = 1; 161 | n += Math.floor(x1) - x; 162 | t_next_horizontal = (Math.floor(x0) + 1 - x0) * dt_dx; 163 | } 164 | else { 165 | x_inc = -1; 166 | n += x - Math.floor(x1); 167 | t_next_horizontal = (x0 - Math.floor(x0)) * dt_dx; 168 | } 169 | 170 | if (dy == 0) { 171 | y_inc = 0; 172 | t_next_vertical = dt_dy; // infinity 173 | } 174 | else if (y1 > y0) { 175 | y_inc = 1; 176 | n += Math.floor(y1) - y; 177 | t_next_vertical = (Math.floor(y0) + 1 - y0) * dt_dy; 178 | } 179 | else { 180 | y_inc = -1; 181 | n += y - Math.floor(y1); 182 | t_next_vertical = (y0 - Math.floor(y0)) * dt_dy; 183 | } 184 | 185 | for(; n > 0; --n) { 186 | if(evaluate_cell(x, y, n - 1)) return; 187 | if (t_next_vertical < t_next_horizontal) { 188 | y += y_inc; 189 | t = t_next_vertical; 190 | t_next_vertical += dt_dy; 191 | } 192 | else { 193 | x += x_inc; 194 | t = t_next_horizontal; 195 | t_next_horizontal += dt_dx; 196 | } 197 | } 198 | }; 199 | 200 | /* 201 | * beam_measurement_model_t 202 | * Takes range sensor variance. Provides functions to compute the probability 203 | * of measurement vectors and individual measurements for a given map and 204 | * robot location. 205 | */ 206 | var beam_measurement_model_t = function(variance, max_ray, samples, size) { 207 | var _this = this; 208 | _this.variance = variance; 209 | _this.max_ray = max_ray; 210 | _this.size = size; 211 | _this.samples = samples; 212 | _this.range_size = size / samples; 213 | _this.start_index = 0; 214 | _this.delta_rot = 2.0 * Math.PI / _this.size; 215 | 216 | _this.prob_ray = function(robot_location, hit_location, map_lookup) { 217 | var end_location = copy_location(robot_location); 218 | end_location.add(_this.max_ray, hit_location.angle); 219 | var exp_hit = null; 220 | 221 | ray_trace( 222 | robot_location, 223 | end_location, 224 | function(x, y, n) { 225 | if(map_lookup(x, y)) { 226 | exp_hit = new location_t(x + 0.5, y + 0.5, 0.0); 227 | 228 | return true; 229 | } 230 | 231 | return false; 232 | } 233 | ); 234 | 235 | if(exp_hit != null) { 236 | var actual = robot_location.distance(hit_location); 237 | var expected = robot_location.distance(exp_hit); 238 | 239 | return prob_normal(actual, expected, _this.variance) * 100.0; 240 | } 241 | 242 | return 1.0; 243 | }; 244 | 245 | _this.prob = function(robot_location, measurement, map_lookup) { 246 | var q = 1.0; 247 | var rot = 1.0 * _this.delta_rot * _this.start_index; 248 | 249 | for(var i = _this.start_index; i < _this.size; i += _this.range_size) { 250 | if(measurement[i] != 0.0) { 251 | var hit_location = copy_location(robot_location); 252 | hit_location.add(measurement[i], rot); 253 | 254 | p = _this.prob_ray(robot_location, hit_location, map_lookup); 255 | q *= p; 256 | } 257 | 258 | rot += _this.delta_rot * _this.range_size; 259 | } 260 | //console.log(q); 261 | return q; 262 | }; 263 | 264 | _this.update = function(robot_location, measurement, map_lookup, map_update) { 265 | var rot = 1.0 * _this.delta_rot * _this.start_index; 266 | 267 | for(var i = _this.start_index; i < _this.size; i += _this.range_size) { 268 | if(measurement[i] != 0.0) { 269 | var hit_location = copy_location(robot_location); 270 | hit_location.add(measurement[i], rot); 271 | 272 | //console.log("measurement " + i + " length " + measurement[i] 273 | //+ " hit (" + hit_location.x + ", " + hit_location.y + ")"); 274 | 275 | ray_trace( 276 | robot_location, 277 | hit_location, 278 | function(x, y, n) { 279 | if(n > 1 && map_lookup(x, y) == true) return true; 280 | else if(n == 0) { 281 | map_update(true, x, y); 282 | } 283 | return false; 284 | } 285 | ); 286 | } 287 | 288 | rot += _this.delta_rot * _this.range_size; 289 | } 290 | }; 291 | 292 | _this.increment = function() { 293 | _this.start_index = (_this.start_index + 1) % _this.range_size; 294 | }; 295 | }; 296 | 297 | /* 298 | * particle_filter_t 299 | * A simple particle filter to estimate a posterior distribution over a finite 300 | * number of sample points. Call predict and weight each timestep to estimate 301 | * the posterior distribution with controls and measurements. Call resample to 302 | * eliminate low probability particles when needed to produce better estimates 303 | * with lower particle counts. 304 | */ 305 | var particle_filter_t = function(prediction_model, weight_model, size, 306 | elimination_factor = 0.01) 307 | { 308 | var _this = this; 309 | _this.size = size; 310 | _this.n = 1.0 / size; 311 | _this.threshold = elimination_factor * _this.n; 312 | _this.prediction_model = prediction_model; 313 | _this.weight_model = weight_model; 314 | _this.weights = []; 315 | for(var i = 0; i < _this.size; ++i) { 316 | _this.weights[i] = _this.n; 317 | } 318 | 319 | _this.predict = function(particles, control) { 320 | var new_particles = []; 321 | for(var i = 0; i < _this.size; ++i) { 322 | new_particles[i] = _this.prediction_model(particles[i], control); 323 | } 324 | return new_particles; 325 | }; 326 | 327 | _this.weight = function(particles, measurement) { 328 | var sum = 0.0; 329 | for(var i = 0; i < _this.size; ++i) { 330 | if(_this.weights[i] > _this.threshold) { 331 | _this.weights[i] *= _this.weight_model(particles[i], measurement); 332 | } 333 | else { 334 | _this.weights[i] = 0.0; 335 | } 336 | sum += _this.weights[i]; 337 | } 338 | 339 | for(var i = 0; i < _this.size; ++i) { 340 | if(sum > 0.0000000000000000000000001) { 341 | _this.weights[i] /= sum; 342 | } 343 | else { 344 | _this.weights[i] = _this.n; 345 | } 346 | } 347 | }; 348 | 349 | _this.effective_sample_size = function() { 350 | var sum = 0.0; 351 | for(var i = 0; i < _this.size; ++i) { 352 | sum += Math.pow(_this.weights[i], 2); 353 | } 354 | 355 | return 1.0 / sum; 356 | }; 357 | 358 | _this.resample = function(particles) { 359 | var new_particles = []; 360 | var r = Math.random() * _this.n, c = _this.weights[0], i = 0; 361 | 362 | for(var m = 0; m < _this.size; ++m) { 363 | var u = r + m * _this.n; 364 | while(u > c) { 365 | ++i; 366 | c += _this.weights[i]; 367 | } 368 | new_particles.push(particles[i]); 369 | } 370 | 371 | for(var i = 0; i < _this.size; ++i) { 372 | _this.weights[i] = _this.n; 373 | } 374 | 375 | return new_particles; 376 | }; 377 | 378 | _this.sample = function(particles) { 379 | var r = Math.random(), c = _this.weights[0], i = 0; 380 | while(r > c) { 381 | ++i; 382 | c += _this.weights[i]; 383 | } 384 | return particles[i]; 385 | }; 386 | }; 387 | 388 | /* 389 | * dp_map_t 390 | * Simple two dimensional distributed particle map with binary occupancy values. 391 | */ 392 | var dp_map_t = function() { 393 | var _this = this; 394 | _this.map = []; 395 | 396 | _this.lookup_by_id = function(x, y, id) { 397 | if(typeof _this.map[x] !== "undefined" && 398 | typeof _this.map[x][y] !== "undefined" && 399 | typeof _this.map[x][y][id] !== "undefined") 400 | { 401 | return _this.map[x][y][id]; 402 | } 403 | return -1; 404 | }; 405 | 406 | _this.lookup = function(x, y, dp_node) { 407 | var temp = dp_node; 408 | do { 409 | var val = _this.lookup_by_id(x, y, temp.id); 410 | if(val == 1) { 411 | return true; 412 | } 413 | else if(val == 0) { 414 | return false; 415 | } 416 | } while((temp = temp.parent) != null); 417 | return false; 418 | }; 419 | 420 | _this.update_by_id = function(value, x, y, id) { 421 | if(typeof _this.map[x] == "undefined") { 422 | _this.map[x] = []; 423 | } 424 | if(typeof _this.map[x][y] == "undefined") { 425 | _this.map[x][y] = {}; 426 | } 427 | _this.map[x][y][id] = value; 428 | //console.log("setting map[" + x + "][" + y + "][" + id + " = " + value); 429 | }; 430 | 431 | _this.update = function(value, x, y, dp_node) { 432 | var temp = dp_node; 433 | do { 434 | if(_this.lookup_by_id(x, y, temp.id) != -1) { 435 | return false; 436 | } 437 | } while((temp = temp.parent) != null); 438 | _this.update_by_id(value, x, y, dp_node.id); 439 | return true; 440 | }; 441 | 442 | _this.erase = function(x, y, id) { 443 | delete _this.map[x][y][id]; 444 | }; 445 | 446 | _this.rename = function(x, y, old_id, new_id) { 447 | _this.map[x][y][new_id] = _this.map[x][y][old_id]; 448 | _this.erase(x, y, old_id); 449 | }; 450 | 451 | _this.get_map = function(x_min, x_max, y_min, y_max, dp_node) { 452 | var map = []; 453 | for(var x = x_min; x < x_max; ++x) { 454 | map[x] = []; 455 | for(var y = y_min; y < y_max; ++y) { 456 | map[x][y] = _this.lookup(x, y, dp_node); 457 | } 458 | } 459 | return map; 460 | }; 461 | }; 462 | 463 | /* 464 | * dp_node_t 465 | * Node class to construct distributed particle trees. 466 | */ 467 | var dp_node_t = function(id, location, parent) { 468 | var _this = this; 469 | _this.id = id; 470 | _this.location = copy_location(location); 471 | if(parent != null) { 472 | parent.leaf = false; 473 | parent.children += 1; 474 | } 475 | _this.parent = parent; 476 | _this.leaf = false; 477 | _this.children = 0; 478 | _this.modified_cells = []; 479 | 480 | _this.add_cell = function(x, y) { 481 | _this.modified_cells.push({ "x" : x, "y" : y }); 482 | }; 483 | 484 | _this.trim = function(map) { 485 | if(_this.parent.id == 0) { 486 | //console.log("hit root"); 487 | return; 488 | } 489 | 490 | if(!_this.leaf && _this.children == 0) { 491 | //console.log("trimming node " + _this.id); 492 | _this.parent.children -= 1; 493 | for(var i = 0; i < _this.modified_cells.length; ++i) { 494 | map.erase( 495 | _this.modified_cells[i].x, 496 | _this.modified_cells[i].y, 497 | _this.id 498 | ); 499 | } 500 | _this.parent.trim(map); 501 | _this.parent = null; 502 | } 503 | else if(_this.parent.children == 1 && _this.parent.id != 0) { 504 | //console.log("merging node " + _this.id + " with node " + _this.parent.id); 505 | for(var i = 0; i < _this.modified_cells.length; ++i) { 506 | map.rename( 507 | _this.modified_cells[i].x, 508 | _this.modified_cells[i].y, 509 | _this.id, 510 | _this.parent.id 511 | ); 512 | _this.parent.modified_cells.push(_this.modified_cells[i]); 513 | } 514 | 515 | _this.modified_cells = _this.parent.modified_cells; 516 | _this.id = _this.parent.id; 517 | 518 | _this.parent = _this.parent.parent; 519 | _this.trim(map); 520 | } 521 | else { 522 | _this.parent.trim(map); 523 | } 524 | }; 525 | }; 526 | 527 | /* 528 | * dp_slam_t 529 | * Distributed particle slam object. Estimates posterior of robot pose and 530 | * binary occupancy map with particle filter based on the given motion and 531 | * measurement models. 532 | */ 533 | var dp_slam_t = function(size, motion_model, measurement_model, frac = 0.5) { 534 | var _this = this; 535 | _this.size = size; 536 | _this.resample_size = _this.size * frac; 537 | _this.next_id = 0; 538 | _this.root = new dp_node_t(_this.next_id++, (new location_t(0.0, 0.0, 0.0)), null); 539 | _this.motion_model = motion_model; 540 | _this.measurement_model = measurement_model; 541 | _this.particles = []; 542 | _this.map = new dp_map_t; 543 | for(var i = 0; i < _this.size; ++i) { 544 | _this.particles.push(new dp_node_t( 545 | _this.next_id++, 546 | _this.root.location, 547 | _this.root 548 | )); 549 | } 550 | _this.particle_filter = new particle_filter_t( 551 | function(dp_node, control) { 552 | var location = _this.motion_model.sample(control, dp_node.location); 553 | return new dp_node_t(_this.next_id++, location, dp_node); 554 | }, 555 | function(dp_node, measurement) { 556 | return _this.measurement_model.prob( 557 | dp_node.location, 558 | measurement, 559 | function(x, y) { 560 | return _this.map.lookup(x, y, dp_node); 561 | } 562 | ); 563 | }, 564 | _this.size 565 | ); 566 | 567 | _this.update = function(control, measurement) { 568 | //console.log("predicting particles"); 569 | _this.particles = _this.particle_filter.predict(_this.particles, control); 570 | 571 | //console.log("weighting particles"); 572 | _this.particle_filter.weight(_this.particles, measurement); 573 | 574 | //console.log("checking for resample particles"); 575 | console.log(_this.particle_filter.effective_sample_size()); 576 | if(_this.particle_filter.effective_sample_size() < _this.resample_size) { 577 | console.log("resampling particles"); 578 | var new_particles = _this.particle_filter.resample(_this.particles); 579 | 580 | for(var i = 0; i < _this.size; ++i) { 581 | new_particles[i].leaf = true; 582 | } 583 | 584 | //console.log("trimming particle tree"); 585 | for(var i = 0; i < _this.size; ++i) { 586 | _this.particles[i].trim(_this.map); 587 | } 588 | 589 | _this.particles = new_particles; 590 | } 591 | else { 592 | console.log("trimming particle tree"); 593 | for(var i = 0; i < _this.size; ++i) { 594 | _this.particles[i].leaf = true; 595 | _this.particles[i].trim(_this.map); 596 | } 597 | } 598 | 599 | //console.log("updating maps"); 600 | for(var i = 0; i < _this.size; ++i) { 601 | var dp_node = _this.particles[i]; 602 | _this.measurement_model.update( 603 | dp_node.location, 604 | measurement, 605 | function(x, y) { 606 | return _this.map.lookup(x, y, dp_node); 607 | }, 608 | function(value, x, y) { 609 | if(_this.map.update(value, x, y, dp_node)) { 610 | dp_node.add_cell(x, y); 611 | } 612 | } 613 | ); 614 | } 615 | 616 | _this.measurement_model.increment(); 617 | }; 618 | 619 | _this.sample = function(x_min, x_max, y_min, y_max) { 620 | var dp_node = _this.particle_filter.sample(_this.particles); 621 | return { 622 | location : dp_node.location, 623 | map : _this.map.get_map( 624 | x_min, x_max, 625 | y_min, y_max, 626 | dp_node 627 | ) 628 | }; 629 | }; 630 | }; 631 | --------------------------------------------------------------------------------