Skip to content
This repository
Browse code

Added an experimental .obj to HTML converter

  • Loading branch information...
commit 8231826a98a1496e2e61baba6ded0db44658ba53 1 parent a28a843
Victor Porof authored August 07, 2012
65  src/utils/Blimp/blimp.css
... ...
@@ -0,0 +1,65 @@
  1
+body {
  2
+  background: #000 url("img/noise.png");
  3
+  position: fixed;
  4
+  width: 100%;
  5
+  height: 100%;
  6
+  margin: 0;
  7
+  font-family: monospace;
  8
+  text-shadow: 0 4px 24px #0f0, 0 3px 2px #000, 0 1px #000;
  9
+  color: #0f6;
  10
+}
  11
+
  12
+* ::-moz-selection {
  13
+  background: #040;
  14
+}
  15
+
  16
+a {
  17
+  background: #020;
  18
+  color: #afc;
  19
+  outline: none;
  20
+}
  21
+
  22
+mark {
  23
+  background: #030;
  24
+  color: inherit;
  25
+}
  26
+
  27
+#hello {
  28
+  margin-top: 130px;
  29
+  text-align: center;
  30
+  font-size: 16px;
  31
+}
  32
+
  33
+#sadpanda {
  34
+  font-size: 20px;
  35
+  color: #fff;
  36
+}
  37
+
  38
+#progress {
  39
+  font-weight: 600;
  40
+  font-size: 16px;
  41
+  text-shadow: 0 0 6px #afa;
  42
+  color: #0fa;
  43
+}
  44
+
  45
+#importing {
  46
+  visibility: hidden;
  47
+  font-size: 12px;
  48
+  color: #040;
  49
+}
  50
+
  51
+#bottom {
  52
+  position: absolute;
  53
+  bottom: 30px;
  54
+  left: 30px;
  55
+  font-size: 12px;
  56
+}
  57
+
  58
+#bound {
  59
+  background: transparent;
  60
+  width: 40px;
  61
+  border: 1px solid #040;
  62
+  text-align: center;
  63
+  font: inherit;
  64
+  color: #fff;
  65
+}
38  src/utils/Blimp/blimp.html
... ...
@@ -0,0 +1,38 @@
  1
+<!DOCTYPE html>
  2
+<html>
  3
+
  4
+  <head>
  5
+    <meta charset="UTF-8"/>
  6
+    <title>Blimp!</title>
  7
+    <link href="blimp.css" rel="stylesheet" type="text/css" />
  8
+  </head>
  9
+
  10
+  <body>
  11
+    <div id="hello">
  12
+      <p id="sadpanda">
  13
+        Whoa there! This is bleeding edge stuff, you'll need
  14
+        <a href='http://nightly.mozilla.org/'>Firefox Nightly</a>
  15
+        to play with this.
  16
+      </p>
  17
+      <p>
  18
+        "Blimp!" is a nifty tool which turns a 3D model into basic HTML voxels.<br>
  19
+        <mark>Drag and drop</mark> an .obj file, wait a bit, then start Tilt.
  20
+      </p>
  21
+      <div id="progress"></div>
  22
+      <div id="importing">hardcore importing, stand by...</div>
  23
+    </div>
  24
+    <div id="bottom">
  25
+      <div>
  26
+        You can even play with this magic number!
  27
+      </div>
  28
+      <input id="bound" type="number" value="350">
  29
