Skip to main content
Search IntMath
Close

Flux

Page by Murray Bourne, IntMath.com. Last updated: 07 September 2019.

This is what's going on in the math-based smoke-like artwork below.

Perlin Noise

In an attempt to make computer graphics more realistic, Ken Perlin in the 1980's developed a new kind of gradient noise which earned him an Academy Award for Technical Achievement. The algorithm produced "natural appearing textures on computer generated surfaces for motion picture visual effects", according to the Award statement.

The steps in producing Perlin noise involve several concepts from mathematics. They are:

  1. Define a grid (which involves gradient vectors and random number generators
  2. Find the dot product of the gradient vector and the distance vector between a point and a node
  3. Interpolate using a function which has zero first derivative, giving smooth joins between the nodes

Read more about Perlin Noise with pseudocode.

Instructions

Drag your mouse or finger over the animation to change the speed and color.

The code

function doFlux() {
	var WebGLCanvas = document.getElementById("WebGLCanvas");
	var viewPortWidth = window.innerWidth;
	var viewPortHeight = window.innerHeight;
	var	canvasWrapWidth = canvasWrap.clientWidth;
	var canvasWidth = canvasWrapWidth;
	var canvasHeight = Math.min(viewPortHeight-40, canvasWrapWidth);	
console.log(canvasWrapWidth, canvasWidth, canvasHeight)	
	//WebGLCanvas.style.width = canvasWidth+"px";
	//WebGLCanvas.style.height = canvasHeight+"px";	
	WebGLCanvas.innerHTML = "";
console.log(renderer)
	if(!renderer) {
console.log("hyar")		
		var renderer = new THREE.WebGLRenderer({ antialias: true, alpha:true });
		renderer.setClearColor( 0x000000, 0 );
		renderer.setSize(canvasWidth, canvasHeight);
		renderer.shadowMap.type = THREE.PCFSoftShadowMap;
		WebGLCanvas.appendChild( renderer.domElement );
	}
	renderer.setPixelRatio( window.devicePixelRatio > 1 ? 2 : 1 );
	var scene = new THREE.Scene();
	var camera = new THREE.PerspectiveCamera(
	  45,
	  canvasWidth / canvasHeight,
	  1,
	  1000
	);
	camera.position.z = 60;
	var length = 30;
	var mouseJump = {
	  x: 0,
	  y: 0
	};
	var offset = 0;
	function Spline() {
	  this.geometry = new THREE.Geometry();
	  this.color = Math.floor(Math.random() * 80 + 180);
	  for (var j = 0; j < 180; j++) {
		this.geometry.vertices.push(
		  new THREE.Vector3(j / 180 * length * 2 - length, 0, 0)
		);
		this.geometry.colors[j] = new THREE.Color(
		  "hsl(" + (j * 0.6 + this.color) + ",70%,70%)"
		);
	  }
	  this.material = new THREE.LineBasicMaterial({
		vertexColors: THREE.VertexColors
	  });
	  this.mesh = new THREE.Line(this.geometry, this.material);
	  this.speed = (Math.random() + 0.1) * 0.0002;
	  scene.add(this.mesh);
	}
	var isMouseDown = false;
	var prevA = 0;
	function render(a) {
	  requestAnimationFrame(render);
	  for (var i = 0; i < splines.length; i++) {
		for (var j = 0; j < splines[i].geometry.vertices.length; j++) {
		  var vector = splines[i].geometry.vertices[j];
		  vector.y =
			noise.simplex2(j * 0.05 + i - offset, a * splines[i].speed) * 8;
		  vector.z = noise.simplex2(vector.x * 0.05 + i, a * splines[i].speed) * 8;
		  vector.y *= 1 - Math.abs(vector.x / length);
		  vector.z *= 1 - Math.abs(vector.x / length);
		}
		splines[i].geometry.verticesNeedUpdate = true;
	  }
	  scene.rotation.x = a * 0.0003;
	  if (isMouseDown) {
		mouseJump.x += 0.001;
		if (a - prevA > 100) {
		  updateColor();
		  prevA = a;
		}
	  } else {
		mouseJump.x -= 0.001;
	  }
	  mouseJump.x = Math.max(0, Math.min(0.07, mouseJump.x));
	  offset += mouseJump.x;
	  renderer.render(scene, camera);
	}
	var splines = [];
	for (var i = 0; i < 12; i++) splines.push(new Spline());
	function onResize() {
	  camera.updateProjectionMatrix();
	}
	function updateColor() {
	  for (var i = 0; i < splines.length; i++) {
		var color = Math.abs((splines[i].color - offset * 10) % 360);
		for (var j = 0; j < splines[i].geometry.vertices.length; j++) {
		  splines[i].mesh.geometry.colors[j] = new THREE.Color(
			"hsl(" + (j * 0.6 + color) + ",70%,70%)"
		  );
		}
		splines[i].mesh.geometry.colorsNeedUpdate = true;
	  }
	}
	function onMouseDown(e) {
	  isMouseDown = true;
	  return false;
	}
	function onMouseUp() {
	  isMouseDown = false;
	}
	window.addEventListener( "resize", onResize);
	window.addEventListener( "keydown", onMouseDown);
	document.body.addEventListener( "mousedown", onMouseDown);
	document.body.addEventListener( "mouseup", onMouseUp);
	document.body.addEventListener( "touchstart", onMouseDown);
	document.body.addEventListener( "touchend", onMouseUp);
	requestAnimationFrame(render);
    window.addEventListener("resize", function() {
console.log("ress");		
		if(Math.abs(canvasWrapWidth - canvasWrap.clientWidth) > 20 || Math.abs(viewPortHeight - window.innerHeight) > 20) {
			doFlux();
		}
    });		
} 

Credits

The script is by Louis Hoebregts, source: CodePen.

The Perlin script is by Stefan Gustavson

From the Perlin script:

* Based on example code by Stefan Gustavson ([email protected])
* Optimisations by Peter Eastman ([email protected])
* Better rank ordering method by Stefan Gustavson in 2012
* Converted to Javascript by Joseph Gentle.

Tips, tricks, lessons, and tutoring to help reduce test anxiety and move to the top of the class.