John Trengrove

home posts about

three.js hexagons

08 Aug 2014

3d is in early stages on the web. WebGL (a way of doing OpenGL / 3d graphics for the web), has been around for some time now but browsers and developers are still catching up.

three.js is an interesting javascript 3d library. It is a high level library that wraps around WebGL in a fairly easy to use manner. I've used it here to create a few rotating hexagons.

A couple things to notice while using three.js, or other javascript 3d graphics libraries, it that performance here really matters. I am not used to small changes having such disparate affects on how my code (and laptop fan) runs. The stats module for three.js is quite useful here for giving framerates.

3d also literally adds a "new dimension". Simple things become more complicated. You begin to work with camera's rotating around defined structures rather than simple 2d transformations. From a data visualisation point of view, it is more immersive, but requires more thought on how to present the data.

var colours = [0xCD853F, 0x191970, 0xDAA520];
var container;
var camera, scene, projector, renderer;
var particleMaterial;
var width = 740;
var height = 350;
var objects = [];

function init() {
  container = $("#webgl");
  camera = new THREE.PerspectiveCamera( 70, width / height, 1, 10000 );
  camera.position.set( 0, 300, 500 );
  scene = new THREE.Scene();

  var geometry = new THREE.CylinderGeometry(80,80,20,7);

  for ( var i = 0; i < 8; i ++ ) {

    var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: colours[Math.floor(Math.random() * colours.length)], opacity: 0.4 } ) );
    object.position.x = Math.random() * 400 - 200;
    object.position.y = Math.random() * 400 - 200;
    object.position.z = Math.random() * 400 - 200;

    var r = Math.random();
    object.scale.x = Math.random() * 0.5 + 0.8;
    object.scale.y = Math.random() * 0.5 + 0.8;
    object.scale.z = Math.random() * 0.5 + 0.8;

    object.rotation.x = Math.random() * 2 * Math.PI;
    object.rotation.y = Math.random() * 2 * Math.PI;
    object.rotation.z = Math.random() * 2 * Math.PI;

    scene.add( object );

    objects.push( object );

  }

  var PI2 = Math.PI * 2;
  particleMaterial = new THREE.SpriteCanvasMaterial( {

    color: 0x000000,
    program: function ( context ) {

      context.beginPath();
      context.arc( 0, 0, 0.5, 0, PI2, true );
      context.fill();

    }

  } );

  projector = new THREE.Projector();

  renderer = new THREE.CanvasRenderer();
  renderer.setClearColor( 0xfefefe );
  renderer.setSize( width, height );

  container.append(renderer.domElement);

  document.addEventListener( 'mousedown', onDocumentMouseDown, false );
  window.addEventListener( 'resize', onWindowResize, false );

}

function onWindowResize() {
  width = $(".site").width();
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  renderer.setSize( width, height );
}

function onDocumentMouseDown(event) {

  event.preventDefault();
  var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
  projector.unprojectVector( vector, camera );
  var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
  var intersects = raycaster.intersectObjects( objects );

  if ( intersects.length > 0 ) {
    intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
  }

}

function animate() {
  requestAnimationFrame( animate );
  render();
}

var radius = 600;
var theta = 0;

function render() {
  theta += 0.2;
  camera.position.x = radius * Math.sin( THREE.Math.degToRad( theta ) );
  camera.position.y = radius * Math.sin( THREE.Math.degToRad( theta ) );
  camera.position.z = radius * Math.cos( THREE.Math.degToRad( theta ) );
  camera.lookAt( scene.position );
  renderer.render( scene, camera );
}

$(function() {
  width = $(".site").width();
  init();
  animate();
});