├── .gitignore ├── LICENSE ├── README.md ├── TODO ├── build.sh ├── build ├── trimesh.js └── trimesh.js.min ├── client.js ├── index.js ├── package.json ├── src ├── conway.js ├── distance.js ├── heap.js ├── loop_subdivision.js ├── marchingcubes.js ├── marchingtetrahedra.js ├── normals.js ├── obj.js ├── repair.js ├── shapes.js ├── surfacenets.js └── topology.js └── tests ├── all.js ├── isosurface.js ├── metric.js ├── normals.js ├── repair.js ├── subdivision.js └── topology.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Mikola Lysenko 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trimesh.js 2 | 3 | (DEPRECATED WARNING: I am currently in the process of splitting the functionality in trimesh.js up into smaller libraries. This is still an ongoing process.) 4 | 5 | ... is an ever expanding collection of algorithms for processing triangulated meshes in Javascript. 6 | 7 | ## Server side (node.js) 8 | 9 | First, install the library using npm: 10 | 11 | npm install trimesh 12 | 13 | Then include the library in your project like usual: 14 | 15 | var trimesh = require('trimesh'); 16 | 17 | 18 | ## Client side 19 | 20 | First download the script from the following URL: 21 | 22 | https://raw.github.com/mikolalysenko/TrimeshJS/master/build/trimesh.js.min 23 | 24 | Add a reference to the script in your header: 25 | 26 | 27 | 28 | Which will create an object called `trimesh` in the global namespace that contains the API. 29 | 30 | ## Getting Test Data 31 | 32 | If you want to get some mesh data to mess around with and the built in shape generators are not enough for you, you can also take a look at the stuff stored in the sister MeshData npm module: 33 | 34 | https://github.com/mikolalysenko/MeshData 35 | 36 | 37 | # General Philosophal and Religious Discussion 38 | 39 | Working with meshes is hard -- and yet it has to be done if we are to compute on surfaces. The situation is not helped by the enormous confusion of data structures for optimizing spatial and topological queries on meshes. Picking a single representation, like a winged edge, half edge or cell tuple complex brings with it many tradeoffs and introduces enormous complexity into algorithms which operate on these meshes. These choices cause implementations of mesh based algorithms to rapidly diverge, resulting in an enormous proliferation and duplication of effort. Clearly this situation is unacceptable from the stand point of interoperability and coder sanity. The core philosophy of trimesh.js is a reaction to this offensive mess and can be summed up in the following central thesis: 40 | 41 | > ** Meshes do not need their own data structure. ** 42 | 43 | To avoid falling into the trap of overengineering that seems to sidetrack other mesh libraries, trimesh.js adopts a "Just the facts, ma'am" personality, with each method taking only enough data to answer the necessary basic queries required to implement the described functionality. Guided by these ideals, trimesh.js departs radically from other mesh libraries in the following ways: 44 | 45 | * No in place updates or side effects. 46 | * No 'cute' accessors for member components (eg. x/y/z values of a 3D vector). If an array will suffice to store the data, use an array. Don't make a new object type. 47 | * Along the same lines, store multiple vertex attributes in separate arrays. This allows for more efficient iteration, since properties which are not needed are not stored. 48 | * Spatial/topological indices (like grids/winged edge data) are maintained separately from mesh data. If an index is needed, either build it from scratch or if possible reuse an existing index. 49 | 50 | The advantage to this more functional style of mesh computation is that many algorithms can be written in a very straightforward manner. However, it is not without consequences. Most severely, because in place updates are not supported, this library may not suitable for large scale interactive mesh processing. (Of course for those sort of problems you should probably not be using javascript anyway and should look for a way to engineer some sort of streaming solution...) The other more serious problem is that because indices are not maintained, it may be necessary to rebuild certain indices that could have been computed more efficiently by merging or amortized over mesh operations. However, it again my opinion that this tradeoff may be worthwhile, since most index calculations scale roughly linearly or log-linearly with the size of the mesh and so asymptotically the added cost is typically at most an extra log factor. 51 | 52 | Removing custom vertex/vector types may also offend some, however I find the x/y/z notation quite tedious and error prone, (as does Carmack, who after documenting his various programming mistakes found that x/y/z notation was the most frequent source of his errors). Using numerical indices makes it much easier to write per component operations in a generic way. 53 | 54 | 55 | # API 56 | 57 | Trimesh.JS is just a big bag of functions. Like any library of numerical recipes, many of these functions take a large number of arguments, some of which are optional. Since javascript does not support features like named arguments, these methods are all called by passing in a dictionary (or object) containing all the parameters. For example, to call a method which computes the stars of a mesh you would do the following: 58 | 59 | var stars = trimesh.vertex_stars({ faces: [ [0, 1, 2], [1, 2, 3] ], vertex_count: 4 }); 60 | 61 | Since certain parameters are used frequently, we use the following conventions: 62 | 63 | * `faces` is always an array of triples representing the indices of each face in the mesh. 64 | * `positions` is an array of length 3 arrays representing the position of each vertex. 65 | * `stars` is an array of vertex stars, which can be precomputed using the `vertex_stars` function. 66 | 67 | 68 | ## Topology 69 | 70 | ### `edges` 71 | 72 | Finds all edges in a mesh. 73 | 74 | #### Parameters: 75 | * `faces`: The mesh topology 76 | 77 | #### Returns: 78 | A dictionary mapping pairs of vertex indices to lists of incident faces. 79 | 80 | #### Running time: `O(faces.length)` 81 | 82 | ### `vertex_stars` 83 | 84 | Computes the set of incident faces for each vertex 85 | 86 | #### Parameters: 87 | * `faces`: The mesh topology 88 | * `vertex_count`: (Optional) The number of vertices in the mesh. If not present, is computed from `faces`. 89 | 90 | #### Returns: 91 | An array of length `vertex_count`, containing for each vertex an array of all faces incident to it. 92 | 93 | #### Running time: `O(faces.length + vertex_count)` 94 | 95 | 96 | 97 | ## Mesh Repair and Validation 98 | 99 | ### `fuse_vertices` 100 | 101 | Welds nearby vertices together, removing small cracks and sliver faces within a mesh. 102 | 103 | #### Parameters: 104 | 105 | * `positions`: Vertex positions 106 | * `faces`: Mesh faces 107 | * `tolerance`: Distance within which vertices must be welded 108 | 109 | #### Returns: 110 | 111 | * `positions`: The updated fused positions 112 | * `faces`: Fused face topology 113 | 114 | #### Running time: `O(positions.length + faces.length)` 115 | 116 | 117 | ## Implicit Function Modeling 118 | 119 | Trimesh.JS has 3 different methods for converting implicit functions into triangulated meshes. They each take the same arguments and produce similar results. For more information about the differences, see the following post: http://0fps.wordpress.com/2012/07/12/smooth-voxel-terrain-part-2/ 120 | 121 | 122 | ### `marching_cubes`, `marching_tetrahedra`, `surface_nets` 123 | 124 | #### Parameters: 125 | 126 | * `potential` : A function f(x,y,z) that represents the potential function. (<0 for in, >0 for out) 127 | * `resolution` : A triple [nx,ny,nz] representing the resolution of the isosurface 128 | * `bounds`: (Optional) A pair of ranges [[x0,y0,z0], [x1,y1,z1]] representing the bounds for the volume. 129 | 130 | #### Returns: 131 | 132 | * `positions`: The positions of the vertices on the isosurface 133 | * `faces`: The faces of the isosurface 134 | 135 | #### Running time: Makes `O(nx * ny * nz)` calls to `potential()`. 136 | 137 | 138 | 139 | ## Differential Geometry 140 | 141 | ### `vertex_normals` 142 | 143 | #### Parameters: 144 | 145 | * `positions` 146 | * `faces` 147 | * `stars` (Optional) 148 | 149 | #### Returns: 150 | 151 | An array representing an estimate for the normals of the mesh computed using cotangent weights. 152 | 153 | #### Running time: `O( |stars| )` 154 | 155 | ### `face_normals` 156 | 157 | #### Parameters: 158 | 159 | * `positions` 160 | * `faces` 161 | 162 | #### Returns: 163 | 164 | An array of normals for each face 165 | 166 | #### Running time: `O( number of faces )` 167 | 168 | ### `geodesic_distance` 169 | 170 | #### Parameters: 171 | 172 | * `initial_vertex` 173 | * `positions` 174 | * `faces` 175 | * `max_distance` (Optional) 176 | * `stars` (Optional) 177 | 178 | #### Returns: 179 | 180 | Hash map of distance to point 181 | 182 | #### Running Time: Worse than `O( number of vertices^2 )` (bad) 183 | 184 | #### Notes: 185 | 186 | The current implementation of this method is bad. 187 | 188 | ## Subdivision Surfaces 189 | 190 | ### `loop_subdivision` 191 | 192 | Evaluates one iteration of Loop's algorithm 193 | 194 | #### Parameters: 195 | 196 | * `positions` 197 | * `faces` 198 | * `stars` (Optional) 199 | 200 | #### Returns: 201 | 202 | * `positions` 203 | * `faces` 204 | 205 | 206 | 207 | # Acknowledgements 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Huge list of stuff to do: 2 | 3 | Mesh repair: 4 | Check manifoldness 5 | Fix orientation 6 | 7 | Custom tolerances? 8 | 9 | Subdivision surfaces: 10 | Loop 11 | sqrt(3) 12 | 13 | Surface simplification 14 | 15 | Differential stuff: 16 | Tangent space calculations 17 | Exponential maps 18 | Parallel transport 19 | 20 | Metrics: 21 | Minkowski functionals 22 | 23 | Geometry queries: 24 | Closest feature 25 | Distance to point 26 | Point membership test 27 | 28 | Finite element: 29 | Stiffness matrix assembly 30 | Laplacian eigen functions 31 | 32 | Solid modeling: 33 | Booleans 34 | Minkowski sums 35 | 36 | Interoperability: 37 | parsers/exporters for different STL, VRML, etc. 38 | 39 | ...and probably much more 40 | 41 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir build 3 | ./node_modules/browserify/bin/cmd.js ./client.js -o ./build/trimesh.js 4 | ./node_modules/uglify-js/bin/uglifyjs < ./build/trimesh.js > ./build/trimesh.js.min 5 | 6 | -------------------------------------------------------------------------------- /build/trimesh.js.min: -------------------------------------------------------------------------------- 1 | (function(){var e=function(t,n){var r=e.resolve(t,n||"/"),i=e.modules[r];if(!i)throw new Error("Failed to resolve module "+t+", tried "+r);var s=e.cache[r],o=s?s.exports:i();return o};e.paths=[],e.modules={},e.cache={},e.extensions=[".js",".coffee",".json"],e._core={assert:!0,events:!0,fs:!0,path:!0,vm:!0},e.resolve=function(){return function(t,n){function u(t){t=r.normalize(t);if(e.modules[t])return t;for(var n=0;n=0;i--){if(t[i]==="node_modules")continue;var s=t.slice(0,i+1).join("/")+"/node_modules";n.push(s)}return n}n||(n="/");if(e._core[t])return t;var r=e.modules.path();n=r.resolve("/",n);var i=n||"/";if(t.match(/^(?:\.\.?\/|\/)/)){var s=u(r.resolve(i,t))||a(r.resolve(i,t));if(s)return s}var o=f(t,i);if(o)return o;throw new Error("Cannot find module '"+t+"'")}}(),e.alias=function(t,n){var r=e.modules.path(),i=null;try{i=e.resolve(t+"/package.json","/")}catch(s){i=e.resolve(t,"/")}var o=r.dirname(i),u=(Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t})(e.modules);for(var a=0;a=0;r--){var i=e[r];i=="."?e.splice(r,1):i===".."?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;n)e.unshift("..");return e}var f=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;n.resolve=function(){var e="",t=!1;for(var n=arguments.length;n>=-1&&!t;n--){var r=n>=0?arguments[n]:s.cwd();if(typeof r!="string"||!r)continue;e=r+"/"+e,t=r.charAt(0)==="/"}return e=a(u(e.split("/"),function(e){return!!e}),!t).join("/"),(t?"/":"")+e||"."},n.normalize=function(e){var t=e.charAt(0)==="/",n=e.slice(-1)==="/";return e=a(u(e.split("/"),function(e){return!!e}),!t).join("/"),!e&&!t&&(e="."),e&&n&&(e+="/"),(t?"/":"")+e},n.join=function(){var e=Array.prototype.slice.call(arguments,0);return n.normalize(u(e,function(e,t){return e&&typeof e=="string"}).join("/"))},n.dirname=function(e){var t=f.exec(e)[1]||"",n=!1;return t?t.length===1||n&&t.length<=3&&t.charAt(1)===":"?t:t.substring(0,t.length-1):"."},n.basename=function(e,t){var n=f.exec(e)[2]||"";return t&&n.substr(-1*t.length)===t&&(n=n.substr(0,n.length-t.length)),n},n.extname=function(e){return f.exec(e)[3]||""}}),e.define("__browserify_process",function(e,t,n,r,i,s,o){var s=t.exports={};s.nextTick=function(){var e=typeof window!="undefined"&&window.setImmediate,t=typeof window!="undefined"&&window.postMessage&&window.addEventListener;if(e)return function(e){return window.setImmediate(e)};if(t){var n=[];return window.addEventListener("message",function(e){if(e.source===window&&e.data==="browserify-tick"){e.stopPropagation();if(n.length>0){var t=n.shift();t()}}},!0),function(t){n.push(t),window.postMessage("browserify-tick","*")}}return function(t){setTimeout(t,0)}}(),s.title="browser",s.browser=!0,s.env={},s.argv=[],s.binding=function(t){if(t==="evals")return e("vm");throw new Error("No such module. (Possibly not yet loaded)")},function(){var t="/",n;s.cwd=function(){return t},s.chdir=function(r){n||(n=e("path")),t=n.resolve(r,t)}}()}),e.define("/package.json",function(e,t,n,r,i,s,o){t.exports={main:"index.js"}}),e.define("/index.js",function(e,t,n,r,i,s,o){var u=e("./src/topology.js");n.vertex_stars=u.vertex_stars,n.edges=u.edges;var a=e("./src/repair.js");n.fuse_vertices=a.fuse_vertices,n.marching_cubes=e("./src/marchingcubes.js").marching_cubes,n.marching_tetrahedra=e("./src/marchingtetrahedra.js").marching_tetrahedra,n.surface_nets=e("./src/surfacenets.js").surface_nets;var f=e("./src/normals.js");n.vertex_normals=f.vertex_normals,n.face_normals=f.face_normals,n.surface_distance_to_point=e("./src/metric.js").surface_distance_to_point;var l=e("./src/shapes.js");n.grid_mesh=l.grid_mesh,n.cube_mesh=l.cube_mesh,n.sphere_mesh=l.sphere_mesh,n.loop_subdivision=e("./src/loop_subdivision.js").loop_subdivision}),e.define("/src/topology.js",function(e,t,n,r,i,s,o){function u(e){var t=0;for(var n=0;n0?1<1e-6&&(N=S/T);for(var C=0;C<3;++C)y[C]=h[C]+w[C]+N*(E[C]-w[C]);r.push(y)}var k=a[p];for(var d=0;d1e-6&&(c=n/c);for(var h=0;h<3;++h)f[h]+=s[h]+c*(a[h]-s[h]);return r.push(f),r.length-1}var t=e.potential,n=e.resolution,r=[],i=[],s=0,o=new Float32Array(8),f=new Int32Array(12),l=[0,0,0];for(l[2]=0;l[2]>1):0}a[t]=i}})();var f=new Array(4096);(function(){for(var e=0;ef.length){var p=f.length;f.length=l[2]*2;while(p1e-6))continue;L=C/L;for(var y=0,g=1;y<3;++y,g<<=1){var A=T&g,O=N&g;A!==O?S[y]+=A?1-L:L:S[y]+=A?1:0}}var M=1/x;for(var b=0;b<3;++b)S[b]=o[b]+M*S[b];f[d]=r.length,r.push(S);for(var b=0;b<3;++b){if(!(E&1<u){var w=i[f],E=1/Math.sqrt(m*y);for(var b=0;b<3;++b){var S=(b+1)%3,x=(b+2)%3;w[b]+=E*(g[S]*v[x]-g[x]*v[S])}}}}for(var s=0;su){var E=1/Math.sqrt(T);for(var b=0;b<3;++b)w[b]*=E}else for(var b=0;b<3;++b)w[b]=0}return i},n.face_normals=function(e){var t=e.positions,n=e.faces,r=n.length,i=new Array(r);for(var s=0;su?p=1/Math.sqrt(p):p=0;for(var f=0;f<3;++f)h[f]*=p;i[s]=h}return i}}),e.define("/src/metric.js",function(e,t,n,r,i,s,o){function c(e,t,n,r,i,s){var o=new Array(3),u=new Array(3),a=0;for(var f=0;f<3;++f)o[f]=t[f]-e[f],a+=o[f]*o[f],u[f]=n[f]-e[f];if(a0){var h=u.pop();if(h.v in l)continue;var p=h.d,d=h.v;l[d]=p;var v=t[d],m=i[d];for(var g=0;gs)break;var m=i[d];for(var L=0;Ls)continue;var M=l[A[2]];Oo&&(l[A[2]]=Math.min(M,O),N=!1)}}if(N)return l}}var u=e("assert"),a=e("./heap.js").BinaryHeap,f=e("./topology.js").vertex_stars,l=1e-6;n.quadratic_distance=c,n.surface_distance_to_point=h}),e.define("assert",function(e,t,n,r,i,s,o){function c(e,t){return t===undefined?""+t:typeof t=="number"&&(isNaN(t)||!isFinite(t))?t.toString():typeof t=="function"||t instanceof RegExp?t.toString():t}function h(e,t){return typeof e=="string"?e.length=0;s--)if(n[s]!=r[s])return!1;for(s=n.length-1;s>=0;s--){i=n[s];if(!v(e[i],t[i]))return!1}return!0}function b(e,t){return!e||!t?!1:t instanceof RegExp?t.test(e):e instanceof t?!0:t.call({},e)===!0?!0:!1}function w(e,t,n,r){var i;typeof n=="string"&&(r=n,n=null);try{t()}catch(s){i=s}r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!i&&p("Missing expected exception"+r),!e&&b(i,n)&&p("Got unwanted exception"+r);if(e&&i&&n&&!b(i,n)||!e&&i)throw i}var u=e("util"),a=e("buffer").Buffer,f=Array.prototype.slice,l=t.exports=d;l.AssertionError=function(t){this.name="AssertionError",this.message=t.message,this.actual=t.actual,this.expected=t.expected,this.operator=t.operator;var n=t.stackStartFunction||p;Error.captureStackTrace&&Error.captureStackTrace(this,n)},u.inherits(l.AssertionError,Error),l.AssertionError.prototype.toString=function(){return this.message?[this.name+":",this.message].join(" "):[this.name+":",h(JSON.stringify(this.actual,c),128),this.operator,h(JSON.stringify(this.expected,c),128)].join(" ")},l.AssertionError.__proto__=Error.prototype,l.fail=p,l.ok=d,l.equal=function(t,n,r){t!=n&&p(t,n,r,"==",l.equal)},l.notEqual=function(t,n,r){t==n&&p(t,n,r,"!=",l.notEqual)},l.deepEqual=function(t,n,r){v(t,n)||p(t,n,r,"deepEqual",l.deepEqual)},l.notDeepEqual=function(t,n,r){v(t,n)&&p(t,n,r,"notDeepEqual",l.notDeepEqual)},l.strictEqual=function(t,n,r){t!==n&&p(t,n,r,"===",l.strictEqual)},l.notStrictEqual=function(t,n,r){t===n&&p(t,n,r,"!==",l.notStrictEqual)},l.throws=function(e,t,n){w.apply(this,[!0].concat(f.call(arguments)))},l.doesNotThrow=function(e,t,n){w.apply(this,[!1].concat(f.call(arguments)))},l.ifError=function(e){if(e)throw e}}),e.define("util",function(e,t,n,r,i,s,o){function a(e){return e instanceof Array||Array.isArray(e)||e&&e!==Object.prototype&&a(e.__proto__)}function f(e){return e instanceof RegExp||typeof e=="object"&&Object.prototype.toString.call(e)==="[object RegExp]"}function l(e){if(e instanceof Date)return!0;if(typeof e!="object")return!1;var t=Date.prototype&&v(Date.prototype),n=e.__proto__&&v(e.__proto__);return JSON.stringify(n)===JSON.stringify(t)}function c(e){return e<10?"0"+e.toString(10):e.toString(10)}function p(){var e=new Date,t=[c(e.getHours()),c(e.getMinutes()),c(e.getSeconds())].join(":");return[e.getDate(),h[e.getMonth()],t].join(" ")}var u=e("events");n.print=function(){},n.puts=function(){},n.debug=function(){},n.inspect=function(e,t,r,i){function u(e,r){if(e&&typeof e.inspect=="function"&&e!==n&&(!e.constructor||e.constructor.prototype!==e))return e.inspect(r);switch(typeof e){case"undefined":return o("undefined","undefined");case"string":var i="'"+JSON.stringify(e).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return o(i,"string");case"number":return o(""+e,"number");case"boolean":return o(""+e,"boolean")}if(e===null)return o("null","null");var c=d(e),h=t?v(e):c;if(typeof e=="function"&&h.length===0){if(f(e))return o(""+e,"regexp");var p=e.name?": "+e.name:"";return o("[Function"+p+"]","special")}if(l(e)&&h.length===0)return o(e.toUTCString(),"date");var m,g,y;a(e)?(g="Array",y=["[","]"]):(g="Object",y=["{","}"]);if(typeof e=="function"){var b=e.name?": "+e.name:"";m=f(e)?" "+e:" [Function"+b+"]"}else m="";l(e)&&(m=" "+e.toUTCString());if(h.length===0)return y[0]+m+y[1];if(r<0)return f(e)?o(""+e,"regexp"):o("[Object]","special");s.push(e);var w=h.map(function(t){var n,i;e.__lookupGetter__&&(e.__lookupGetter__(t)?e.__lookupSetter__(t)?i=o("[Getter/Setter]","special"):i=o("[Getter]","special"):e.__lookupSetter__(t)&&(i=o("[Setter]","special"))),c.indexOf(t)<0&&(n="["+t+"]"),i||(s.indexOf(e[t])<0?(r===null?i=u(e[t]):i=u(e[t],r-1),i.indexOf("\n")>-1&&(a(e)?i=i.split("\n").map(function(e){return" "+e}).join("\n").substr(2):i="\n"+i.split("\n").map(function(e){return" "+e}).join("\n"))):i=o("[Circular]","special"));if(typeof n=="undefined"){if(g==="Array"&&t.match(/^\d+$/))return i;n=JSON.stringify(""+t),n.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(n=n.substr(1,n.length-2),n=o(n,"name")):(n=n.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),n=o(n,"string"))}return n+": "+i});s.pop();var E=0,S=w.reduce(function(e,t){return E++,t.indexOf("\n")>=0&&E++,e+t.length+1},0);return S>50?w=y[0]+(m===""?"":m+"\n ")+" "+w.join(",\n ")+" "+y[1]:w=y[0]+m+" "+w.join(", ")+" "+y[1],w}var s=[],o=function(e,t){var n={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},r={special:"cyan",number:"blue","boolean":"yellow","undefined":"grey","null":"bold",string:"green",date:"magenta",regexp:"red"}[t];return r?"["+n[r][0]+"m"+e+"["+n[r][1]+"m":e};return i||(o=function(e,t){return e}),u(e,typeof r=="undefined"?2:r)};var h=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];n.log=function(e){},n.pump=null;var d=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t},v=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.hasOwnProperty.call(e,n)&&t.push(n);return t},m=Object.create||function(e,t){var n;if(e===null)n={__proto__:null};else{if(typeof e!="object")throw new TypeError("typeof prototype["+typeof e+"] != 'object'");var r=function(){};r.prototype=e,n=new r,n.__proto__=e}return typeof t!="undefined"&&Object.defineProperties&&Object.defineProperties(n,t),n};n.inherits=function(e,t){e.super_=t,e.prototype=m(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})};var g=/%[sdj%]/g;n.format=function(e){if(typeof e!="string"){var t=[];for(var r=0;r=s)return e;switch(e){case"%s":return String(i[r++]);case"%d":return Number(i[r++]);case"%j":return JSON.stringify(i[r++]);default:return e}});for(var u=i[r];r0&&this._events[e].length>n&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),console.trace())}this._events[e].push(t)}else this._events[e]=[this._events[e],t];return this},u.prototype.on=u.prototype.addListener,u.prototype.once=function(e,t){var n=this;return n.on(e,function r(){n.removeListener(e,r),t.apply(this,arguments)}),this},u.prototype.removeListener=function(e,t){if("function"!=typeof t)throw new Error("removeListener only takes instances of Function");if(!this._events||!this._events[e])return this;var n=this._events[e];if(a(n)){var r=n.indexOf(t);if(r<0)return this;n.splice(r,1),n.length==0&&delete this._events[e]}else this._events[e]===t&&delete this._events[e];return this},u.prototype.removeAllListeners=function(e){return e&&this._events&&this._events[e]&&(this._events[e]=null),this},u.prototype.listeners=function(e){return this._events||(this._events={}),this._events[e]||(this._events[e]=[]),a(this._events[e])||(this._events[e]=[this._events[e]]),this._events[e]}}),e.define("buffer",function(e,t,n,r,i,s,o){t.exports=e("buffer-browserify")}),e.define("/node_modules/buffer-browserify/package.json",function(e,t,n,r,i,s,o){t.exports={main:"index.js",browserify:"index.js"}}),e.define("/node_modules/buffer-browserify/index.js",function(e,t,n,r,i,s,o){function u(e){this.length=e}function f(e){return e<16?"0"+e.toString(16):e.toString(16)}function l(e){var t=[];for(var n=0;n=t.length||s>=e.length)break;t[s+n]=e[s],s++}return s}function d(e){try{return decodeURIComponent(e)}catch(t){return String.fromCharCode(65533)}}function v(e){return e=~~Math.ceil(+e),e<0?0:e}function m(e,t,n){if(!(this instanceof m))return new m(e,t,n);var r;if(typeof n=="number")this.length=v(t),this.parent=e,this.offset=n;else{switch(r=typeof e){case"number":this.length=v(e);break;case"string":this.length=m.byteLength(e,t);break;case"object":this.length=v(e.length);break;default:throw new Error("First argument needs to be a number, array or string.")}this.length>m.poolSize?(this.parent=new u(this.length),this.offset=0):((!y||y.length-y.used>>0 2 | ):(i=e.parent[e.offset+t+2]<<16,i|=e.parent[e.offset+t+1]<<8,i|=e.parent[e.offset+t],i+=e.parent[e.offset+t+3]<<24>>>0),i}function S(e,t,n,r){var i,s;return r||(a.ok(typeof n=="boolean","missing or invalid endian"),a.ok(t!==undefined&&t!==null,"missing offset"),a.ok(t+1=0,"specified a negative value for writing an unsigned value"),a.ok(e<=t,"value is larger than maximum value for type"),a.ok(Math.floor(e)===e,"value has a fractional component")}function k(e,t,n,r,i){i||(a.ok(t!==undefined&&t!==null,"missing value"),a.ok(typeof r=="boolean","missing or invalid endian"),a.ok(n!==undefined&&n!==null,"missing offset"),a.ok(n+1>>8,e.parent[e.offset+n+1]=t&255):(e.parent[e.offset+n+1]=(t&65280)>>>8,e.parent[e.offset+n]=t&255)}function L(e,t,n,r,i){i||(a.ok(t!==undefined&&t!==null,"missing value"),a.ok(typeof r=="boolean","missing or invalid endian"),a.ok(n!==undefined&&n!==null,"missing offset"),a.ok(n+3>>24&255,e.parent[e.offset+n+1]=t>>>16&255,e.parent[e.offset+n+2]=t>>>8&255,e.parent[e.offset+n+3]=t&255):(e.parent[e.offset+n+3]=t>>>24&255,e.parent[e.offset+n+2]=t>>>16&255,e.parent[e.offset+n+1]=t>>>8&255,e.parent[e.offset+n]=t&255)}function A(e,t,n){a.ok(typeof e=="number","cannot write a non-number as a number"),a.ok(e<=t,"value larger than maximum allowed value"),a.ok(e>=n,"value smaller than minimum allowed value"),a.ok(Math.floor(e)===e,"value has a fractional component")}function O(e,t,n){a.ok(typeof e=="number","cannot write a non-number as a number"),a.ok(e<=t,"value larger than maximum allowed value"),a.ok(e>=n,"value smaller than minimum allowed value")}function M(e,t,n,r,i){i||(a.ok(t!==undefined&&t!==null,"missing value"),a.ok(typeof r=="boolean","missing or invalid endian"),a.ok(n!==undefined&&n!==null,"missing offset"),a.ok(n+1=0?k(e,t,n,r,i):k(e,65535+t+1,n,r,i)}function _(e,t,n,r,i){i||(a.ok(t!==undefined&&t!==null,"missing value"),a.ok(typeof r=="boolean","missing or invalid endian"),a.ok(n!==undefined&&n!==null,"missing offset"),a.ok(n+3=0?L(e,t,n,r,i):L(e,4294967295+t+1,n,r,i)}function D(t,n,r,i,s){s||(a.ok(n!==undefined&&n!==null,"missing value"),a.ok(typeof i=="boolean","missing or invalid endian"),a.ok(r!==undefined&&r!==null,"missing offset"),a.ok(r+3"},u.prototype.hexSlice=function(e,t){var n=this.length;if(!e||e<0)e=0;if(!t||t<0||t>n)t=n;var r="";for(var i=e;ir&&(n=r)):n=r;var i=e.length;if(i%2)throw new Error("Invalid hex string");n>i/2&&(n=i/2);for(var s=0;ss&&(n=s)):n=s,r=String(r||"utf8").toLowerCase();switch(r){case"hex":return this.hexWrite(e,t,n);case"utf8":case"utf-8":return this.utf8Write(e,t,n);case"ascii":return this.asciiWrite(e,t,n);case"binary":return this.binaryWrite(e,t,n);case"base64":return this.base64Write(e,t,n);case"ucs2":case"ucs-2":return this.ucs2Write(e,t,n);default:throw new Error("Unknown encoding")}},u.prototype.slice=function(e,t){t===undefined&&(t=this.length);if(t>this.length)throw new Error("oob");if(e>t)throw new Error("oob");return new m(this,t-e,+e)},u.prototype.copy=function(e,t,n,r){var i=[];for(var s=n;s"},m.prototype.get=function(t){if(t<0||t>=this.length)throw new Error("oob");return this.parent[this.offset+t]},m.prototype.set=function(t,n){if(t<0||t>=this.length)throw new Error("oob");return this.parent[this.offset+t]=n},m.prototype.write=function(e,t,n,r){if(isFinite(t))isFinite(n)||(r=n,n=undefined);else{var i=r;r=t,t=n,n=i}t=+t||0;var s=this.length-t;n?(n=+n,n>s&&(n=s)):n=s,r=String(r||"utf8").toLowerCase();var o;switch(r){case"hex":o=this.parent.hexWrite(e,this.offset+t,n);break;case"utf8":case"utf-8":o=this.parent.utf8Write(e,this.offset+t,n);break;case"ascii":o=this.parent.asciiWrite(e,this.offset+t,n);break;case"binary":o=this.parent.binaryWrite(e,this.offset+t,n);break;case"base64":o=this.parent.base64Write(e,this.offset+t,n);break;case"ucs2":case"ucs-2":o=this.parent.ucs2Write(e,this.offset+t,n);break;default:throw new Error("Unknown encoding")}return m._charsWritten=u._charsWritten,o},m.prototype.toString=function(e,t,n){e=String(e||"utf8").toLowerCase(),typeof t=="undefined"||t<0?t=0:t>this.length&&(t=this.length),typeof n=="undefined"||n>this.length?n=this.length:n<0&&(n=0),t+=this.offset,n+=this.offset;switch(e){case"hex":return this.parent.hexSlice(t,n);case"utf8":case"utf-8":return this.parent.utf8Slice(t,n);case"ascii":return this.parent.asciiSlice(t,n);case"binary":return this.parent.binarySlice(t,n);case"base64":return this.parent.base64Slice(t,n);case"ucs2":case"ucs-2":return this.parent.ucs2Slice(t,n);default:throw new Error("Unknown encoding")}},m.byteLength=u.byteLength,m.prototype.fill=function(t,n,r){t||(t=0),n||(n=0),r||(r=this.length),typeof t=="string"&&(t=t.charCodeAt(0));if(typeof t!="number"||isNaN(t))throw new Error("value is not a number");if(r=this.length)throw new Error("start out of bounds");if(r<0||r>this.length)throw new Error("end out of bounds");return this.parent.fill(t,n+this.offset,r+this.offset)},m.prototype.copy=function(e,t,n,r){var i=this;n||(n=0),r||(r=this.length),t||(t=0);if(r=e.length)throw new Error("targetStart out of bounds");if(n<0||n>=i.length)throw new Error("sourceStart out of bounds");if(r<0||r>i.length)throw new Error("sourceEnd out of bounds");return r>this.length&&(r=this.length),e.length-tthis.length)throw new Error("oob");if(e>t)throw new Error("oob");return new m(this.parent,t-e,+e+this.offset)},m.prototype.utf8Slice=function(e,t){return this.toString("utf8",e,t)},m.prototype.binarySlice=function(e,t){return this.toString("binary",e,t)},m.prototype.asciiSlice=function(e,t){return this.toString("ascii",e,t)},m.prototype.utf8Write=function(e,t){return this.write(e,t,"utf8")},m.prototype.binaryWrite=function(e,t){return this.write(e,t,"binary")},m.prototype.asciiWrite=function(e,t){return this.write(e,t,"ascii")},m.prototype.readUInt8=function(e,t){var n=this;return t||(a.ok(e!==undefined&&e!==null,"missing offset"),a.ok(e=0?r.writeUInt8(e,t,n):r.writeUInt8(255+e+1,t,n)},m.prototype.writeInt16LE=function(e,t,n){M(this,e,t,!1,n)},m.prototype.writeInt16BE=function(e,t,n){M(this,e,t,!0,n)},m.prototype.writeInt32LE=function(e,t,n){_(this,e,t,!1,n)},m.prototype.writeInt32BE=function(e,t,n){_(this,e,t,!0,n)},m.prototype.writeFloatLE=function(e,t,n){D(this,e,t,!1,n)},m.prototype.writeFloatBE=function(e,t,n){D(this,e,t,!0,n)},m.prototype.writeDoubleLE=function(e,t,n){P(this,e,t,!1,n)},m.prototype.writeDoubleBE=function(e,t,n){P(this,e,t,!0,n)},u.prototype.readUInt8=m.prototype.readUInt8,u.prototype.readUInt16LE=m.prototype.readUInt16LE,u.prototype.readUInt16BE=m.prototype.readUInt16BE,u.prototype.readUInt32LE=m.prototype.readUInt32LE,u.prototype.readUInt32BE=m.prototype.readUInt32BE,u.prototype.readInt8=m.prototype.readInt8,u.prototype.readInt16LE=m.prototype.readInt16LE,u.prototype.readInt16BE=m.prototype.readInt16BE,u.prototype.readInt32LE=m.prototype.readInt32LE,u.prototype.readInt32BE=m.prototype.readInt32BE,u.prototype.readFloatLE=m.prototype.readFloatLE,u.prototype.readFloatBE=m.prototype.readFloatBE,u.prototype.readDoubleLE=m.prototype.readDoubleLE,u.prototype.readDoubleBE=m.prototype.readDoubleBE,u.prototype.writeUInt8=m.prototype.writeUInt8,u.prototype.writeUInt16LE=m.prototype.writeUInt16LE,u.prototype.writeUInt16BE=m.prototype.writeUInt16BE,u.prototype.writeUInt32LE=m.prototype.writeUInt32LE,u.prototype.writeUInt32BE=m.prototype.writeUInt32BE,u.prototype.writeInt8=m.prototype.writeInt8,u.prototype.writeInt16LE=m.prototype.writeInt16LE,u.prototype.writeInt16BE=m.prototype.writeInt16BE,u.prototype.writeInt32LE=m.prototype.writeInt32LE,u.prototype.writeInt32BE=m.prototype.writeInt32BE,u.prototype.writeFloatLE=m.prototype.writeFloatLE,u.prototype.writeFloatBE=m.prototype.writeFloatBE,u.prototype.writeDoubleLE=m.prototype.writeDoubleLE,u.prototype.writeDoubleBE=m.prototype.writeDoubleBE}),e.define("/node_modules/buffer-browserify/node_modules/base64-js/package.json",function(e,t,n,r,i,s,o){t.exports={main:"lib/b64.js"}}),e.define("/node_modules/buffer-browserify/node_modules/base64-js/lib/b64.js",function(e,t,n,r,i,s,o){(function(e){"use strict";function r(e){var t,r,i,s,o,u;if(e.length%4>0)throw"Invalid string. Length must be a multiple of 4";o=e.indexOf("="),o=o>0?e.length-o:0,u=[],i=o>0?e.length-4:e.length;for(t=0,r=0;t>16),u.push((s&65280)>>8),u.push(s&255);return o===2?(s=n.indexOf(e[t])<<2|n.indexOf(e[t+1])>>4,u.push(s&255)):o===1&&(s=n.indexOf(e[t])<<10|n.indexOf(e[t+1])<<4|n.indexOf(e[t+2])>>2,u.push(s>>8&255),u.push(s&255)),u}function i(e){function u(e){return n[e>>18&63]+n[e>>12&63]+n[e>>6&63]+n[e&63]}var t,r=e.length%3,i="",s,o;for(t=0,o=e.length-r;t>2],i+=n[s<<4&63],i+="==";break;case 2:s=(e[e.length-2]<<8)+e[e.length-1],i+=n[s>>10],i+=n[s>>4&63],i+=n[s<<2&63],i+="="}return i}var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";t.exports.toByteArray=r,t.exports.fromByteArray=i})()}),e.define("/node_modules/buffer-browserify/buffer_ieee754.js",function(e,t,n,r,i,s,o){n.readIEEE754=function(e,t,n,r,i){var s,o,u=i*8-r-1,a=(1<>1,l=-7,c=n?0:i-1,h=n?1:-1,p=e[t+c];c+=h,s=p&(1<<-l)-1,p>>=-l,l+=u;for(;l>0;s=s*256+e[t+c],c+=h,l-=8);o=s&(1<<-l)-1,s>>=-l,l+=r;for(;l>0;o=o*256+e[t+c],c+=h,l-=8);if(s===0)s=1-f;else{if(s===a)return o?NaN:(p?-1:1)*Infinity;o+=Math.pow(2,r),s-=f}return(p?-1:1)*o*Math.pow(2,s-r)},n.writeIEEE754=function(e,t,n,r,i,s){var o,u,a,f=s*8-i-1,l=(1<>1,h=i===23?Math.pow(2,-24)-Math.pow(2,-77):0,p=r?s-1:0,d=r?-1:1,v=t<0||t===0&&1/t<0?1:0;t=Math.abs(t),isNaN(t)||t===Infinity?(u=isNaN(t)?1:0,o=l):(o=Math.floor(Math.log(t)/Math.LN2),t*(a=Math.pow(2,-o))<1&&(o--,a*=2),o+c>=1?t+=h/a:t+=h*Math.pow(2,1-c),t*a>=2&&(o++,a/=2),o+c>=l?(u=0,o=l):o+c>=1?(u=(t*a-1)*Math.pow(2,i),o+=c):(u=t*Math.pow(2,c-1)*Math.pow(2,i),o=0));for(;i>=8;e[n+p]=u&255,p+=d,u/=256,i-=8);o=o<0;e[n+p]=o&255,p+=d,o/=256,f-=8);e[n+p-d]|=v*128}}),e.define("/src/heap.js",function(e,t,n,r,i,s,o){function u(e){this.content=[],this.scoreFunction=e}u.prototype={push:function(e){this.content.push(e),this.bubbleUp(this.content.length-1)},pop:function(){var e=this.content[0],t=this.content.pop();return this.content.length>0&&(this.content[0]=t,this.sinkDown(0)),e},remove:function(e){var t=this.content.length;for(var n=0;n0){var n=Math.floor((e+1)/2)-1,r=this.content[n];if(!(this.scoreFunction(t)>1,i=2*r+1,o=new Array(6*i*i),a=[];for(var f=0;f<3;++f){var l=(f+1)%3,c=(f+2)%3;for(var h=0;h<2;++h){var p=2*f+h,d=new Array(3);d[l]=-r,d[c]=-r,d[f]=(1-2*h)*r;for(var v=0;v= 0.8" 26 | } 27 | -------------------------------------------------------------------------------- /src/conway.js: -------------------------------------------------------------------------------- 1 | //Platonic solids (not triangulated) 2 | 3 | 4 | var cube = {}, 5 | tetrahedron = {}, 6 | icosahedron = {}; 7 | 8 | function reflect(params) { 9 | var positions = params.positions; 10 | var faces = params.faces; 11 | 12 | var npositions = new Array(positions.length); 13 | var nfaces = new Array(faces.length); 14 | 15 | for(var i=0; i 0) { 72 | var node = to_visit.pop(); 73 | if(node.v in distances) { 74 | continue; 75 | } 76 | 77 | var d = node.d; 78 | var v = node.v; 79 | distances[v] = d; 80 | 81 | var a = positions[v]; 82 | var nbhd = stars[v]; 83 | 84 | for(var i=0; i max_distance) { 131 | break; 132 | } 133 | 134 | //Iterate over all faces incident to v 135 | var nbhd = stars[v]; 136 | 137 | for(var nn=0; nn max_distance) { 156 | continue; 157 | } 158 | 159 | var o_distance = distances[face[2]]; 160 | 161 | //Update distance 162 | if(n_distance < o_distance && Math.abs(n_distance - o_distance) > tolerance) { 163 | distances[face[2]] = Math.min(o_distance, n_distance); 164 | stabilized = false; 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | 172 | //Computes a distances to a vertex p 173 | function geodesic_distance(args) { 174 | 175 | var positions = args.positions; 176 | var faces = args.faces; 177 | var p = args.initial_vertex; 178 | var stars = args.stars 179 | || vertex_stars({ vertex_count: positions.length, faces: faces }); 180 | var max_distance = args.max_distance 181 | || Number.POSITIVE_INFINITY; 182 | var tolerance = args.tolerance || 1e-4; 183 | 184 | //First, run Dijkstra's algorithm to get an initial bound on the distance from each vertex 185 | // to the base point just using edge lengths 186 | var distances = dijkstra(p, stars, faces, positions, max_distance); 187 | 188 | //Then refine distances to acceptable threshold 189 | refine_distances(p, distances, positions, stars, faces, max_distance, tolerance); 190 | 191 | return distances; 192 | } 193 | 194 | exports.quadratic_distance = quadratic_distance; 195 | exports.geodesic_distance = geodesic_distance; 196 | -------------------------------------------------------------------------------- /src/heap.js: -------------------------------------------------------------------------------- 1 | // Binary Heap 2 | // By: Marjin Haverbeke 3 | // Web: http://eloquentjavascript.net/appendix2.html 4 | 5 | function BinaryHeap(scoreFunction){ 6 | this.content = []; 7 | this.scoreFunction = scoreFunction; 8 | } 9 | 10 | BinaryHeap.prototype = { 11 | push: function(element) { 12 | // Add the new element to the end of the array. 13 | this.content.push(element); 14 | // Allow it to bubble up. 15 | this.bubbleUp(this.content.length - 1); 16 | }, 17 | 18 | pop: function() { 19 | // Store the first element so we can return it later. 20 | var result = this.content[0]; 21 | // Get the element at the end of the array. 22 | var end = this.content.pop(); 23 | // If there are any elements left, put the end element at the 24 | // start, and let it sink down. 25 | if (this.content.length > 0) { 26 | this.content[0] = end; 27 | this.sinkDown(0); 28 | } 29 | return result; 30 | }, 31 | 32 | remove: function(node) { 33 | var len = this.content.length; 34 | // To remove a value, we must search through the array to find 35 | // it. 36 | for (var i = 0; i < len; i++) { 37 | if (this.content[i] == node) { 38 | // When it is found, the process seen in 'pop' is repeated 39 | // to fill up the hole. 40 | var end = this.content.pop(); 41 | if (i != len - 1) { 42 | this.content[i] = end; 43 | if (this.scoreFunction(end) < this.scoreFunction(node)) 44 | this.bubbleUp(i); 45 | else 46 | this.sinkDown(i); 47 | } 48 | return; 49 | } 50 | } 51 | }, 52 | 53 | size: function() { 54 | return this.content.length; 55 | }, 56 | 57 | bubbleUp: function(n) { 58 | // Fetch the element that has to be moved. 59 | var element = this.content[n]; 60 | // When at 0, an element can not go up any further. 61 | while (n > 0) { 62 | // Compute the parent element's index, and fetch it. 63 | var parentN = Math.floor((n + 1) / 2) - 1, 64 | parent = this.content[parentN]; 65 | // Swap the elements if the parent is greater. 66 | if (this.scoreFunction(element) < this.scoreFunction(parent)) { 67 | this.content[parentN] = element; 68 | this.content[n] = parent; 69 | // Update 'n' to continue at the new position. 70 | n = parentN; 71 | } 72 | // Found a parent that is less, no need to move it further. 73 | else { 74 | break; 75 | } 76 | } 77 | }, 78 | 79 | sinkDown: function(n) { 80 | // Look up the target element and its score. 81 | var length = this.content.length, 82 | element = this.content[n], 83 | elemScore = this.scoreFunction(element); 84 | 85 | while(true) { 86 | // Compute the indices of the child elements. 87 | var child2N = (n + 1) * 2, child1N = child2N - 1; 88 | // This is used to store the new position of the element, 89 | // if any. 90 | var swap = null; 91 | // If the first child exists (is inside the array)... 92 | if (child1N < length) { 93 | // Look it up and compute its score. 94 | var child1 = this.content[child1N], 95 | child1Score = this.scoreFunction(child1); 96 | // If the score is less than our element's, we need to swap. 97 | if (child1Score < elemScore) 98 | swap = child1N; 99 | } 100 | // Do the same checks for the other child. 101 | if (child2N < length) { 102 | var child2 = this.content[child2N], 103 | child2Score = this.scoreFunction(child2); 104 | if (child2Score < (swap == null ? elemScore : child1Score)) 105 | swap = child2N; 106 | } 107 | 108 | // If the element needs to be moved, swap it, and continue. 109 | if (swap != null) { 110 | this.content[n] = this.content[swap]; 111 | this.content[swap] = element; 112 | n = swap; 113 | } 114 | // Otherwise, we are done. 115 | else { 116 | break; 117 | } 118 | } 119 | } 120 | }; 121 | 122 | 123 | if(typeof(exports) !== "undefined") { 124 | exports.BinaryHeap = BinaryHeap; 125 | } 126 | 127 | -------------------------------------------------------------------------------- /src/loop_subdivision.js: -------------------------------------------------------------------------------- 1 | var topology = require('./topology.js'); 2 | 3 | function opposite(u, v, f) { 4 | for(var i=0; i<3; ++i) { 5 | if(f[i] !== u && f[i] !== v) { 6 | return f[i]; 7 | } 8 | } 9 | return 0; 10 | } 11 | 12 | //A super inefficient implementation of Loop's algorithm 13 | exports.loop_subdivision = function(args) { 14 | var positions = args.positions; 15 | var faces = args.faces; 16 | var edges = args.edges || topology.edges({faces: faces}); 17 | var stars = args.stars || topology.vertex_stars({ vertex_count: positions.length, faces: faces }); 18 | var npositions = []; 19 | var nfaces = []; 20 | var e_indices = {}; 21 | var v_indices = new Array(positions.length); 22 | 23 | var e_verts = new Array(3); 24 | var v_verts = new Array(3); 25 | 26 | for(var f=0; f 0) ? 1 << i : 0; 344 | } 345 | //Compute vertices 346 | var edge_mask = edgeTable[cube_index]; 347 | if(edge_mask === 0) { 348 | continue; 349 | } 350 | for(var i=0; i<12; ++i) { 351 | if((edge_mask & (1< 1e-6) { 364 | t = a / d; 365 | } 366 | for(var j=0; j<3; ++j) { 367 | nv[j] = scale[j] * ((x[j] + p0[j]) + t * (p1[j] - p0[j])) + shift[j]; 368 | } 369 | vertices.push(nv); 370 | } 371 | //Add faces 372 | var f = triTable[cube_index]; 373 | for(var i=0; i 1e-6) { 55 | t = g0 / t; 56 | } 57 | for(var i=0; i<3; ++i) { 58 | v[i] = scale[i] * (v[i] + p0[i] + t * (p1[i] - p0[i])) + shift[i]; 59 | } 60 | vertices.push(v); 61 | return vertices.length - 1; 62 | } 63 | 64 | //March over the volume 65 | for(x[2]=0; x[2] EPSILON) { 47 | var norm = normals[c]; 48 | var w = 1.0 / Math.sqrt(m01 * m21); 49 | for(var k=0; k<3; ++k) { 50 | var u = (k+1)%3; 51 | var v = (k+2)%3; 52 | norm[k] += w * (d21[u] * d01[v] - d21[v] * d01[u]); 53 | } 54 | } 55 | } 56 | } 57 | 58 | //Scale all normals to unit length 59 | for(var i=0; i EPSILON) { 66 | var w = 1.0 / Math.sqrt(m); 67 | for(var k=0; k<3; ++k) { 68 | norm[k] *= w; 69 | } 70 | } else { 71 | for(var k=0; k<3; ++k) { 72 | norm[k] = 0.0; 73 | } 74 | } 75 | } 76 | 77 | //Return the resulting set of patches 78 | return normals; 79 | } 80 | 81 | //Compute face normals of a mesh 82 | exports.face_normals = function(args) { 83 | var positions = args.positions; 84 | var faces = args.faces; 85 | var N = faces.length; 86 | var normals = new Array(N); 87 | 88 | for(var i=0; i EPSILON) { 111 | l = 1.0 / Math.sqrt(l); 112 | } else { 113 | l = 0.0; 114 | } 115 | for(var j=0; j<3; ++j) { 116 | n[j] *= l; 117 | } 118 | normals[i] = n; 119 | } 120 | return normals; 121 | } 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/obj.js: -------------------------------------------------------------------------------- 1 | exports.parse_obj = function(obj) { 2 | var lines = obj.split("\n"); 3 | 4 | var positions = []; 5 | var texture_coordinates = []; 6 | var normals = []; 7 | var parameters = []; 8 | var faces = []; 9 | var materials = {}; 10 | for(var i=0; i> 1; 38 | var side_len = 2*radius + 1; 39 | function p(x,y,s) { 40 | return x + side_len * (y + side_len * s); 41 | } 42 | 43 | var positions = new Array(6 * side_len * side_len); 44 | var faces = []; 45 | 46 | for(var d=0; d<3; ++d) { 47 | var u = (d+1)%3; 48 | var v = (d+2)%3; 49 | 50 | for(var s=0; s<2; ++s) { 51 | var f = 2*d + s; 52 | var x = new Array(3); 53 | 54 | x[u] = -radius; 55 | x[v] = -radius; 56 | x[d] = (1 - 2*s) * radius; 57 | 58 | for(var j=0; j 2^(edge configuration) map 33 | // There is one entry for each possible cube configuration, and the output is a 12-bit vector enumerating all edges crossing the 0-level. 34 | for(var i=0; i<256; ++i) { 35 | var em = 0; 36 | for(var j=0; j<24; j+=2) { 37 | var a = !!(i & (1<> 1)) : 0; 40 | } 41 | edge_table[i] = em; 42 | } 43 | })(); 44 | 45 | //Internal buffer, this may get resized at run time 46 | var buffer = new Array(4096); 47 | (function() { 48 | for(var i=0; i buffer.length) { 79 | var ol = buffer.length; 80 | buffer.length = R[2] * 2; 81 | while(ol < buffer.length) { 82 | buffer[ol++] = 0; 83 | } 84 | } 85 | 86 | //March over the voxel grid 87 | for(x[2]=0; x[2] 1e-6) { 139 | t = g0 / t; 140 | } else { 141 | continue; 142 | } 143 | 144 | //Interpolate vertices and add up intersections (this can be done without multiplying) 145 | for(var j=0, k=1; j<3; ++j, k<<=1) { 146 | var a = e0 & k 147 | , b = e1 & k; 148 | if(a !== b) { 149 | v[j] += a ? 1.0 - t : t; 150 | } else { 151 | v[j] += a ? 1.0 : 0; 152 | } 153 | } 154 | } 155 | 156 | //Now we just average the edge intersections and add them to coordinate 157 | var s = 1.0 / e_count; 158 | for(var i=0; i<3; ++i) { 159 | v[i] = scale[i] * (x[i] + s * v[i]) + shift[i]; 160 | } 161 | 162 | //Add vertex to buffer, store pointer to vertex index in buffer 163 | buffer[m] = vertices.length; 164 | vertices.push(v); 165 | 166 | //Now we need to add faces together, to do this we just loop over 3 basis components 167 | for(var i=0; i<3; ++i) { 168 | //The first three entries of the edge_mask count the crossings along the edge 169 | if(!(edge_mask & (1<" + i + ". Expected: " + d + ", got: " + distances[i]); 85 | } 86 | } 87 | }, 88 | 89 | //This still doesn't quite work... 90 | 'sphere': { 91 | topic: shapes.sphere_mesh(10, 1.0), 92 | 93 | 'testing sphere pairwise distances': function(sphere) { 94 | 95 | for(var i=0; i<100; ++i) { 96 | var p_idx = Math.floor(Math.random() * sphere.positions.length); 97 | var distances = distance.geodesic_distance({ 98 | positions: sphere.positions, 99 | faces: sphere.faces, 100 | initial_vertex: p_idx, 101 | max_distance: Math.PI/4.0 102 | }); 103 | var p = sphere.positions[p_idx]; 104 | 105 | for(var j in distances) { 106 | if(distances[j] > Math.PI/4) { 107 | continue; 108 | } 109 | 110 | var q = sphere.positions[j]; 111 | var angle = 0.0; 112 | for(var k=0; k<3; ++k) { 113 | angle += p[k] * q[k]; 114 | } 115 | angle = Math.min(1.0, Math.max(-1.0, angle)); 116 | angle = Math.acos(angle); 117 | 118 | assert.ok(Math.abs(distances[j] - angle) < 0.2, "Incorrect distance from " + i + "->" + j + ". Expected: " + angle + ", got: " + distances[j]); 119 | } 120 | } 121 | 122 | } 123 | }, 124 | }, 125 | }).run(); 126 | 127 | 128 | -------------------------------------------------------------------------------- /tests/normals.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'); 2 | var assert = require('assert'); 3 | var normals = require('../src/normals.js'); 4 | var vertex_normals = normals.vertex_normals; 5 | var face_normals = normals.face_normals; 6 | var shapes = require('../src/shapes.js'); 7 | 8 | 9 | vows.describe("surface normals").addBatch({ 10 | 'normals' : { 11 | topic: shapes.sphere_mesh(20, 1.0), 12 | 13 | 'testing vertex normals': function(sphere) { 14 | 15 | var normals = vertex_normals(sphere); 16 | 17 | for(var i=0; i= 0, 'Invalid face in star #' + i + ", face=" + mesh.faces[s[j]] + ", star= " + stars[i]); 20 | } 21 | } 22 | }, 23 | } 24 | }).run(); 25 | 26 | 27 | --------------------------------------------------------------------------------