Displaying a simple cube inside a canvas with three.js

This is a very basic WebGL example. I will display a simple rotating cube using Three.js library with revision 69. If the code has evolved with the new versions I suggest you have a look at the changelog: https://github.com/mrdoob/three.js/releases.

Html

For the Html part we only need a canvas to store the webGL animation. So you just have to add those lines in your Html file / output function.

  <div class="canvas_container">
    <canvas id="cube_canvas"></canvas>
  </div>

We will aslo need to link some javascript libraries that I have stored in a "js" subdirectory. Do it at the end of your Html file. The script "cube.js" will be our custom javascript file for this example.

  <script type="text/javascript" src="js/three.min.js"></script>
  <script type="text/javascript" src="js/Projector.js"></script>
  <script type="text/javascript" src="js/CanvasRenderer.js"></script>
  <script type="text/javascript" src="js/cube.js"></script> // our code

Javascript

Initialization of three.js

Let's dive into the javascript file cube.js now. We will create some global variables so they can be accessible from anywhere in the script and then initialize them in the init() function:

var camera, scene, renderer;
var cube, plane;
var myCanvas = document.getElementById('cube_canvas');
var width = 400;
var height = 300;

We will need a camera (our eye), a scene (the content) and a renderer to actually render a scene from the camera point of view. We will also need a cube and a plane to simulate the shadow of the cube.
Then we will retrieve the canvas element in the html document and set some width and height variables.

Let's write the init function as follows:

function init() {

    // Camera
    camera = new THREE.PerspectiveCamera( 70, width/height, 1, 1000 );
    camera.position.y = 150;
    camera.position.z = 500;
    
    // Scene
    scene = new THREE.Scene();

    // Cube
    var geometry = new THREE.BoxGeometry( 200, 200, 200 );
    for ( var i = 0; i < geometry.faces.length; i += 2 ) {

        var hex = Math.random() * 0xffffff;
        geometry.faces[ i ].color.setHex( hex );
        geometry.faces[ i + 1 ].color.setHex( hex );
    }
    var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, overdraw: 0.5 } );
    cube = new THREE.Mesh( geometry, material );
    cube.position.y = 150;
    scene.add( cube );

    // Plane
    var geometry = new THREE.PlaneBufferGeometry( 200, 200 );
    geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
    var material = new THREE.MeshBasicMaterial( { color: 0xe0e0e0, overdraw: 0.5 } );
    plane = new THREE.Mesh( geometry, material );
    scene.add( plane );

    // Renderer
    renderer = new THREE.WebGLRenderer( { canvas : myCanvas, antialias : true});
    renderer.setClearColor( 0xf0f0f0 );
    renderer.setSize( width, height );
}

In the example code above, I initialized the camera to Perspective camera, created a scene, a cube and a plane and added them to the scene. I also assigned some materials to the plane and the cube. Then I created a WebGLRenderer and I chose to render into myCanvas with antialiasing and to set its size to the width and height I chose previously.

Next we need to write the render loop:

function renderLoop() {
    requestAnimationFrame(renderLoop);
    cube.rotation.y += 0.025;    
    plane.rotation.y = cube.rotation.y;
    renderer.render( scene, camera );
}

In this loop we need to call the requestAnimationFrame() function that causes the renderer to draw the scene 60 times per second and pass the same function as a parameter.
Next I adjust the rotation around y axis for the cube and pass the scene and the camera to the renderer.render() function which will render one frame. The plane will have the same rotation speed to simulate the shadow of the cube.

Now we just have to call init() and renderLoop() functions in our js script.

init();
renderLoop();

 

Adding manual mouse controls

To add mouse controls we need new global variables to keep track of the rotation.

var camera, scene, renderer;
var cube, plane;
var myCanvas = document.getElementById('cube_canvas');
var width = 400;
var height = 300;

var manual = false; // Manual mode or automatic rotation
var cubeRotation = 0; // Store target current rotation
var cubeRotationOnMouseDown = 0; // Store rotation on mouse down event

var mouseX = 0; // Store mouse X position
var mouseXOnMouseDown = 0; // Store mouse X position on mouse down event

And we will update the cubeRotation in the renderLoop function but only during the automatic mode, we will manage the manual mode later.

function renderLoop() {
    requestAnimationFrame(renderLoop);
    if (!manual) {
      cubeRotation += 0.025;
    }
    cube.rotation.y = cubeRotation;
    plane.rotation.y = cube.rotation.y;
    renderer.render( scene, camera );
}

We will also need to add a function that triggers on the event called "mouse down" in our init function.

document.addEventListener( 'mousedown', onDocumentMouseDown, false );

And write the behavior on the "mouse down" event in the function we just gave to the addEventListener as a parameter:

function onDocumentMouseDown( event ) {
    var rect = myCanvas.getBoundingClientRect();
    
    if ( event.clientX > rect.left &&
         event.clientX < rect.right &&
         event.clientY > rect.top &&
         event.clientY < rect.bottom ){
        
        document.addEventListener( 'mousemove', onDocumentMouseMove, false );
        document.addEventListener( 'mouseup', onDocumentMouseUp, false );
        document.addEventListener( 'mouseout', onDocumentMouseOut, false );

        mouseXOnMouseDown = event.clientX ;
        cubeRotationOnMouseDown = cubeRotation;
        manual = true;
    }
}

Once the mouse button is down we need to manage 3 new events. If the mouse is moving then we will move the cube. If the mouse is up, then we will stop the manual mode and if the mouse is out of the canvas we will also stop the manual mode and return to the automatic rotation mode.
In this function I also store the position of the mouse along the x axis and the current cube rotation value. At the end of the function I set the manual boolean to true so the automatic rotation in the renderLoop will stop. I wrapped evereything inside a condition that checks if the mouse click happened in the canvas area using the bounding box object given by the getBoundingClientRect() function.

Now we need to write all 3 other events.

function onDocumentMouseMove( event ) {
    mouseX = event.clientX ;
    cubeRotation = cubeRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
}

function onDocumentMouseUp( event ) {
    document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
    document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
    document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
    manual = false;
}

function onDocumentMouseOut( event ) {
    document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
    document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
    document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
    manual = false;
}

So for the mouse moving event I added to the current cube rotation the relative movement of the mouse multiplied by a speed factor (0.02).
For the mouse up movement and the mouse out movement I removed all the other listeners and set manual to false to resume the automatic rotation.

Final code

Here is the final javascript code:

var camera, scene, renderer;
var cube, plane;
var myCanvas = document.getElementById('cube_canvas');
var width = 400;
var height = 300;

var manual = false; // Manual mode or automatic rotation
var cubeRotation = 0; // Store target current rotation
var cubeRotationOnMouseDown = 0; // Store rotation on mouse down event

var mouseX = 0; // Store mouse X position
var mouseXOnMouseDown = 0; // Store mouse X position on mouse down event

init();
renderLoop();

function init() {

    // Camera
    camera = new THREE.PerspectiveCamera( 70, width/height, 1, 1000 );
    camera.position.y = 150;
    camera.position.z = 500;
    
    // Scene
    scene = new THREE.Scene();

    // Cube
    var geometry = new THREE.BoxGeometry( 200, 200, 200 );
    for ( var i = 0; i < geometry.faces.length; i += 2 ) {

        var hex = Math.random() * 0xffffff;
        geometry.faces[ i ].color.setHex( hex );
        geometry.faces[ i + 1 ].color.setHex( hex );
    }
    var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, overdraw: 0.5 } );
    cube = new THREE.Mesh( geometry, material );
    cube.position.y = 150;
    scene.add( cube );

    // Plane
    var geometry = new THREE.PlaneBufferGeometry( 200, 200 );
    geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
    var material = new THREE.MeshBasicMaterial( { color: 0xe0e0e0, overdraw: 0.5 } );
    plane = new THREE.Mesh( geometry, material );
    scene.add( plane );

    // Renderer
    renderer = new THREE.WebGLRenderer( { canvas : myCanvas, antialias : true});
    renderer.setClearColor( 0xf0f0f0 );
    renderer.setSize( width, height );

    document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}

function onDocumentMouseDown( event ) {
    
    var rect = myCanvas.getBoundingClientRect();
    
    if ( event.clientX > rect.left &&
         event.clientX < rect.right &&
         event.clientY > rect.top &&
         event.clientY < rect.bottom ){
        
        document.addEventListener( 'mousemove', onDocumentMouseMove, false );
        document.addEventListener( 'mouseup', onDocumentMouseUp, false );
        document.addEventListener( 'mouseout', onDocumentMouseOut, false );

        mouseXOnMouseDown = event.clientX ;
        cubeRotationOnMouseDown = cubeRotation;
        manual = true;
    }
}

function onDocumentMouseMove( event ) {
    mouseX = event.clientX ;
    cubeRotation = cubeRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
}

function onDocumentMouseUp( event ) {
    document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
    document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
    document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
    manual = false;
}

function onDocumentMouseOut( event ) {
    document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
    document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
    document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
    manual = false;
}

function renderLoop() {
    requestAnimationFrame(renderLoop);
    if (!manual) {
      cubeRotation += 0.025;
    }
    cube.rotation.y = cubeRotation;
    plane.rotation.y = cube.rotation.y;
    renderer.render( scene, camera );
}



Result

 
 

GitHub markDownload it on GitHub.

Menu