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