201 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			201 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /** | ||
|  |  * SurfaceNets in JavaScript | ||
|  |  * | ||
|  |  * Written by Mikola Lysenko (C) 2012 | ||
|  |  * | ||
|  |  * MIT License | ||
|  |  * | ||
|  |  * Based on: S.F. Gibson, 'Constrained Elastic Surface Nets'. (1998) MERL Tech Report. | ||
|  |  * from https://github.com/mikolalysenko/isosurface/tree/master
 | ||
|  |  *  | ||
|  |  */ | ||
|  | 
 | ||
|  | let surfaceNet = ( dims, potential, bounds ) => { | ||
|  | 		 | ||
|  | 	 | ||
|  | 	//Precompute edge table, like Paul Bourke does.
 | ||
|  | 	// This saves a bit of time when computing the centroid of each boundary cell
 | ||
|  | 	var cube_edges = new Int32Array(24) , edge_table = new Int32Array(256); | ||
|  | 	(function() { | ||
|  | 
 | ||
|  | 		//Initialize the cube_edges table
 | ||
|  | 		// This is just the vertex number of each cube
 | ||
|  | 		var k = 0; | ||
|  | 		for(var i=0; i<8; ++i) { | ||
|  | 			for(var j=1; j<=4; j<<=1) { | ||
|  | 				var p = i^j; | ||
|  | 				if(i <= p) { | ||
|  | 					cube_edges[k++] = i; | ||
|  | 					cube_edges[k++] = p; | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		//Initialize the intersection table.
 | ||
|  | 		//  This is a 2^(cube configuration) ->  2^(edge configuration) map
 | ||
|  | 		//  There is one entry for each possible cube configuration, and the output is a 12-bit vector enumerating all edges crossing the 0-level.
 | ||
|  | 		for(var i=0; i<256; ++i) { | ||
|  | 			var em = 0; | ||
|  | 			for(var j=0; j<24; j+=2) { | ||
|  | 				var a = !!(i & (1<<cube_edges[j])) | ||
|  | 					, b = !!(i & (1<<cube_edges[j+1])); | ||
|  | 				em |= a !== b ? (1 << (j >> 1)) : 0; | ||
|  | 			} | ||
|  | 			edge_table[i] = em; | ||
|  | 		} | ||
|  | 	})(); | ||
|  | 
 | ||
|  | 	//Internal buffer, this may get resized at run time
 | ||
|  | 	var buffer = new Array(4096); | ||
|  | 	(function() { | ||
|  | 		for(var i=0; i<buffer.length; ++i) { | ||
|  | 			buffer[i] = 0; | ||
|  | 		} | ||
|  | 	})(); | ||
|  | 
 | ||
|  | 	if(!bounds) { | ||
|  | 		bounds = [[0,0,0],dims]; | ||
|  | 	} | ||
|  | 	 | ||
|  | 	var scale     = [0,0,0]; | ||
|  | 	var shift     = [0,0,0]; | ||
|  | 	for(var i=0; i<3; ++i) { | ||
|  | 		scale[i] = (bounds[1][i] - bounds[0][i]) / dims[i]; | ||
|  | 		shift[i] = bounds[0][i]; | ||
|  | 	} | ||
|  | 	 | ||
|  | 	var vertices = [] | ||
|  | 		, faces = [] | ||
|  | 		, n = 0 | ||
|  | 		, x = [0, 0, 0] | ||
|  | 		, R = [1, (dims[0]+1), (dims[0]+1)*(dims[1]+1)] | ||
|  | 		, grid = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] | ||
|  | 		, buf_no = 1; | ||
|  | 	 | ||
|  | 		 | ||
|  | 	//Resize buffer if necessary 
 | ||
|  | 	if(R[2] * 2 > buffer.length) { | ||
|  | 		var ol = buffer.length; | ||
|  | 		buffer.length = R[2] * 2; | ||
|  | 		while(ol < buffer.length) { | ||
|  | 			buffer[ol++] = 0; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	 | ||
|  | 	//March over the voxel grid
 | ||
|  | 	for(x[2]=0; x[2]<dims[2]-1; ++x[2], n+=dims[0], buf_no ^= 1, R[2]=-R[2]) { | ||
|  | 	 | ||
|  | 		//m is the pointer into the buffer we are going to use.  
 | ||
|  | 		//This is slightly obtuse because javascript does not have good support for packed data structures, so we must use typed arrays :(
 | ||
|  | 		//The contents of the buffer will be the indices of the vertices on the previous x/y slice of the volume
 | ||
|  | 		var m = 1 + (dims[0]+1) * (1 + buf_no * (dims[1]+1)); | ||
|  | 		 | ||
|  | 		for(x[1]=0; x[1]<dims[1]-1; ++x[1], ++n, m+=2) | ||
|  | 		for(x[0]=0; x[0]<dims[0]-1; ++x[0], ++n, ++m) { | ||
|  | 		 | ||
|  | 			//Read in 8 field values around this vertex and store them in an array
 | ||
|  | 			//Also calculate 8-bit mask, like in marching cubes, so we can speed up sign checks later
 | ||
|  | 			var mask = 0, g = 0; | ||
|  | 			for(var k=0; k<2; ++k) | ||
|  | 			for(var j=0; j<2; ++j)       | ||
|  | 			for(var i=0; i<2; ++i, ++g) { | ||
|  | 				var p = potential( | ||
|  | 					scale[0]*(x[0]+i)+shift[0], | ||
|  | 					scale[1]*(x[1]+j)+shift[1], | ||
|  | 					scale[2]*(x[2]+k)+shift[2]); | ||
|  | 				grid[g] = p; | ||
|  | 				mask |= (p < 0) ? (1<<g) : 0; | ||
|  | 			} | ||
|  | 			 | ||
|  | 			//Check for early termination if cell does not intersect boundary
 | ||
|  | 			if(mask === 0 || mask === 0xff) { | ||
|  | 				continue; | ||
|  | 			} | ||
|  | 			 | ||
|  | 			//Sum up edge intersections
 | ||
|  | 			var edge_mask = edge_table[mask] | ||
|  | 				, v = [0.0,0.0,0.0] | ||
|  | 				, e_count = 0; | ||
|  | 				 | ||
|  | 			//For every edge of the cube...
 | ||
|  | 			for(var i=0; i<12; ++i) { | ||
|  | 			 | ||
|  | 				//Use edge mask to check if it is crossed
 | ||
|  | 				if(!(edge_mask & (1<<i))) { | ||
|  | 					continue; | ||
|  | 				} | ||
|  | 				 | ||
|  | 				//If it did, increment number of edge crossings
 | ||
|  | 				++e_count; | ||
|  | 				 | ||
|  | 				//Now find the point of intersection
 | ||
|  | 				var e0 = cube_edges[ i<<1 ]       //Unpack vertices
 | ||
|  | 					, e1 = cube_edges[(i<<1)+1] | ||
|  | 					, g0 = grid[e0]                 //Unpack grid values
 | ||
|  | 					, g1 = grid[e1] | ||
|  | 					, t  = g0 - g1;                 //Compute point of intersection
 | ||
|  | 				if(Math.abs(t) > 1e-6) { | ||
|  | 					t = g0 / t; | ||
|  | 				} else { | ||
|  | 					continue; | ||
|  | 				} | ||
|  | 				 | ||
|  | 				//Interpolate vertices and add up intersections (this can be done without multiplying)
 | ||
|  | 				for(var j=0, k=1; j<3; ++j, k<<=1) { | ||
|  | 					var a = e0 & k | ||
|  | 						, b = e1 & k; | ||
|  | 					if(a !== b) { | ||
|  | 						v[j] += a ? 1.0 - t : t; | ||
|  | 					} else { | ||
|  | 						v[j] += a ? 1.0 : 0; | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 			 | ||
|  | 			//Now we just average the edge intersections and add them to coordinate
 | ||
|  | 			var s = 1.0 / e_count; | ||
|  | 			for(var i=0; i<3; ++i) { | ||
|  | 				v[i] = scale[i] * (x[i] + s * v[i]) + shift[i]; | ||
|  | 			} | ||
|  | 			 | ||
|  | 			//Add vertex to buffer, store pointer to vertex index in buffer
 | ||
|  | 			buffer[m] = vertices.length; | ||
|  | 			vertices.push(v); | ||
|  | 			 | ||
|  | 			//Now we need to add faces together, to do this we just loop over 3 basis components
 | ||
|  | 			for(var i=0; i<3; ++i) { | ||
|  | 				//The first three entries of the edge_mask count the crossings along the edge
 | ||
|  | 				if(!(edge_mask & (1<<i)) ) { | ||
|  | 					continue; | ||
|  | 				} | ||
|  | 				 | ||
|  | 				// i = axes we are point along.  iu, iv = orthogonal axes
 | ||
|  | 				var iu = (i+1)%3 | ||
|  | 					, iv = (i+2)%3; | ||
|  | 					 | ||
|  | 				//If we are on a boundary, skip it
 | ||
|  | 				if(x[iu] === 0 || x[iv] === 0) { | ||
|  | 					continue; | ||
|  | 				} | ||
|  | 				 | ||
|  | 				//Otherwise, look up adjacent edges in buffer
 | ||
|  | 				var du = R[iu] | ||
|  | 					, dv = R[iv]; | ||
|  | 				 | ||
|  | 				//Remember to flip orientation depending on the sign of the corner.
 | ||
|  | 				if(mask & 1) { | ||
|  | 					faces.push([buffer[m],    buffer[m-du],    buffer[m-dv]]); | ||
|  | 					faces.push([buffer[m-dv], buffer[m-du],    buffer[m-du-dv]]); | ||
|  | 				} else { | ||
|  | 					faces.push([buffer[m],    buffer[m-dv],    buffer[m-du]]); | ||
|  | 					faces.push([buffer[m-du], buffer[m-dv],    buffer[m-du-dv]]); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 	 | ||
|  | 	//All done!  Return the result
 | ||
|  | 	return { positions: vertices, cells: faces }; | ||
|  | } | ||
|  | 
 | ||
|  | export { surfaceNet } |