+      &lt;~ generated mesh dimensions (try not to enter a very large value;
  30
+      <a href="http://twitter.com/victorporof">#kthxbye</a>)
  31
+    </div>
  32
+    <a href="http://github.com/victorporof"><img style="position: absolute; top: 0; left: 0; border: 0;" src="https://a248.e.akamai.net/assets.github.com/img/bec6c51521dcc8148146135149fe06a9cc737577/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f6c6566745f6461726b626c75655f3132313632312e706e67" alt="Fork me on GitHub"></a>
  33
+
  34
+    <script type="text/javascript;version=1.8" src="blimp.js"></script>
  35
+    <script type="text/javascript;version=1.8" src="dropbox.js"></script>
  36
+  </body>
  37
+
  38
+</html>
112  src/utils/Blimp/blimp.js
... ...
@@ -0,0 +1,112 @@
  1
+"use strict";
  2
+
  3
+(function() {
  4
+  let $sadpanda = document.getElementById("sadpanda");
  5
+  let $progress = document.getElementById("progress");
  6
+  let $importing = document.getElementById("importing");
  7
+  let $bound = document.getElementById("bound");
  8
+
  9
+  // we'll need some features that started being availalbe only in Nightlies
  10
+  // (currently Firefox 13+), mostly related to bug 726634
  11
+  if (parseInt(navigator.userAgent.replace(/.*Firefox\//, "")
  12
+                                  .replace(/[^0-9.]+\d+$/, "")) < 14) {
  13
+    return; // :(
  14
+  } else {
  15
+    $sadpanda.style.display = "none"; // :)
  16
+  }
  17
+
  18
+  /**
  19
+   * Mesh constructor, containing the model strings. Currently, only 'obj' is
  20
+   * used, but we may tackle with the 'mtl' at some point.
  21
+   *
  22
+   * @param String obj
  23
+   *        The model obj file contents (vertices, faces etc.)
  24
+   * @param String mtl
  25
+   *        Optional, the material file contents. Unused.
  26
+   */
  27
+  function Mesh(obj, mtl) {
  28
+    this.obj = obj || "";
  29
+    this.mtl = mtl || "";
  30
+    this.importer = new ImporterWorker(this);
  31
+  }
  32
+
  33
+  Mesh.prototype = {
  34
+
  35
+    /**
  36
+     * Talks to a worker responsible with creating the html representing a
  37
+     * voxelized version of the mesh.
  38
+     */
  39
+    export: function()
  40
+    {
  41
+      let bound = parseInt($bound.value);
  42
+      let size = 15; // magic constant, the thickness of a stack node in Tilt
  43
+
  44
+      this.importer.request("export", { bound: bound, size: size }, function(data) {
  45
+        document.head.innerHTML = data.css;
  46
+        document.body.innerHTML = data.html;
  47
+      });
  48
+    }
  49
+  };
  50
+
  51
+  /**
  52
+   * A wrapper around the importer.js worker.
  53
+   *
  54
+   * @param Mesh mesh
  55
+   *        The mesh containing the model 'obj' and 'mtl' strings.
  56
+   */
  57
+  function ImporterWorker(mesh) {
  58
+    this.mesh = mesh;
  59
+  }
  60
+
  61
+  ImporterWorker.prototype = {
  62
+
  63
+    /**
  64
+     * Sends a generic request message to the worker and issues a callback when
  65
+     * ready. See importer.js for more goodies. Possible requests could be:
  66
+     *
  67
+     * @param String method
  68
+     *        The method name in the importer. Either "vertices",
  69
+     *                                                "triangles",
  70
+     *                                                "bounds",
  71
+     *                                                "intersections",
  72
+     *                                                "xyzscan",
  73
+     *                                                "voxels" or
  74
+     *                                                "export".
  75
+     * @param Object params
  76
+     *        An object containing stuff to be sent as params for the method.
  77
+     * @param Function callback
  78
+     *        To be called when worker finished working.
  79
+     */
  80
+    request: function(method, params, callback)
  81
+    {
  82
+      let worker = new Worker("importer.js");
  83
+
  84
+      // cool animation bro'
  85
+      let spinner = ["|", "/", "–", "\\", "|", "/", "–", "\\"];
  86
+      let count = 0;
  87
+
  88
+      worker.addEventListener("message", function(event) {
  89
+        let data = event.data;
  90
+
  91
+        if (data.signature === method) {
  92
+          // the response is exactly what we wanted, callback!
  93
+          callback(event.data.response);
  94
+        } else {
  95
+          // sometimes bad stuff may happen in the importer worker, or maybe
  96
+          // simple status report messages can be sent; deal with this here
  97
+          console.log(JSON.stringify(data));
  98
+          $progress.textContent = spinner[(count++) % spinner.length];
  99
+          $importing.style.visibility = "visible";
  100
+        }
  101
+      });
  102
+
  103
+      worker.postMessage({
  104
+        mesh: this.mesh,
  105
+        method: method,
  106
+        params: params
  107
+      });
  108
+    }
  109
+  };
  110
+
  111
+  window.Mesh = Mesh;
  112
+})();
37  src/utils/Blimp/dropbox.js
... ...
@@ -0,0 +1,37 @@
  1
+"use strict";
  2
+
  3
+(function() {
  4
+
  5
+  function dragenter(e) {
  6
+    e.stopPropagation();
  7
+    e.preventDefault();
  8
+  }
  9
+
  10
+  function dragover(e) {
  11
+    e.stopPropagation();
  12
+    e.preventDefault();
  13
+  }
  14
+
  15
+  function drop(e) {
  16
+    e.stopPropagation();
  17
+    e.preventDefault();
  18
+
  19
+    let file = e.dataTransfer.files[0];
  20
+    if (!file.name.match(/\.obj$/)) {
  21
+      return;
  22
+    }
  23
+
  24
+    let reader = new FileReader();
  25
+    reader.onload = function(e) {
  26
+      let text = e.target.result;
  27
+      let mesh = new Mesh(text).export();
  28
+    };
  29
+
  30
+    reader.readAsText(file);
  31
+  }
  32
+
  33
+  let dropbox = document.body;
  34
+  dropbox.addEventListener("dragenter", dragenter, false);
  35
+  dropbox.addEventListener("dragover", dragover, false);
  36
+  dropbox.addEventListener("drop", drop, false);
  37
+})();
BIN  src/utils/Blimp/img/noise.png
756  src/utils/Blimp/importer.js
... ...
@@ -0,0 +1,756 @@
  1
+"use strict";
  2
+
  3
+/**
  4
+ * Function handling messages sent from the main thread.
  5
+ */
  6
+self.onmessage = function(event) {
  7
+  var data = event.data;
  8
+  var mesh = data.mesh;
  9
+  var method = data.method;
  10
+  var params = data.params;
  11
+
  12
+  self.postMessage({
  13
+    signature: method,
  14
+    response: new Importer(mesh)["get " + method](params) // cool protocol bro'
  15
+  });
  16
+
  17
+  close();
  18
+};
  19
+
  20
+const EPSILON = 0.001;
  21
+
  22
+/**
  23
+ * A tridimensional vector.
  24
+ *
  25
+ * @param Number x
  26
+ * @param Number y
  27
+ * @param Number z
  28
+ */
  29
+function Vector3(x, y, z)
  30
+{
  31
+  this.x = x || 0;
  32
+  this.y = y || 0;
  33
+  this.z = z || 0;
  34
+}
  35
+
  36
+Vector3.prototype = {
  37
+
  38
+  /**
  39
+   * Assigns the x, y and z fields from an array.
  40
+   *
  41
+   * @param Array arr
  42
+   *        The [x, y, z] array to be used.
  43
+   * @return Vector3
  44
+   *         The same object.
  45
+   */
  46
+  fromArray: function(arr)
  47
+  {
  48
+    this.x = arr[0];
  49
+    this.y = arr[1];
  50
+    this.z = arr[2];
  51
+    return this;
  52
+  },
  53
+
  54
+  /**
  55
+   * Assigns the x, y and z fields from another vector.
  56
+   *
  57
+   * @param Vector3 vec3
  58
+   *        The source vector to be used.
  59
+   * @return Vector3
  60
+   *         The same object.
  61
+   */
  62
+  fromVec3: function(vec3)
  63
+  {
  64
+    this.x = vec3.x;
  65
+    this.y = vec3.y;
  66
+    this.z = vec3.z;
  67
+    return this;
  68
+  },
  69
+
  70
+  /**
  71
+   * Turns the x, y and z fields into integers.
  72
+   * This is especially nifty when dealing with voxels.
  73
+   *
  74
+   * @return Vector3
  75
+   *         The same object.
  76
+   */
  77
+  toInteger: function()
  78
+  {
  79
+    this.x = parseInt(this.x);
  80
+    this.y = parseInt(this.y);
  81
+    this.z = parseInt(this.z);
  82
+    return this;
  83
+  },
  84
+
  85
+  /**
  86
+   * Adds two vectors togeher, creates a new one to store the result in.
  87
+   *
  88
+   * @param Vector3 that
  89
+   *        The vector to add this vector to.
  90
+   * @return Vector3
  91
+   *         The operation result.
  92
+   */
  93
+  add: function(that)
  94
+  {
  95
+    return new Vector3(this.x + that.x, this.y + that.y, this.z + that.z);
  96
+  },
  97
+
  98
+  /**
  99
+   * Subtracts this vector with another, creates a new one to store the result.
  100
+   *
  101
+   * @param Vector3 that
  102
+   *        The vector to subtract this vector with.
  103
+   * @return Vector3
  104
+   *         The operation result.
  105
+   */
  106
+  subtract: function(that)
  107
+  {
  108
+    return new Vector3(this.x - that.x, this.y - that.y, this.z - that.z);
  109
+  },
  110
+
  111
+  /**
  112
+   * Multiplies this vector with a scalar, creates a new one to store the result.
  113
+   *
  114
+   * @param Number scalar
  115
+   *        The scalar to multiply this vector with.
  116
+   * @return Vector3
  117
+   *         The operation result.
  118
+   */
  119
+  multiply: function(scalar)
  120
+  {
  121
+    return new Vector3(this.x * scalar, this.y * scalar, this.z * scalar);
  122
+  },
  123
+
  124
+  /**
  125
+   * Interpolates this vector with another, creates a new one to store the result.
  126
+   *
  127
+   * @param Vector3 that
  128
+   *        The vector to multiply this vector with.
  129
+   * @param Number scalar
  130
+   *        The interpolation abount. Must be between [0..1] to work properly.
  131
+   * @return Vector3
  132
+   *         The operation result.
  133
+   */
  134
+  lerp: function(that, scalar)
  135
+  {
  136
+    return new Vector3(this.x + scalar * (that.x - this.x),
  137
+                       this.y + scalar * (that.y - this.y),
  138
+                       this.z + scalar * (that.z - this.z));
  139
+  },
  140
+
  141
+  /**
  142
+   * It dots!
  143
+   *
  144
+   * @param Vector3 that
  145
+   *        The vector to dot this vector with. Lololo.
  146
+   * @return Number
  147
+   *         The operation result.
  148
+   */
  149
+  dot: function(that)
  150
+  {
  151
+    return this.x * that.x + this.y * that.y + this.z * that.z;
  152
+  },
  153
+
  154
+  /**
  155
+   * Gets this vector's length.
  156
+   * @return Number
  157
+   */
  158
+  get length()
  159
+  {
  160
+    return Math.sqrt(this.dot(this));
  161
+  },
  162
+
  163
+  /**
  164
+   * Calculates the distance between this vector (point) to another.
  165
+   *
  166
+   * @param Vector3 that
  167
+   *        The vector to calculate the distance to
  168
+   * @return Number
  169
+   *         The operation result.
  170
+   */
  171
+  distanceTo: function(that)
  172
+  {
  173
+    return that.subtract(this).length;
  174
+  },
  175
+
  176
+  /**
  177
+   * Calculates the distance between this vector (point) to a plane.
  178
+   *
  179
+   * @param Plane that
  180
+   *        The plane to calculate the distance to
  181
+   * @return Number
  182
+   *         The operation result.
  183
+   */
  184
+  distanceToPlane: function(plane)
  185
+  {
  186
+    return this.dot(plane.N) + plane.D;
  187
+  }
  188
+};
  189
+
  190
+/**
  191
+ * This is basically a specialized type a vector, which includes a 'size' field.
  192
+ *
  193
+ * @param Number size
  194
+ * @param Number x
  195
+ * @param Number y
  196
+ * @param Number z
  197
+ */
  198
+function Voxel(size, x, y, z)
  199
+{
  200
+  Vector3.call(this, x, y, z);
  201
+  this.size = size;
  202
+}
  203
+
  204
+Voxel.prototype = new Vector3();
  205
+Voxel.prototype.constructor = Voxel;
  206
+
  207
+/**
  208
+ * Constrains the x, y and z fields of this voxel to particular bounds. For
  209
+ * example, if this voxel's size is 10 and its position is [4, 5, 6], the
  210
+ * new position will become [0, 0, 0]. Similarily, if the original position
  211
+ * was [14, 15, 16], the new position will become [10, 10, 10].
  212
+ *
  213
+ * @return Voxel
  214
+ *         The same object.
  215
+ */
  216
+Voxel.prototype.normalize = function()
  217
+{
  218
+  this.x = this.x - (this.x % this.size);
  219
+  this.y = this.y - (this.y % this.size);
  220
+  this.z = this.z - (this.z % this.size);
  221
+  return this;
  222
+};
  223
+
  224
+/**
  225
+ * A segment, basically a line between two vectors (points).
  226
+ *
  227
+ * @param Vector3 v1
  228
+ * @param Vector3 v2
  229
+ */
  230
+function Segment(v1, v2)
  231
+{
  232
+  this.left = v1;
  233
+  this.right = v2;
  234
+}
  235
+
  236
+/**
  237
+ * A triangle (orly?).
  238
+ *
  239
+ * @param Vector3 v1
  240
+ * @param Vector3 v2
  241
+ * @param Vector3 v3
  242
+ */
  243
+function Triangle(v1, v2, v3)
  244
+{
  245
+  this.v1 = v1;
  246
+  this.v2 = v2;
  247
+  this.v3 = v3;
  248
+}
  249
+
  250
+/**
  251
+ * A plane (not the flying kind!), defined in the purest mathematical way
  252
+ *
  253
+ * @param Vector3 vecN
  254
+ *        The plane normal vector.
  255
+ * @param Number scalarD
  256
+ *        The normal length.
  257
+ */
  258
+function Plane(vecN, scalarD)
  259
+{
  260
+  this.N = vecN;
  261
+  this.D = scalarD;
  262
+}
  263
+
  264
+/**
  265
+ * A bounding box, defined by the minimum and maximum values in all three
  266
+ * dimensions. Initially, all these values are undefined.
  267
+ */
  268
+function BoundingBox()
  269
+{
  270
+  this.X = { min: undefined, max: undefined };
  271
+  this.Y = { min: undefined, max: undefined };
  272
+  this.Z = { min: undefined, max: undefined };
  273
+}
  274
+
  275
+BoundingBox.prototype = {
  276
+
  277
+  /**
  278
+   * Gets the largest distance from the bounding box center to the outer edge
  279
+   * on the X axis.
  280
+   */
  281
+  get outerX()
  282
+  {
  283
+    return Math.max(Math.abs(this.X.min), Math.abs(this.X.max));
  284
+  },
  285
+
  286
+  /**
  287
+   * Gets the largest distance from the bounding box center to the outer edge
  288
+   * on the Y axis.
  289
+   */
  290
+  get outerY()
  291
+  {
  292
+    return Math.max(Math.abs(this.Y.min), Math.abs(this.Y.max));
  293
+  },
  294
+
  295
+  /**
  296
+   * Gets the largest distance from the bounding box center to the outer edge
  297
+   * on the Z axis.
  298
+   */
  299
+  get outerZ()
  300
+  {
  301
+    return Math.max(Math.abs(this.Z.min), Math.abs(this.Z.max));
  302
+  }
  303
+}
  304
+
  305
+/**
  306
+ * This is where all the magic happens!
  307
+ *
  308
+ * @param Mesh mesh
  309
+ *        The mesh containing the model 'obj' and 'mtl' strings.
  310
+ */
  311
+function Importer(mesh)
  312
+{
  313
+  this.mesh = mesh;
  314
+}
  315
+
  316
+Importer.prototype = {
  317
+
  318
+  /**
  319
+   * Gets all the vertices in the mesh and stores them as Vector3 instances.
  320
+   *
  321
+   * @return Array
  322
+   *         A list with all the vertices.
  323
+   */
  324
+  "get vertices": function()
  325
+  {
  326
+    if (this.vertices) {
  327
+      return;
  328
+    }
  329
+    var vertices = [];
  330
+
  331
+    try {
  332
+      var lines = this.mesh.obj.replace(/\ \ /g, " ").split("\n");
  333
+
  334
+      for (var i = 0, l = lines.length; i < l; i++) {
  335
+        var line = lines[i];
  336
+
  337
+        if (line[0] === "v") {
  338
+          var data = this._splitData(line);
  339
+          vertices.push(new Vector3(parseFloat(data[0]),
  340
+                                    parseFloat(data[1]),
  341
+                                    parseFloat(data[2])));
  342
+        }
  343
+      }
  344
+    } catch (e) {
  345
+      self.postMessage(e);
  346
+    }
  347
+
  348
+    self.postMessage("get vertices finished");
  349
+    return (this.vertices = vertices);
  350
+  },
  351
+
  352
+  /**
  353
+   * Gets all the triangles in the mesh and stores them as Triangle instances.
  354
+   *
  355
+   * @return Array
  356
+   *         A list with all the triangles.
  357
+   */
  358
+  "get triangles": function()
  359
+  {
  360
+    if (this.triangles) {
  361
+      return;
  362
+    }
  363
+    this["get vertices"](); // we'll need all the vertices first!
  364
+    var triangles = [];
  365
+
  366
+    try {
  367
+      var lines = this.mesh.obj.replace(/\ \ /g, " ").split("\n");
  368
+      var vertices = this.vertices;
  369
+
  370
+      for (var i = 0, l = lines.length; i < l; i++) {
  371
+        var line = lines[i];
  372
+
  373
+        if (line[0] === "f") {
  374
+          var data = this._splitData(line);
  375
+          var v0, v1, v2;
  376
+
  377
+          for (var v = 0, step = 0, dl = data.length; v < dl && dl >= 3;) {
  378
+            if (step++ === 0) {
  379
+              v0 = vertices[parseInt(data[v    ]) - 1];
  380
+              v1 = vertices[parseInt(data[v + 1]) - 1];
  381
+              v2 = vertices[parseInt(data[v + 2]) - 1];
  382
+              v += 3;
  383
+            } else {
  384
+              v0 = vertices[parseInt(data[v - 3]) - 1];
  385
+              v1 = vertices[parseInt(data[v - 1]) - 1];
  386
+              v2 = vertices[parseInt(data[v    ]) - 1];
  387
+              v++;
  388
+            }
  389
+            triangles.push(new Triangle(v0, v1, v2));
  390
+          }
  391
+        }
  392
+      }
  393
+    } catch (e) {
  394
+      self.postMessage(e);
  395
+    }
  396
+
  397
+    self.postMessage("get triangles finished");
  398
+    return (this.triangles = triangles);
  399
+  },
  400
+
  401
+  /**
  402
+   * Calculates the bounding box for the mesh.
  403
+   * @return BoundingBox
  404
+   */
  405
+  "get bounds": function()
  406
+  {
  407
+    if (this.bounds) {
  408
+      return;
  409
+    }
  410
+    this["get vertices"]();
  411
+    var bounds = new BoundingBox();
  412
+
  413
+    try {
  414
+      var vertices = this.vertices;
  415
+      var boundsX = bounds.X;
  416
+      var boundsY = bounds.Y;
  417
+      var boundsZ = bounds.Z;
  418
+
  419
+      for (var i = 0, l = vertices.length; i < l; i++) {
  420
+        var vertex = vertices[i];
  421
+        var x = vertex.x;
  422
+        var y = vertex.y;
  423
+        var z = vertex.z;
  424
+
  425
+        if (boundsX.min === undefined || boundsX.min > x) { // wow this is ugly
  426
+          boundsX.min = x;
  427
+        }
  428
+        if (boundsY.min === undefined || boundsY.min > y) {
  429
+          boundsY.min = y;
  430
+        }
  431
+        if (boundsZ.min === undefined || boundsZ.min > z) {
  432
+          boundsZ.min = z;
  433
+        }
  434
+        if (boundsX.max === undefined || boundsX.max < x) {
  435
+          boundsX.max = x;
  436
+        }
  437
+        if (boundsY.max === undefined || boundsY.max < y) {
  438
+          boundsY.max = y;
  439
+        }
  440
+        if (boundsZ.max === undefined || boundsZ.max < z) {
  441
+          boundsZ.max = z;
  442
+        }
  443
+      }
  444
+    } catch (e) {
  445
+      self.postMessage(e);
  446
+    }
  447
+
  448
+    self.postMessage("get bounds finished");
  449
+    return (this.bounds = bounds);
  450
+  },
  451
+
  452
+  /**
  453
+   * Calculates intersections between the mesh and a plane, defined by the
  454
+   * N and D params.
  455
+   *
  456
+   * @param Object params
  457
+   *        An object containing the
  458
+   *          -- Array N
  459
+   *             The nnormal (as a [x, y, z] array)
  460
+   *          -- Number D
  461
+   *             Scalar, used to define the plane which will intersect the mesh.
  462
+   * @param Array out
  463
+   *        Optional, storage for the intersections
  464
+   * @return Array
  465
+   *         An outline defining the plane-mesh intersection, as a list of segments.
  466
+   */
  467
+  "get intersections": function(params, out)
  468
+  {
  469
+    this["get triangles"]();
  470
+    var intersections = out || [];
  471
+
  472
+    try {
  473
+      var triangles = this.triangles;
  474
+      var normal = new Vector3().fromArray(params.N);
  475
+      var plane = new Plane(normal, params.D);
  476
+
  477
+      for (var i = 0, l = triangles.length; i < l; i++) {
  478
+        var triangle = triangles[i];
  479
+        var segment = this._planeTriangleIntersection(plane, triangle);
  480
+
  481
+        if (segment) {
  482
+          intersections.push(segment);
  483
+        }
  484
+      }
  485
+    } catch (e) {
  486
+      self.postMessage(e);
  487
+    }
  488
+
  489
+    self.postMessage("get intersections finished");
  490
+    return (this.intersections = intersections);
  491
+  },
  492
+
  493
+  /**
  494
+   * Calculates all intersections between the mesh and a number of planes
  495
+   * situated at regular distances on the X, Y and Z axes, contained in the
  496
+   * mesh bounding box.
  497
+   * In other words: chop chop chop.
  498
+   *
  499
+   * @param Object params
  500
+   *        An object containing the following fields:
  501
+   *          -- Number size
  502
+   *             The intervals in which to perform the mesh-plane intersections.
  503
+   *          -- Number scale
  504
+   *             Optional, a scalar to scale the entire mesh with.
  505
+   *             If unspecified, it will default to 1.
  506
+   * @return Array
  507
+   *         An outline defining the plane-mesh intersection, as a list of segments.
  508
+   */
  509
+  "get xyzscan": function(params)
  510
+  {
  511
+    if (this.xyzscan) {
  512
+      return;
  513
+    }
  514
+    this["get bounds"]();
  515
+    var xyzscan = [];
  516
+
  517
+    try {
  518
+      var bounds = this.bounds;
  519
+      var boundsX = bounds.X;
  520
+      var boundsY = bounds.Y;
  521
+      var boundsZ = bounds.Z;
  522
+      var step = params.size / (params.scale || 1);
  523
+
  524
+      for (var height = boundsX.min - EPSILON;
  525
+           height <= boundsX.max + EPSILON;
  526
+           height += step) {
  527
+        this["get intersections"]({ N: [1, 0, 0], D: height }, xyzscan);
  528
+      }
  529
+      for (height = boundsY.min - EPSILON;
  530
+           height <= boundsY.max + EPSILON;
  531
+           height += step) {
  532
+        this["get intersections"]({ N: [0, 1, 0], D: height }, xyzscan);
  533
+      }
  534
+      for (height = boundsZ.min - EPSILON;
  535
+           height <= boundsZ.max + EPSILON;
  536
+           height += step) {
  537
+        this["get intersections"]({ N: [0, 0, 1], D: height }, xyzscan);
  538
+      }
  539
+    } catch (e) {
  540
+      self.postMessage(e);
  541
+    }
  542
+
  543
+    self.postMessage("get xyzscan finished");
  544
+    return (this.xyzscan = xyzscan);
  545
+  },
  546
+
  547
+  /**
  548
+   * Creates a voxelized representation of the mesh based on all the outline
  549
+   * intersections on the X, Y and Z axes.
  550
+   *
  551
+   * @param Object params
  552
+   *        An object containing the following fields:
  553
+   *          -- Number size
  554
+   *             The size of a voxel (basically the width/height/depth of a cube).
  555
+   *          -- Number scale
  556
+   *             Optional, a scalar to scale the entire mesh with.
  557
+   *             If unspecified, it will default to 1.
  558
+   * @return Array
  559
+   *         A list of voxels which define a *hollow* version of the object.
  560
+   */
  561
+  "get voxels": function(params)
  562
+  {
  563
+    if (this.voxels) {
  564
+      return;
  565
+    }
  566
+    this["get xyzscan"](params);
  567
+    var voxels = [];
  568
+
  569
+    try {
  570
+      var cache = {};
  571
+      var xyzscan = this.xyzscan;
  572
+      var scale = params.scale;
  573
+      var size = params.size;
  574
+
  575
+      for (var i = 0, l = xyzscan.length; i < l; i++) {
  576
+        var segment = xyzscan[i];
  577
+        var v1 = segment.left.multiply(scale);
  578
+        var v2 = segment.right.multiply(scale);
  579
+
  580
+        var step = 1 / (v1.distanceTo(v2) / (size / 2));
  581
+        var lerp = step;
  582
+
  583
+        if (i % 100 === 0) {
  584
+          self.postMessage(voxels.length + ", " + i + "/" + l);
  585
+        }
  586
+        do {
  587
+          var loc = v1.lerp(v2, lerp);
  588
+          var voxel = new Voxel(size).fromVec3(loc).normalize().toInteger();
  589
+          var key = "" + voxel.x + voxel.y + voxel.z;
  590
+
  591
+          if (!cache[key]) {
  592
+            cache[key] = true;
  593
+            voxels.push(voxel);
  594
+          }
  595
+        } while ((lerp += step) <= 1);
  596
+      }
  597
+    } catch (e) {
  598
+      self.postMessage(e);
  599
+    }
  600
+
  601
+    self.postMessage("get voxels finished");
  602
+    return (this.voxels = voxels);
  603
+  },
  604
+
  605
+  /**
  606
+   * Creates a html + css string representing a voxelized version of the mesh.
  607
+   * This will look good only in Tilt (for now).
  608
+   *
  609
+   * @param Object params
  610
+   *        An object containing the follwing fields:
  611
+   *          -- Number size
  612
+   *             The size of a voxel (basically the width/height/depth of a cube).
  613
+   *          -- Number bound
  614
+   *             A scalar (roughly) determining the size of the exported model.
  615
+   *             Theoretically, no voxel should exceed the edges defined by this
  616
+   *             scalar, but stranger things have happened.
  617
+   * @return Object
  618
+   *         An object containing a list of all the voxels, the html and css
  619
+   *         represeting the mesh. It looks like { voxels, html, css }.
  620
+   */
  621
+  "get export": function(params)
  622
+  {
  623
+    this["get bounds"]();
  624
+    params = {
  625
+      size: params.size,
  626
+      scale: params.bound * 3 / (this.bounds.outerX +
  627
+                                 this.bounds.outerY +
  628
+                                 this.bounds.outerZ)
  629
+    };
  630
+
  631
+    if (this.html) {
  632
+      return;
  633
+    }
  634
+    this["get voxels"](params);
  635
+    var css = [""];
  636
+    var html = [""];
  637
+
  638
+    try {
  639
+      var voxels = this.voxels;
  640
+      var scale = params.scale;
  641
+      var size = params.size;
  642
+      var layers = {};
  643
+
  644
+      var top = -parseInt(this.bounds.Z.min * scale);
  645
+      var left = -parseInt(this.bounds.X.min * scale);
  646
+      var right = parseInt(this.bounds.X.max * scale);
  647
+
  648
+      voxels.sort(function(a, b) {
  649
+        return a.y > b.y;
  650
+      });
  651
+
  652
+      for (var i = 0, l = voxels.length; i < l; i++) {
  653
+        var voxel = voxels[i];
  654
+        var depth = voxel.y / size;
  655
+
  656
+        if (!layers[depth]) {
  657
+          layers[depth] = {};
  658
+        }
  659
+        layers[depth][i] = voxel;
  660
+      }
  661
+
  662
+      css.push(
  663
+        "<style>",
  664
+          "body {",
  665
+          "  background: #000;",
  666
+          "  margin: 0;",
  667
+          "}",
  668
+          "#model {",
  669
+          "  position: relative;",
  670
+          "  top: " + size + "px;",
  671
+          "  width: " + (left + right) + "px;",
  672
+          "  margin: 0 auto;",
  673
+          "}",
  674
+          ".px {",
  675
+          "  position: absolute;",
  676
+          "  width: " + size + "px;",
  677
+          "  height: " + size + "px;",
  678
+          "  background: #fff;",
  679
+          "}",
  680
+        "</style>", "");
  681
+
  682
+      html.push("<div id='model'>");
  683
+
  684
+      for (var depth in layers) {
  685
+        var layer = layers[depth];
  686
+        html.push("<div>");
  687
+
  688
+        for (var index in layer) {
  689
+          var voxel = layer[index];
  690
+          var position = [
  691
+            "top: ", (top + voxel.z), "px; ",
  692
+            "left: ", (left + voxel.x), "px;"].join("");
  693
+
  694
+          html.push("<div class='px' style='" + position + "'></div>");
  695
+        }
  696
+      }
  697
+      for (var depth in layers) {
  698
+        html.push("</div>");
  699
+      }
  700
+      html.push("</div>");
  701
+    } catch (e) {
  702
+      self.postMessage(e);
  703
+    }
  704
+
  705
+    self.postMessage("get export finished");
  706
+    return {
  707
+      voxels: this.voxels,
  708
+      css: css.join("\n"),
  709
+      html: html.join("\n")
  710
+    };
  711
+  },
  712
+
  713
+  _planeTriangleIntersection: function(plane, triangle)
  714
+  {
  715
+    var point = null;
  716
+    var edges = [];
  717
+    var v1 = triangle.v1;
  718
+    var v2 = triangle.v2;
  719
+    var v3 = triangle.v3;
  720
+
  721
+    if ((point = this._planeSegmentIntersection(plane, v1, v2))) {
  722
+      edges.push(point);
  723
+    }
  724
+    if ((point = this._planeSegmentIntersection(plane, v2, v3))) {
  725
+      edges.push(point);
  726
+    }
  727
+    if ((point = this._planeSegmentIntersection(plane, v3, v1))) {
  728
+      edges.push(point);
  729
+    }
  730
+
  731
+    return edges.length === 2 ? new Segment(edges[0], edges[1]) : null;
  732
+  },
  733
+
  734
+  _planeSegmentIntersection: function(plane, v1, v2)
  735
+  {
  736
+    var d1 = v1.distanceToPlane(plane);
  737
+    var d2 = v2.distanceToPlane(plane);
  738
+
  739
+    if (d1 * d2 > 0) {
  740
+      return null;
  741
+    }
  742
+
  743
+    var t = d1 / (d1 - d2);
  744
+    return v1.add(v2.subtract(v1).multiply(t));
  745
+  },
  746
+
  747
+  _splitData: function(line, offset)
  748
+  {
  749
+    var data = line.substring(offset || 1).trim().split(" ");
  750
+
  751
+    for (var i = 0, l = data.length; i < l; i++) {
  752
+      data[i] = data[i].split("/")[0];
  753
+    }
  754
+    return data;
  755
+  }
  756
+};
339,535  src/utils/Blimp/test/enterprise.obj
339535 additions, 0 deletions not shown
1,928  src/utils/Blimp/test/sphere.obj
... ...
@@ -0,0 +1,1928 @@
  1
+# Blender v2.61 (sub 0) OBJ File: ''
  2
+# www.blender.org
  3
+mtllib sphere.mtl
  4
+o Icosphere
  5
+v -0.059619 -0.599158 0.712047
  6
+v -0.040231 -0.546238 0.756865
  7
+v -0.127732 -0.613082 0.693291
  8
+v -0.010527 -0.634406 0.681539
  9
+v 0.056590 -0.566420 0.736918
  10
+v -0.139687 -0.534377 0.754137
  11
+v -0.094291 -0.681682 0.627296
  12
+v 0.173436 -0.734686 0.533772
  13
+v 0.154861 -0.778197 0.476603
  14
+v 0.243284 -0.710647 0.540846
  15
+v 0.121087 -0.710648 0.580551
  16
+v 0.051616 -0.767208 0.515324
  17
+v 0.261147 -0.767208 0.447243
  18
+v 0.203235 -0.651381 0.625482
  19
+v 0.466770 -0.599158 0.541011
  20
+v 0.477428 -0.546237 0.588665
  21
+v 0.409121 -0.634406 0.545184
  22
+v 0.510849 -0.613082 0.485800
  23
+v 0.556285 -0.534377 0.527999
  24
+v 0.387374 -0.566420 0.629438
  25
+v 0.445004 -0.681682 0.452066
  26
+v 0.085869 -0.890141 0.264272
  27
+v 0.015343 -0.899240 0.252942
  28
+v 0.136266 -0.899240 0.213652
  29
+v 0.105459 -0.866383 0.324562
  30
+v -0.002866 -0.864537 0.345913
  31
+v 0.052672 -0.919104 0.162105
  32
+v 0.205645 -0.864537 0.278163
  33
+v 0.658775 -0.599158 0.276737
  34
+v 0.707390 -0.546237 0.272148
  35
+v 0.619889 -0.613082 0.335720
  36
+v 0.644930 -0.634405 0.220621
  37
+v 0.718339 -0.566419 0.173902
  38
+v 0.674062 -0.534376 0.365892
  39
+v 0.567458 -0.681682 0.283522
  40
+v 0.561244 -0.734685 0.000000
  41
+v 0.501132 -0.778196 0.000000
  42
+v 0.589556 -0.710647 -0.064242
  43
+v 0.589556 -0.710647 0.064242
  44
+v 0.506054 -0.767207 0.110156
  45
+v 0.506054 -0.767207 -0.110156
  46
+v 0.657673 -0.651381 0.000000
  47
+v 0.658775 -0.599158 -0.276737
  48
+v 0.707390 -0.546237 -0.272148
  49
+v 0.644930 -0.634405 -0.220621
  50
+v 0.619889 -0.613082 -0.335720
  51
+v 0.674062 -0.534377 -0.365892
  52
+v 0.718339 -0.566419 -0.173902
  53
+v 0.567458 -0.681682 -0.283522
  54
+v 0.277873 -0.890141 0.000000
  55
+v 0.245305 -0.899240 0.063573
  56
+v 0.245305 -0.899240 -0.063573
  57
+v 0.341267 -0.866383 0.000000
  58
+v 0.328098 -0.864537 0.109620
  59
+v 0.170449 -0.919104 0.000000
  60
+v 0.328098 -0.864537 -0.109620
  61
+v -0.695627 -0.599157 0.163330
  62
+v -0.732260 -0.546236 0.195618
  63
+v -0.698837 -0.613081 0.092755
  64
+v -0.651440 -0.634404 0.200592
  65
+v -0.683369 -0.566418 0.281537
  66
+v -0.760399 -0.534376 0.100187
  67
+v -0.625737 -0.681681 0.104166
  68
+v -0.454056 -0.734684 0.329890
  69
+v -0.405424 -0.778196 0.294557
  70
+v -0.439199 -0.710647 0.398505
  71
+v -0.514722 -0.710646 0.294558
  72
+v -0.474156 -0.767206 0.208332
  73
+v -0.344657 -0.767207 0.386569
  74
+v -0.532069 -0.651380 0.386570
  75
+v -0.370293 -0.599158 0.611104
  76
+v -0.412322 -0.546237 0.635966
  77
+v -0.392078 -0.634405 0.557566
  78
+v -0.304164 -0.613082 0.635966
  79
+v -0.330256 -0.534377 0.692218
  80
+v -0.478929 -0.566419 0.562919
  81
+v -0.292428 -0.681682 0.562918
  82
+v -0.224804 -0.890141 0.163329
  83
+v -0.235823 -0.899239 0.092754
  84
+v -0.161087 -0.899239 0.195617
  85
+v -0.276090 -0.866382 0.200591
  86
+v -0.329871 -0.864535 0.104166
  87
+v -0.137895 -0.919104 0.100187
  88
+v -0.201002 -0.864536 0.281536
  89
+v -0.370293 -0.599158 -0.611104
  90
+v -0.412322 -0.546237 -0.635966
  91
+v -0.304164 -0.613082 -0.635966
  92
+v -0.392078 -0.634405 -0.557566
  93
+v -0.478929 -0.566419 -0.562919
  94
+v -0.330256 -0.534377 -0.692218
  95
+v -0.292428 -0.681682 -0.562918
  96
+v -0.454056 -0.734684 -0.329890
  97
+v -0.405424 -0.778196 -0.294557
  98
+v -0.514722 -0.710646 -0.294558
  99
+v -0.439199 -0.710647 -0.398505
  100
+v -0.344657 -0.767207 -0.386569
  101
+v -0.474156 -0.767206 -0.208332
  102
+v -0.532069 -0.651380 -0.386570
  103
+v -0.695627 -0.599157 -0.163330
  104
+v -0.732260 -0.546236 -0.195618
  105
+v -0.651440 -0.634404 -0.200592
  106
+v -0.698837 -0.613081 -0.092755
  107
+v -0.760399 -0.534376 -0.100187
  108
+v -0.683369 -0.566418 -0.281537
  109
+v -0.625737 -0.681681 -0.104166
  110
+v -0.224804 -0.890141 -0.163329
  111
+v -0.161087 -0.899239 -0.195617
  112
+v -0.235823 -0.899239 -0.092754
  113
+v -0.276090 -0.866382 -0.200591
  114
+v -0.201002 -0.864536 -0.281536
  115
+v -0.137895 -0.919104 -0.100187
  116
+v -0.329871 -0.864535 -0.104166
  117
+v 0.466770 -0.599158 -0.541011
  118
+v 0.477428 -0.546238 -0.588665
  119
+v 0.510849 -0.613082 -0.485800
  120
+v 0.409121 -0.634406 -0.545185
  121
+v 0.387374 -0.566420 -0.629438
  122
+v 0.556285 -0.534377 -0.527999
  123
+v 0.445004 -0.681682 -0.452066
  124
+v 0.173436 -0.734686 -0.533772
  125
+v 0.154860 -0.778197 -0.476603
  126
+v 0.121087 -0.710648 -0.580551
  127
+v 0.243284 -0.710647 -0.540846
  128
+v 0.261147 -0.767208 -0.447243
  129
+v 0.051616 -0.767208 -0.515325
  130
+v 0.203235 -0.651381 -0.625482
  131
+v -0.059619 -0.599158 -0.712047
  132
+v -0.040231 -0.546238 -0.756865
  133
+v -0.010527 -0.634406 -0.681539
  134
+v -0.127732 -0.613082 -0.693291
  135
+v -0.139687 -0.534377 -0.754137
  136
+v 0.056590 -0.566420 -0.736918
  137
+v -0.094291 -0.681682 -0.627296
  138
+v 0.085869 -0.890141 -0.264272
  139
+v 0.136266 -0.899240 -0.213652
  140
+v 0.015343 -0.899240 -0.252942
  141
+v 0.105459 -0.866383 -0.324562
  142
+v 0.205645 -0.864537 -0.278163
  143
+v 0.052672 -0.919104 -0.162105
  144
+v -0.002866 -0.864537 -0.345913
  145
+v 0.830512 -0.321282 0.276738
  146
+v 0.825576 -0.280273 0.335721
  147
+v 0.804919 -0.388432 0.272148
  148
+v 0.855847 -0.293135 0.220621
  149
+v 0.863487 -0.202698 0.283523
  150
+v 0.779407 -0.363926 0.365892
  151
+v 0.827867 -0.389199 0.173903
  152
+v 0.908116 -0.173436 0.000000
  153