Skip to main content

Flux

Page by Murray Bourne, IntMath.com. Last updated: 24 April 2018.

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.

top

Search IntMath, blog and Forum

Search IntMath

Online Algebra Solver

This algebra solver can solve a wide range of math problems.

Math Lessons on DVD

Math videos by MathTutorDVD.com

Easy to understand math lessons on DVD. See samples before you commit.

More info: Math videos

The IntMath Newsletter

Sign up for the free IntMath Newsletter. Get math study tips, information, news and updates each fortnight. Join thousands of satisfied students, teachers and parents!


See the Interactive Mathematics spam guarantee.