├── .gitignore
├── README.md
├── demo
└── demo.js
├── index.html
├── package.json
├── smooth.gif
├── smooth.js
└── stdcyborg.png
/.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
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # taubin-smooth
2 | A pure JavaScript implementation of Taubin's mesh smoothing algorithm.
3 |
4 |
5 |
6 | ## [DEMO](https://mikolalysenko.github.io/taubin-smooth/index.html)
7 |
8 | # Install
9 |
10 | ```
11 | npm i taubin-smooth
12 | ```
13 |
14 | # API
15 |
16 | #### `require('taubin-smooth')(cells, positions[, options])`
17 | Takes a mesh as input, returns the smoothed vertices as output
18 |
19 | * `cells` are the cells of the mesh
20 | * `positions` are the coordinates of the vertices
21 | * `options` is an optional object containing any of the following configuration parameters:
22 | + `iters` the number of smoothing iters to run (default `10`)
23 | + `passBand` a scalar between `0` and `1` determining the cutoff frequency. (default `0.1`)
24 |
25 | # Credits
26 |
27 | Development supported by [Standard Cyborg](http://www.standardcyborg.com/)
28 |
29 |
30 |
31 | (c) 2017 Mikola Lysenko. MIT License
32 |
--------------------------------------------------------------------------------
/demo/demo.js:
--------------------------------------------------------------------------------
1 | const regl = require('regl')()
2 | const camera = require('regl-camera')(regl, {
3 | center: [0, 2.5, 0]
4 | })
5 | const smooth = require('../smooth')
6 | const calcNormals = require('angle-normals')
7 | const bunny = require('bunny')
8 |
9 | const params = {
10 | passBand: 0.1,
11 | iters: 0
12 | }
13 |
14 | const verts = regl.buffer(bunny.positions)
15 | const normals = regl.buffer(calcNormals(bunny.cells, bunny.positions))
16 |
17 | require('control-panel')([
18 | { type: 'range', label: 'passBand', min: 0, max: 1, step: 0.01, initial: params.passBand },
19 | { type: 'range', label: 'iters', min: 0, max: 50, initial: params.iters, step: 1 }
20 | ]).on('input', (data) => {
21 | Object.assign(params, data)
22 | const smoothPosition = smooth(bunny.cells, bunny.positions, params)
23 | verts(smoothPosition)
24 | normals(calcNormals(bunny.cells, smoothPosition))
25 | })
26 |
27 | const drawBunny = regl({
28 | attributes: {
29 | position: verts,
30 | normal: normals
31 | },
32 |
33 | elements: bunny.cells,
34 |
35 | vert: `
36 | precision highp float;
37 | attribute vec3 position, normal;
38 | uniform mat4 projection, view;
39 | varying vec3 vNormal;
40 | void main () {
41 | vNormal = normal;
42 | gl_Position = projection * view * vec4(position, 1);
43 | }
44 | `,
45 |
46 | frag: `
47 | precision highp float;
48 | varying vec3 vNormal;
49 | void main () {
50 | gl_FragColor = vec4(vNormal, 1);
51 | }
52 | `
53 | })
54 |
55 | regl.frame(() => {
56 | regl.clear({
57 | color: [0, 0, 0, 1],
58 | depth: 1
59 | })
60 |
61 | camera(() => {
62 | drawBunny()
63 | })
64 | })
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "taubin-smooth",
3 | "version": "1.0.1",
4 | "description": "Taubin's Laplacian mesh smoothing algorithm",
5 | "main": "smooth.js",
6 | "dependencies": {
7 | "typedarray-pool": "^1.1.0"
8 | },
9 | "devDependencies": {
10 | "angle-normals": "^1.0.0",
11 | "bunny": "^1.0.1",
12 | "control-panel": "^1.2.0",
13 | "regl": "^1.3.0",
14 | "regl-camera": "^1.1.2"
15 | },
16 | "scripts": {
17 | "test": "echo \"Error: no test specified\" && exit 1"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/mikolalysenko/taubin-smooth.git"
22 | },
23 | "keywords": [
24 | "taubin",
25 | "mesh",
26 | "smooth",
27 | "simplicial",
28 | "complex"
29 | ],
30 | "author": "Mikola Lysenko",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/mikolalysenko/taubin-smooth/issues"
34 | },
35 | "homepage": "https://github.com/mikolalysenko/taubin-smooth#readme"
36 | }
37 |
--------------------------------------------------------------------------------
/smooth.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikolalysenko/taubin-smooth/4508eae58c7f2ac76edddd2117f1879d22ffe141/smooth.gif
--------------------------------------------------------------------------------
/smooth.js:
--------------------------------------------------------------------------------
1 | var pool = require('typedarray-pool')
2 |
3 | function hypot (x, y, z) {
4 | return Math.sqrt(
5 | Math.pow(x, 2) +
6 | Math.pow(y, 2) +
7 | Math.pow(z, 2))
8 | }
9 |
10 | function accumulate (out, inp, w) {
11 | for (var i = 0; i < 3; ++i) {
12 | out[i] += inp[i] * w
13 | }
14 | }
15 |
16 | function dup (array) {
17 | var result = new Array(array.length)
18 | for (var i = 0; i < array.length; ++i) {
19 | result[i] = array[i].slice()
20 | }
21 | return result
22 | }
23 |
24 | function smoothStep (cells, positions, outAccum, trace, weight) {
25 | var i
26 | var numVerts = positions.length
27 | var numCells = cells.length
28 |
29 | for (i = 0; i < numVerts; ++i) {
30 | var ov = outAccum[i]
31 | ov[0] = ov[1] = ov[2] = 0
32 | }
33 |
34 | for (i = 0; i < numVerts; ++i) {
35 | trace[i] = 0
36 | }
37 |
38 | for (i = 0; i < numCells; ++i) {
39 | var cell = cells[i]
40 | var ia = cell[0]
41 | var ib = cell[1]
42 | var ic = cell[2]
43 |
44 | var a = positions[ia]
45 | var b = positions[ib]
46 | var c = positions[ic]
47 |
48 | var abx = a[0] - b[0]
49 | var aby = a[1] - b[1]
50 | var abz = a[2] - b[2]
51 |
52 | var bcx = b[0] - c[0]
53 | var bcy = b[1] - c[1]
54 | var bcz = b[2] - c[2]
55 |
56 | var cax = c[0] - a[0]
57 | var cay = c[1] - a[1]
58 | var caz = c[2] - a[2]
59 |
60 | var area = 0.5 * hypot(
61 | aby * caz - abz * cay,
62 | abz * cax - abx * caz,
63 | abx * cay - aby * cax)
64 |
65 | if (area < 1e-8) {
66 | continue
67 | }
68 |
69 | var w = -0.5 / area
70 | var wa = w * (abx * cax + aby * cay + abz * caz)
71 | var wb = w * (bcx * abx + bcy * aby + bcz * abz)
72 | var wc = w * (cax * bcx + cay * bcy + caz * bcz)
73 |
74 | trace[ia] += wb + wc
75 | trace[ib] += wc + wa
76 | trace[ic] += wa + wb
77 |
78 | var oa = outAccum[ia]
79 | var ob = outAccum[ib]
80 | var oc = outAccum[ic]
81 |
82 | accumulate(ob, c, wa)
83 | accumulate(oc, b, wa)
84 | accumulate(oc, a, wb)
85 | accumulate(oa, c, wb)
86 | accumulate(oa, b, wc)
87 | accumulate(ob, a, wc)
88 | }
89 |
90 | for (i = 0; i < numVerts; ++i) {
91 | var o = outAccum[i]
92 | var p = positions[i]
93 | var tr = trace[i]
94 | for (var j = 0; j < 3; ++j) {
95 | var x = p[j]
96 | o[j] = x + weight * (o[j] / tr - x)
97 | }
98 | }
99 | }
100 |
101 | module.exports = function taubinSmooth (cells, positions, _options) {
102 | var options = _options || {}
103 | var passBand = 'passBand' in options ? +options.passBand : 0.1
104 | var iters = ('iters' in options ? (options.iters | 0) : 10)
105 |
106 | var trace = pool.mallocDouble(positions.length)
107 |
108 | var pointA = dup(positions)
109 | var pointB = dup(positions)
110 |
111 | var A = -1
112 | var B = passBand
113 | var C = 2
114 |
115 | var discr = Math.sqrt(B * B - 4 * A * C)
116 | var r0 = (-B + discr) / (2 * A * C)
117 | var r1 = (-B - discr) / (2 * A * C)
118 |
119 | var lambda = Math.max(r0, r1)
120 | var mu = Math.min(r0, r1)
121 |
122 | for (var i = 0; i < iters; ++i) {
123 | smoothStep(cells, pointA, pointB, trace, lambda)
124 | smoothStep(cells, pointB, pointA, trace, mu)
125 | }
126 |
127 | pool.free(trace)
128 |
129 | return pointA
130 | }
131 |
--------------------------------------------------------------------------------
/stdcyborg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikolalysenko/taubin-smooth/4508eae58c7f2ac76edddd2117f1879d22ffe141/stdcyborg.png
--------------------------------------------------------------------------------