function getContour(pixels, doSimplify) { var contour = surfaceNets(pixels, 128) if(doSimplify) { return simplify(contour.cells, contour.positions, 0.25) } return { edges: contour.cells, positions: contour.positions } }
fill(array, function(i,j) { if(i==0 || j == 0 || i > width - 2 || j > width - 2) return 0 else return noise.perlin2(i / 7, j / 9) + noise.perlin2(i / 15, j / 13); // return Math.pow(i-16,2) + Math.pow(j-16,2) }) var svgFile = ['<svg xmlns="http://www.w3.org/2000/svg" width="640" height="640">'] var polygons = []; for(var threshold=0.1;threshold<1.0;threshold += 0.1){ var mult = 8, offset = -20; var complex = surfaceNets(array, threshold); // complex.positions.forEach(function(pt) { // pt[0] += 1 // pt[1] += 1 // }) var faces = getFaces(complex.cells, complex.positions); //console.log(faces) faces.forEach(function(face){ var polygon = []; face.forEach(function(index){ var x = complex.positions[index][0];
function build (regl, res, f) { var data = ndarray(new Float64Array(res*res*res),[res,res,res]) return surfaceNets(fill(data, function (i,j,k) { return f(i/63*2-1,j/63*2-1,k/63*2-1) })) }
proto.update = function(params) { params = params || {} this.dirty = true if('contourWidth' in params) { this.contourWidth = handleArray(params.contourWidth, Number) } if('showContour' in params) { this.showContour = handleArray(params.showContour, Boolean) } if('showSurface' in params) { this.showSurface = !!params.showSurface } if('contourTint' in params) { this.contourTint = handleArray(params.contourTint, Boolean) } if('contourColor' in params) { this.contourColor = handleColor(params.contourColor) } if('contourProject' in params) { this.contourProject = handleArray(params.contourProject, function(x) { return handleArray(x, Boolean) }) } if('surfaceProject' in params) { this.surfaceProject = params.surfaceProject } if('dynamicColor' in params) { this.dynamicColor = handleColor(params.dynamicColor) } if('dynamicTint' in params) { this.dynamicTint = handleArray(params.dynamicTint, Number) } if('dynamicWidth' in params) { this.dynamicWidth = handleArray(params.dynamicWidth, Number) } if('opacity' in params) { this.opacity = params.opacity } var field = params.field || (params.coords && params.coords[2]) || null if(!field) { if(this._field[2].shape[0] || this._field[2].shape[2]) { field = this._field[2].lo(1,1).hi(this._field[2].shape[0]-2, this._field[2].shape[1]-2) } else { field = this._field[2].hi(0,0) } } //Update field if('field' in params || 'coords' in params) { var fsize = (field.shape[0]+2)*(field.shape[1]+2) //Resize if necessary if(fsize > this._field[2].data.length) { pool.freeFloat(this._field[2].data) this._field[2].data = pool.mallocFloat(bits.nextPow2(fsize)) } //Pad field this._field[2] = ndarray(this._field[2].data, [field.shape[0]+2, field.shape[1]+2]) padField(this._field[2], field) //Save shape of field this.shape = field.shape.slice() var shape = this.shape //Resize coordinate fields if necessary for(var i=0; i<2; ++i) { if(this._field[2].size > this._field[i].data.length) { pool.freeFloat(this._field[i].data) this._field[i].data = pool.mallocFloat(this._field[2].size) } this._field[i] = ndarray(this._field[i].data, [shape[0]+2, shape[1]+2]) } //Generate x/y coordinates if(params.coords) { var coords = params.coords if(!Array.isArray(coords) || coords.length !== 3) { throw new Error('gl-surface: invalid coordinates for x/y') } for(var i=0; i<2; ++i) { var coord = coords[i] for(var j=0; j<2; ++j) { if(coord.shape[j] !== shape[j]) { throw new Error('gl-surface: coords have incorrect shape') } } padField(this._field[i], coord) } } else if(params.ticks) { var ticks = params.ticks if(!Array.isArray(ticks) || ticks.length !== 2) { throw new Error('gl-surface: invalid ticks') } for(var i=0; i<2; ++i) { var tick = ticks[i] if(Array.isArray(tick) || tick.length) { tick = ndarray(tick) } if(tick.shape[0] !== shape[i]) { throw new Error('gl-surface: invalid tick length') } //Make a copy view of the tick array var tick2 = ndarray(tick.data, shape) tick2.stride[i] = tick.stride[0] tick2.stride[i^1] = 0 //Fill in field array padField(this._field[i], tick2) } } else { for(var i=0; i<2; ++i) { var offset = [0,0] offset[i] = 1 this._field[i] = ndarray(this._field[i].data, [shape[0]+2, shape[1]+2], offset, 0) } this._field[0].set(0,0,0) for(var j=0; j<shape[0]; ++j) { this._field[0].set(j+1,0,j) } this._field[0].set(shape[0]+1,0,shape[0]-1) this._field[1].set(0,0,0) for(var j=0; j<shape[1]; ++j) { this._field[1].set(0,j+1,j) } this._field[1].set(0,shape[1]+1, shape[1]-1) } //Save shape var fields = this._field //Compute surface normals var fieldSize = fields[2].size var dfields = ndarray(pool.mallocFloat(fields[2].size*3*2), [3, shape[0]+2, shape[1]+2, 2]) for(var i=0; i<3; ++i) { gradient(dfields.pick(i), fields[i], 'mirror') } var normals = ndarray(pool.mallocFloat(fields[2].size*3), [shape[0]+2, shape[1]+2, 3]) for(var i=0; i<shape[0]+2; ++i) { for(var j=0; j<shape[1]+2; ++j) { var dxdu = dfields.get(0, i, j, 0) var dxdv = dfields.get(0, i, j, 1) var dydu = dfields.get(1, i, j, 0) var dydv = dfields.get(1, i, j, 1) var dzdu = dfields.get(2, i, j, 0) var dzdv = dfields.get(2, i, j, 1) var nx = dydu * dzdv - dydv * dzdu var ny = dzdu * dxdv - dzdv * dxdu var nz = dxdu * dydv - dxdv * dydu var nl = Math.sqrt(nx*nx + ny * ny + nz * nz) if(nl < 1e-8) { nl = Math.max(Math.abs(nx), Math.abs(ny), Math.abs(nz)) if(nl < 1e-8) { nz = 1.0 ny = nx = 0.0 nl = 1.0 } else { nl = 1.0/ nl } } else { nl = 1.0 / Math.sqrt(nl) } normals.set(i,j,0, nx*nl) normals.set(i,j,1, ny*nl) normals.set(i,j,2, nz*nl) } } pool.free(dfields.data) //Initialize surface var lo = [ Infinity, Infinity, Infinity] var hi = [-Infinity,-Infinity,-Infinity] var count = (shape[0]-1) * (shape[1]-1) * 6 var tverts = pool.mallocFloat(bits.nextPow2(9*count)) var tptr = 0 var fptr = 0 var vertexCount = 0 for(var i=0; i<shape[0]-1; ++i) { j_loop: for(var j=0; j<shape[1]-1; ++j) { //Test for NaNs for(var dx=0; dx<2; ++dx) { for(var dy=0; dy<2; ++dy) { for(var k=0; k<3; ++k) { var f = this._field[k].get(1+i+dx, 1+j+dy) if(isNaN(f) || !isFinite(f)) { continue j_loop } } } } for(var k=0; k<6; ++k) { var r = i + QUAD[k][0] var c = j + QUAD[k][1] var tx = this._field[0].get(r+1, c+1) var ty = this._field[1].get(r+1, c+1) var f = this._field[2].get(r+1, c+1) var nx = normals.get(r+1, c+1, 0) var ny = normals.get(r+1, c+1, 1) var nz = normals.get(r+1, c+1, 2) tverts[tptr++] = r tverts[tptr++] = c tverts[tptr++] = tx tverts[tptr++] = ty tverts[tptr++] = f tverts[tptr++] = 0 tverts[tptr++] = nx tverts[tptr++] = ny tverts[tptr++] = nz lo[0] = Math.min(lo[0], tx) lo[1] = Math.min(lo[1], ty) lo[2] = Math.min(lo[2], f) hi[0] = Math.max(hi[0], tx) hi[1] = Math.max(hi[1], ty) hi[2] = Math.max(hi[2], f) vertexCount += 1 } } } this._vertexCount = vertexCount this._coordinateBuffer.update(tverts.subarray(0,tptr)) pool.freeFloat(tverts) pool.free(normals.data) //Update bounds this.bounds = [lo, hi] } //Update level crossings var levelsChanged = false if('levels' in params) { var levels = params.levels if(!Array.isArray(levels[0])) { levels = [ [], [], levels ] } else { levels = levels.slice() } for(var i=0; i<3; ++i) { levels[i] = levels[i].slice() levels.sort(function(a,b) { return a-b }) } change_test: for(var i=0; i<3; ++i) { if(levels[i].length !== this.contourLevels[i].length) { levelsChanged = true break } for(var j=0; j<levels[i].length; ++j) { if(levels[i][j] !== this.contourLevels[i][j]) { levelsChanged = true break change_test } } } this.contourLevels = levels } if(levelsChanged) { var fields = this._field var shape = this.shape //Update contour lines var contourVerts = [] for(var dim=0; dim<3; ++dim) { var levels = this.contourLevels[dim] var levelOffsets = [] var levelCounts = [] var parts = [0,0] var graphParts = [0,0] for(var i=0; i<levels.length; ++i) { var graph = surfaceNets(this._field[dim], levels[i]) levelOffsets.push((contourVerts.length/4)|0) var vertexCount = 0 edge_loop: for(var j=0; j<graph.cells.length; ++j) { var e = graph.cells[j] for(var k=0; k<2; ++k) { var p = graph.positions[e[k]] var x = p[0] var ix = Math.floor(x)|0 var fx = x - ix var y = p[1] var iy = Math.floor(y)|0 var fy = y - iy var hole = false dd_loop: for(var dd=0; dd<2; ++dd) { parts[dd] = 0.0 var iu = (dim + dd + 1) % 3 for(var dx=0; dx<2; ++dx) { var s = dx ? fx : 1.0 - fx var r = Math.min(Math.max(ix+dx, 0), shape[0])|0 for(var dy=0; dy<2; ++dy) { var t = dy ? fy : 1.0 - fy var c = Math.min(Math.max(iy+dy, 0), shape[1])|0 var f = this._field[iu].get(r,c) if(!isFinite(f) || isNaN(f)) { hole = true break dd_loop } var w = s * t parts[dd] += w * f } } } if(!hole) { contourVerts.push(parts[0], parts[1], p[0], p[1]) vertexCount += 1 } else { if(k > 0) { //If we already added first edge, pop off verts for(var l=0; l<4; ++l) { contourVerts.pop() } vertexCount -= 1 } continue edge_loop } } } levelCounts.push(vertexCount) } //Store results this._contourOffsets[dim] = levelOffsets this._contourCounts[dim] = levelCounts } var floatBuffer = pool.mallocFloat(contourVerts.length) for(var i=0; i<contourVerts.length; ++i) { floatBuffer[i] = contourVerts[i] } this._contourBuffer.update(floatBuffer) pool.freeFloat(floatBuffer) } if(params.colormap) { this._colorMap.setPixels(genColormap(params.colormap)) } }
proto.highlight = function(selection) { if(!selection) { this._dynamicCounts = [0,0,0] this.dyanamicLevel = [NaN, NaN, NaN] this.highlightLevel = [-1,-1,-1] return } for(var i=0; i<3; ++i) { if(this.enableHighlight[i]) { this.highlightLevel[i] = selection.level[i] } else { this.highlightLevel[i] = -1 } } var levels if(this.snapToData) { levels = selection.dataCoordinate } else { levels = selection.position } if( (!this.enableDynamic[0] || levels[0] === this.dynamicLevel[0]) && (!this.enableDynamic[1] || levels[1] === this.dynamicLevel[1]) && (!this.enableDynamic[2] || levels[2] === this.dynamicLevel[2]) ) { return } var vertexCount = 0 var shape = this.shape var scratchBuffer = pool.mallocFloat(12 * shape[0] * shape[1]) for(var d=0; d<3; ++d) { if(!this.enableDynamic[d]) { this.dynamicLevel[d] = NaN this._dynamicCounts[d] = 0 continue } this.dynamicLevel[d] = levels[d] var u = (d+1) % 3 var v = (d+2) % 3 var f = this._field[d] var g = this._field[u] var h = this._field[v] var graph = surfaceNets(f, levels[d]) var edges = graph.cells var positions = graph.positions this._dynamicOffsets[d] = vertexCount for(var i=0; i<edges.length; ++i) { var e = edges[i] for(var j=0; j<2; ++j) { var p = positions[e[j]] var x = +p[0] var ix = x|0 var jx = Math.min(ix+1, shape[0])|0 var fx = x - ix var hx = 1.0 - fx var y = +p[1] var iy = y|0 var jy = Math.min(iy+1, shape[1])|0 var fy = y - iy var hy = 1.0 - fy var w00 = hx * hy var w01 = hx * fy var w10 = fx * hy var w11 = fx * fy var cu = w00 * g.get(ix,iy) + w01 * g.get(ix,jy) + w10 * g.get(jx,iy) + w11 * g.get(jx,jy) var cv = w00 * h.get(ix,iy) + w01 * h.get(ix,jy) + w10 * h.get(jx,iy) + w11 * h.get(jx,jy) if(isNaN(cu) || isNaN(cv)) { if(j) { vertexCount -= 1 } break } scratchBuffer[2*vertexCount+0] = cu scratchBuffer[2*vertexCount+1] = cv vertexCount += 1 } } this._dynamicCounts[d] = vertexCount - this._dynamicOffsets[d] } this._dynamicBuffer.update(scratchBuffer.subarray(0, 2*vertexCount)) pool.freeFloat(scratchBuffer) }
function getContour(pixels) { var contour = surfaceNets(pixels, 128) return simplify(contour.cells, contour.positions, 0.25) }