├── .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 |
--------------------------------------------------------------------------------