├── .gitignore
├── LICENSE
├── README.md
├── bench
├── contour3d.js
└── isosurface3d.js
├── example
├── 2d.svg
├── 3d.stl
├── 3d.svg
├── example-1d.js
├── example-2d.js
├── example-3d-stl.js
└── example-3d.js
├── package.json
├── surfacenets.js
└── test
└── test.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 | npm-debug.log
15 | node_modules/*
16 | *.DS_Store
--------------------------------------------------------------------------------
/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 | surface-nets
2 | ============
3 | Extract a simplicial level set from an [ndarray](https://github.com/mikolalysenko/ndarray) in any dimension using naive surface nets. This module works in both node.js and with [browserify](http://browserify.org/)!
4 |
5 |
6 | If you are doing experiments with greedy meshing or working with rectangular data, then you might want [contour2d](https://github.com/mikolalysenko/contour2d) instead.
7 |
8 | # Example
9 |
10 | Here is a 2D example:
11 |
12 | ```javascript
13 | //Load modules
14 | var surfaceNets = require("surface-nets")
15 | var ndarray = require("ndarray")
16 | var fill = require("ndarray-fill")
17 |
18 | //Initialize array to a circle
19 | var array = ndarray(new Float32Array(32*32), [32,32])
20 | fill(array, function(i,j) {
21 | return Math.pow(i-16,2) + Math.pow(j-16,2)
22 | })
23 |
24 | //Extract 2D contour (this is all there is to it!)
25 | var complex = surfaceNets(array, 15*15)
26 |
27 | //Write SVG image to stdout
28 | var svgFile = ['')
38 | console.log(svgFile.join(""))
39 | ```
40 |
41 | And here is the output SVG:
42 |
43 |
44 |
45 | This module also works in 3D. Here is an example:
46 |
47 | ```javascript
48 | //Load modules
49 | var surfaceNets = require("surface-nets")
50 | var ndarray = require("ndarray")
51 | var fill = require("ndarray-fill")
52 | var mat4 = require("gl-matrix").mat4
53 |
54 | //Initialize array
55 | var array = ndarray(new Float32Array(32*32*32), [32,32,32])
56 | fill(array, function(i,j,k) {
57 | return Math.pow(i-16,2) + Math.pow(j-16,2) + Math.pow(k-16,2)
58 | })
59 |
60 | //Generate surface! (again, just one line)
61 | var complex = surfaceNets(array, 100)
62 |
63 | //Render the implicit surface to stdout
64 | console.log('")
81 | ```
82 |
83 | And here is the result:
84 |
85 |
86 |
87 | And while it is a bit trivial, you can also generate surfaces in 1D:
88 |
89 | ```javascript
90 | var surfaceNets = require("surface-nets")
91 | var ndarray = require("ndarray")
92 |
93 | console.log(surfaceNets(ndarray([1, -1, 0, 5, -10])))
94 | ```
95 |
96 | Output:
97 |
98 | ```javascript
99 | { positions: [ [ 0.5 ], [ 2 ], [ 3.3333333333333335 ] ],
100 | cells: [ [ 0 ], [ 1 ], [ 2 ] ] }
101 | ```
102 |
103 | The code *should* work in 4D and higher dimensions, but this is not well tested and it is harder to visualize. (Also, why would you want to bother!?!)
104 |
105 | # Install
106 |
107 | ```
108 | npm install surface-nets
109 | ```
110 |
111 | # API
112 |
113 | #### `require("surface-nets")(array[,level])`
114 | Extracts the level set at `level` from `array` as a simplicial complex.
115 |
116 | * `array` is an [ndarray](https://github.com/mikolalysenko/ndarray)
117 | * `level` is an optional number which determines the level at which the levelset is evaluated (default `0`)
118 |
119 | **Returns** An object with a pair of properties representing a simplicial complex:
120 |
121 | * `positions` is an array encoding the positions of the vertices. The coordinates of the positions are with respect to the indices in `array`.
122 | * `cells` is an array encoding the cells of the simplicial complex as tuples of indices into the `position` array.
123 |
124 | # Credits
125 | (c) 2014 Mikola Lysenko. MIT License
126 |
--------------------------------------------------------------------------------
/bench/contour3d.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | //Load modules
4 | var surfaceNets = require("../surfacenets.js")
5 | var ndarray = require("ndarray")
6 | var fill = require("ndarray-fill")
7 |
8 | //Initialize array
9 | var array = ndarray(new Float32Array(32*32*32), [32,32,32])
10 | fill(array, function(i,j,k) {
11 | return Math.pow(i-16,2) + Math.pow(j-16,2) + Math.pow(k-16,2)
12 | })
13 |
14 | //Generate surface!
15 | var complex = surfaceNets(array, 100)
16 | for(var i=0; i<10; ++i) {
17 | complex = surfaceNets(array, 100)
18 | }
19 |
20 | console.time("surface-nets 3d")
21 | for(var i=0; i<1000; ++i) {
22 | complex = surfaceNets(array, 100)
23 | }
24 | console.timeEnd("surface-nets 3d")
--------------------------------------------------------------------------------
/bench/isosurface3d.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | var isosurface = require("isosurface")
4 |
5 | function sphere(i,j,k) {
6 | return Math.pow(i-16,2) + Math.pow(j-16,2) + Math.pow(k-16,2) - 100
7 | }
8 |
9 | var complex
10 |
11 | console.time("isosurface.surfaceNets")
12 | for(var i=0; i<1000; ++i) {
13 | complex = isosurface.surfaceNets([32,32,32], sphere)
14 | }
15 | console.timeEnd("isosurface.surfaceNets")
--------------------------------------------------------------------------------
/example/2d.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/example-1d.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | var surfaceNets = require("../surfacenets.js")
4 | var ndarray = require("ndarray")
5 |
6 | console.log(surfaceNets(ndarray([1, -1, 0, 5, -10])))
--------------------------------------------------------------------------------
/example/example-2d.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | var surfaceNets = require("../surfacenets.js")
4 | var ndarray = require("ndarray")
5 | var fill = require("ndarray-fill")
6 |
7 | var array = ndarray(new Float32Array(33*33), [33,33])
8 | fill(array, function(i,j) {
9 | return Math.pow(i-16,2) + Math.pow(j-16,2)
10 | })
11 |
12 | var complex = surfaceNets(array, 15*15)
13 |
14 | complex.positions.forEach(function(pt) {
15 | pt[0] += 1
16 | pt[1] += 1
17 | })
18 |
19 | var svgFile = ['')
29 |
30 | var fs = require("fs")
31 | fs.writeFileSync("example/2d.svg", svgFile.join(""))
--------------------------------------------------------------------------------
/example/example-3d-stl.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | //Load modules
4 | var surfaceNets = require("../surfacenets.js")
5 | var ndarray = require("ndarray")
6 | var fill = require("ndarray-fill")
7 | var mat4 = require("gl-matrix").mat4
8 | var stl = require("stl")
9 |
10 | //Initialize array
11 | var array = ndarray(new Float32Array(32*32*32), [32,32,32])
12 | fill(array, function(i,j,k) {
13 | return Math.pow(i-16,2) + Math.pow(j-16,2) + Math.pow(k-16,2)
14 | })
15 |
16 | //Generate surface!
17 | var complex = surfaceNets(array, 100)
18 |
19 | //Render the implicit surface to stdout
20 | var data = {
21 | description: "surface net 3d object",
22 | facets: complex.cells.map(function(c) {
23 | return {
24 | verts: c.map(function(idx) {
25 | return complex.positions[idx]
26 | })
27 | }
28 | })
29 | }
30 |
31 | console.log(data)
32 |
33 | require("fs").writeFileSync("example/3d.stl", stl.fromObject(data))
--------------------------------------------------------------------------------
/example/example-3d.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | //Load modules
4 | var surfaceNets = require("../surfacenets.js")
5 | var ndarray = require("ndarray")
6 | var fill = require("ndarray-fill")
7 | var mat4 = require("gl-matrix").mat4
8 |
9 | //Initialize array
10 | var array = ndarray(new Float32Array(32*32*32), [32,32,32])
11 | fill(array, function(i,j,k) {
12 | return Math.pow(i-16,2) + Math.pow(j-16,2) + Math.pow(k-16,2)
13 | })
14 |
15 | //Generate surface!
16 | var complex = surfaceNets(array, 100)
17 |
18 | //Render the implicit surface to stdout
19 | var svg = []
20 | svg.push('")
37 |
38 | require("fs").writeFileSync("example/3d.svg", svg.join(""))
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "surface-nets",
3 | "version": "1.0.2",
4 | "description": "General purpose level set extraction",
5 | "main": "surfacenets.js",
6 | "directories": {
7 | "example": "example",
8 | "test": "test"
9 | },
10 | "dependencies": {
11 | "ndarray-extract-contour": "^1.0.0",
12 | "triangulate-hypercube": "^1.0.0",
13 | "zero-crossings": "^1.0.0"
14 | },
15 | "devDependencies": {
16 | "gl-matrix": "~2.1.0",
17 | "ndarray": "~1.0.11",
18 | "ndarray-fill": "~0.1.0",
19 | "svg-3d-simplicial-complex": "~0.1.1",
20 | "tape": "~2.12.3",
21 | "isosurface": "^1.0.0",
22 | "robust-determinant": "^1.1.0",
23 | "gamma": "^0.1.0",
24 | "stl": "^1.1.1"
25 | },
26 | "scripts": {
27 | "test": "tape test/*.js"
28 | },
29 | "repository": {
30 | "type": "git",
31 | "url": "git://github.com/mikolalysenko/surface-nets.git"
32 | },
33 | "keywords": [
34 | "surface",
35 | "net",
36 | "marching",
37 | "cubes",
38 | "polygonize",
39 | "isosurface",
40 | "ndarray",
41 | "extract",
42 | "boundary",
43 | "volume",
44 | "marching",
45 | "squares",
46 | "surface nets",
47 | "dual contouring",
48 | "dual",
49 | "contour",
50 | "isocontour"
51 | ],
52 | "author": "Mikola Lysenko",
53 | "license": "MIT",
54 | "bugs": {
55 | "url": "https://github.com/mikolalysenko/surface-nets/issues"
56 | },
57 | "homepage": "https://github.com/mikolalysenko/surface-nets"
58 | }
59 |
--------------------------------------------------------------------------------
/surfacenets.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | module.exports = surfaceNets
4 |
5 | var generateContourExtractor = require("ndarray-extract-contour")
6 | var triangulateCube = require("triangulate-hypercube")
7 | var zeroCrossings = require("zero-crossings")
8 |
9 | function buildSurfaceNets(order, dtype) {
10 | var dimension = order.length
11 | var code = ["'use strict';"]
12 | var funcName = "surfaceNets" + order.join("_") + "d" + dtype
13 |
14 | //Contour extraction function
15 | code.push(
16 | "var contour=genContour({",
17 | "order:[", order.join(), "],",
18 | "scalarArguments: 3,",
19 | "phase:function phaseFunc(p,a,b,c) { return (p > c)|0 },")
20 | if(dtype === "generic") {
21 | code.push("getters:[0],")
22 | }
23 |
24 | //Generate vertex function
25 | var cubeArgs = []
26 | var extraArgs = []
27 | for(var i=0; i>>7){")
56 | }
57 | for(var i=0; i<1<<(1< 128) {
59 | if((i%128)===0) {
60 | if(extraFuncs.length > 0) {
61 | currentFunc.push("}}")
62 | }
63 | var efName = "vExtra" + extraFuncs.length
64 | code.push("case ", (i>>>7), ":", efName, "(m&0x7f,", extraArgs.join(), ");break;")
65 | currentFunc = [
66 | "function ", efName, "(m,", extraArgs.join(), "){switch(m){"
67 | ]
68 | extraFuncs.push(currentFunc)
69 | }
70 | }
71 | currentFunc.push("case ", (i&0x7f), ":")
72 | var crossings = new Array(dimension)
73 | var denoms = new Array(dimension)
74 | var crossingCount = new Array(dimension)
75 | var bias = new Array(dimension)
76 | var totalCrossings = 0
77 | for(var j=0; j j) {
87 | continue
88 | }
89 | if(!(i&(1< 0) {
127 | cStr = "+" + crossingCount[k] + "*c"
128 | }
129 | var weight = 0.5 * (crossings[k].length / totalCrossings)
130 | var shift = 0.5 + 0.5 * (bias[k] / totalCrossings)
131 | vertexStr.push("d" + k + "-" + shift + "-" + weight + "*(" + crossings[k].join("+") + cStr + ")/(" + denoms[k].join("+") + ")")
132 |
133 | }
134 | }
135 | currentFunc.push("a.push([", vertexStr.join(), "]);",
136 | "break;")
137 | }
138 | code.push("}},")
139 | if(extraFuncs.length > 0) {
140 | currentFunc.push("}}")
141 | }
142 |
143 | //Create face function
144 | var faceArgs = []
145 | for(var i=0; i<(1<<(dimension-1)); ++i) {
146 | faceArgs.push("v" + i)
147 | }
148 | faceArgs.push("c0", "c1", "p0", "p1", "a", "b", "c")
149 | code.push("cell:function cellFunc(", faceArgs.join(), "){")
150 |
151 | var facets = triangulateCube(dimension-1)
152 | code.push("if(p0){b.push(",
153 | facets.map(function(f) {
154 | return "[" + f.map(function(v) {
155 | return "v" + v
156 | }) + "]"
157 | }).join(), ")}else{b.push(",
158 | facets.map(function(f) {
159 | var e = f.slice()
160 | e.reverse()
161 | return "[" + e.map(function(v) {
162 | return "v" + v
163 | }) + "]"
164 | }).join(),
165 | ")}}});function ", funcName, "(array,level){var verts=[],cells=[];contour(array,verts,cells,level);return {positions:verts,cells:cells};} return ", funcName, ";")
166 |
167 | for(var i=0; i 1) {
46 | //Compute volume of surface
47 | var vol = 0.0
48 | for(var k=0; k= -1e-6, "check orientation: " + r)
60 | vol += r[r.length-1] / gamma(d+1)
61 | }
62 | var bvol = Math.pow(Math.PI, Math.floor(d/2)) * Math.pow(6.0, d)
63 | if((d % 2) === 0) {
64 | bvol /= gamma((d/2) + 1)
65 | } else {
66 | var kk = Math.floor(d/2)
67 | bvol *= Math.pow(2.0, d) * gamma(1+kk) / gamma(1+d)
68 | }
69 | t.ok(Math.abs(vol / bvol - 1.0) < 0.05, "check volume - vol=" + vol + " vs bvol=" + bvol)
70 | }
71 | }
72 | t.end()
73 | })
--------------------------------------------------------------------------------