Source: VolumeData.js

/**
 * @class
 * An object representing a vector field.
 *
 * @param {int} height The desired resolution in the "height"-dimension.
 * @param {int} width The desired resolution in the "width"-dimension.
 * @param {int} depth The desired resolution in the "depth"-dimension.
 */
function Volume(height, width, depth) {
	this.height = height;
	this.width = width;
	this.depth = depth;
	/** An array of type THREE.Vector3 representing the vector field. */
	this.data = new Array(height * width * depth);
	
	/**
	 * Refreshes the internal data as a simulation of vector field, which might have been 
	 * caused by a tornado.
	 *
	 * Taken from: {@link http://web.cse.ohio-state.edu/~crawfis/Data/Tornado/tornadoSrc.c}
	 * @param {int} time The timepoint of the tornado.
	 */
	this.generateTornado = function(time) {
		var ix, iy, iz;
	
		var x, y, z;
		var r, xc, yc, scale, temp, z0;
		var r2 = 8;
		var SMALL = 0.00000000001;
		var xdelta = 1.0 / (this.width-1.0);
		var ydelta = 1.0 / (this.height-1.0);
		var zdelta = 1.0 / (this.depth-1.0);

		for( iz = 0; iz < this.depth; iz++ )
		{
			z = iz * zdelta;                        		 
			xc = 0.5 + 0.1 * Math.sin(0.04*time+10.0*z);   	 
			yc = 0.5 + 0.1 * Math.cos(0.03*time+3.0*z);    	 
			r = 0.1 + 0.4 * z*z + 0.1 * z * Math.sin(8.0*z); 
			r2 = 0.2 + 0.1*z;                           	 
		
			for( iy = 0; iy < this.height; iy++ )
			{
				y = iy * ydelta;
				for( ix = 0; ix < this.width; ix++ )
				{
					x = ix * xdelta;
					temp = Math.sqrt( (y-yc)*(y-yc) + (x-xc)*(x-xc) );
					scale = Math.abs( r - temp );

					if ( scale > r2 )
						scale = 0.8 - scale;
					else
						scale = 1.0;
					z0 = 0.1 * (0.1 - temp*z );
					if ( z0 < 0.0 )  z0 = 0.0;
				
					temp = Math.sqrt( temp*temp + z0*z0 );
					scale = (r + r2 - temp) * scale / (temp + SMALL);
					scale = scale / (1+z);
				
					var vec = new THREE.Vector3(
						scale * (y-yc) + 0.1*(x-xc), 
						scale * -(x-xc) + 0.1*(y-yc), 
						scale * z0
					);
					vec = vec.divideScalar(vec.length());
					this.data[ix*(this.height*this.depth) + iy*this.depth + iz] = vec;
				}
			}
		}
	}
	
	this.generateTest1 = function() {
		for(var iz = 0; iz < this.depth; iz++ )
		{
			for(var iy = 0; iy < this.height; iy++ )
			{
				for(var ix = 0; ix < this.width; ix++ )
				{
					var vec = new THREE.Vector3(-iy/this.height - 0.5, ix/this.width - 0.5, 0.0);
					vec.add(new THREE.Vector3(-ix/this.width + 0.5, iy/this.height - 0.5, 0.0).multiplyScalar(5));
					vec.divideScalar(3);
					
					this.data[ix*(this.height*this.depth) + iy*this.depth + iz] = vec;
				}
			}
		}
	}
	
	/**
	 * Samples the volume by nearest neighbor.
	 * @param {THREE.Vector3} pos The position, where data should be sampled.
	 */
	this.nearest = function(pos) {
		var x = Math.floor(pos.x * this.width);
		var y = Math.floor(pos.y * this.height);
		var z = Math.floor(pos.z * this.depth);
	
		var res = this.data[x*(this.height*this.depth) + y*this.depth + z];
		return res.clone();
	}
	
	/**
	 * Samples the volume trilinear. (Not really ...)
	 * @param {THREE.Vector3} pos The position, where data should be sampled.
	 */
	this.trilinear = function(pos) {
		var x0 = Math.floor(pos.x * this.width);
		var x1 = x0+1;
		var y0 = Math.floor(pos.y * this.height);
		var y1 = y0+1;
		var z0 = Math.floor(pos.z * this.depth);
		var z1 = z0+1;
		
		if (z1 == 0 || z1 == this.depth-1 ||
			y1 == 0 || y1 == this.height-1 ||
			x1 == 0 || x1 == this.width-1 ||
			z0 == 0 || z0 == this.depth-1 ||
			y0 == 0 || y0 == this.height-1 ||
			x0 == 0 || x0 == this.width-1) {
			
			// We are at the edge, so we are lazy and use nearest neighbor here.
			return this.nearest(pos);

		} else {
			// TODO implement real trilinear
			var sum = new THREE.Vector3();
					
			sum.add(this.data[(x0+1)*(this.height*this.depth) +  y0   *this.depth +  z0   ]);
			sum.add(this.data[(x0-1)*(this.height*this.depth) +  y0   *this.depth +  z0   ]);
			sum.add(this.data[ x0   *(this.height*this.depth) + (y0+1)*this.depth +  z0   ]);
			sum.add(this.data[ x0   *(this.height*this.depth) + (y0-1)*this.depth +  z0   ]);
			sum.add(this.data[ x0   *(this.height*this.depth) +  y0   *this.depth + (z0+1)]);
			sum.add(this.data[ x0   *(this.height*this.depth) +  y0   *this.depth + (z0-1)]);
	
			sum.divideScalar(6);
			return sum;
		}
	}
}