How can I make my text labels face the camera at all times? Perhaps using sprites?


I'm looking at two examples, one is canvas interactive objects and the other is mouse tooltip. I tried combining the two to generate text labels on each individual cube and here's what I have so far.

However, the text moves with the rotating cubes and the text appears backwards or sideways at times.

How can I make the text fixed in a sprite like in the mouse tooltip (http://stemkoski.github.io/Three.js/Mouse-Tooltip.html) example? I tried to incorporate the sprite but I kept getting errors. I'm not sure how to do it. Could you explain how I can go by it?

Thanks.

Here's my code so far:

<!DOCTYPE html>
<html lang="en">
    <head>
            <title>three.js canvas - interactive - cubes</title>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
            <style>
                    body {
                            font-family: Monospace;
                            background-color: #f0f0f0;
                            margin: 0px;
                            overflow: hidden;
                    }
            </style>
    </head>
    <body>

            <script src="js/three.min.js"></script>

            <script src="js/stats.min.js"></script>

            <script>

                    var container, stats;
                    var camera, scene, projector, renderer;
                    var projector, mouse = { x: 0, y: 0 }, INTERSECTED;
                    var particleMaterial;
                    var currentLabel = null;

                    var objects = [];

                    init();
                    animate();

                    function init() {

                            container = document.createElement( 'div' );
                            document.body.appendChild( container );

                            var info = document.createElement( 'div' );
                            info.style.position = 'absolute';
                            info.style.top = '10px';
                            info.style.width = '100%';
                            info.style.textAlign = 'center';
                            info.innerHTML = '<a href="http://threejs.org" target="_blank">three.js</a> - clickable objects';
                            container.appendChild( info );

                            camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
                            camera.position.set( 0, 300, 500 );

                            scene = new THREE.Scene();

                            var geometry = new THREE.CubeGeometry( 100, 100, 100 );

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

                                    var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
                                    object.position.x = Math.random() * 800 - 400;
                                    object.position.y = Math.random() * 800 - 400;
                                    object.position.z = Math.random() * 800 - 400;

                                    object.scale.x = Math.random() * 2 + 1;
                                    object.scale.y = Math.random() * 2 + 1;
                                    object.scale.z = Math.random() * 2 + 1;

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

                                    object.label = "Object " + i;

                                    scene.add( object );

                                    objects.push( object );

                            }

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

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

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

                                    }

                            } );

                            projector = new THREE.Projector();

                            renderer = new THREE.CanvasRenderer();
                            renderer.setSize( window.innerWidth, window.innerHeight );

                            container.appendChild( renderer.domElement );

                            stats = new Stats();
                            stats.domElement.style.position = 'absolute';
                            stats.domElement.style.top = '0px';
                            container.appendChild( stats.domElement );

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

                            //

                            window.addEventListener( 'resize', onWindowResize, false );

                    }

                    function onWindowResize() {

                            camera.aspect = window.innerWidth / window.innerHeight;
                            camera.updateProjectionMatrix();

                            renderer.setSize( window.innerWidth, window.innerHeight );

                    }

                    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 ) {

                            if ( intersects[ 0 ].object != INTERSECTED ) 
                                            {

                                             // restore previous intersection object (if it exists) to its original color
                                            if ( INTERSECTED ) {


                                            INTERSECTED.material.color.setHex( INTERSECTED.currentHex ); } 

                                            // store reference to closest object as current intersection object
                                            INTERSECTED = intersects[ 0 ].object;
                                            // store color of closest object (for later restoration)
                                            INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
                                            // set a new color for closest object
                                            INTERSECTED.material.color.setHex( 0xffff00 );

                                            var canvas1 = document.createElement('canvas');
                                            var context1 = canvas1.getContext('2d');
                                            context1.font = "Bold 40px Arial";
                                            context1.fillStyle = "rgba(255,0,0,0.95)";
                                            context1.fillText(INTERSECTED.label, 0, 50);

                                            // canvas contents will be used for a texture
                                            var texture1 = new THREE.Texture(canvas1) 
                                            texture1.needsUpdate = true;

                                            var material1 = new THREE.MeshBasicMaterial( {map: texture1, side:THREE.DoubleSide } );
                                            material1.transparent = true;

                                            var mesh1 = new THREE.Mesh(
                                            new THREE.PlaneGeometry(canvas1.width, canvas1.height),
                                            material1



                                    );
                                            mesh1.position = intersects[0].point;
                                            if (currentLabel)
                                                    scene.remove(currentLabel);
                                            scene.add( mesh1 );                             
                                            currentLabel = mesh1;
                            } 

                            else // there are no intersections
                                            {
                                    // restore previous intersection object (if it exists) to its original color
                                    if ( INTERSECTED ) {
                                            console.log("hello");
                                            INTERSECTED.material.color.setHex( INTERSECTED.currentHex );
                                            }
                                            // remove previous intersection object reference
                                            //     by setting current intersection object to "nothing"
                                            INTERSECTED = null;
                                            mesh1 = null; 
                                            mesh1.position = intersects[0].point; 
                                            scene.add( mesh1 );

                                            }






                                    //var particle = new THREE.Particle( particleMaterial );
                                    //particle.position = intersects[ 0 ].point;
                                    //particle.scale.x = particle.scale.y = 8;
                                    //scene.add( particle );

                            }

                            /*
                            // Parse all the faces
                            for ( var i in intersects ) {

                                    intersects[ i ].face.material[ 0 ].color.setHex( Math.random() * 0xffffff | 0x80000000 );

                            }
                            */


                    }

                    //

                    function animate() {

                            requestAnimationFrame( animate );

                            render();
                            stats.update();

                    }

                    var radius = 600;
                    var theta = 0;

                    function render() {

                            theta += 0.1;

                            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 );

                    }

            </script>

    </body>

Billboarding is easy. All you have to do, in your case, is add this inside your render loop:

if ( currentLabel ) currentLabel.lookAt( camera.position );