three.js

Three.js: change the pivot point of a sprite


I've created a 3D map and I'm labelling points on this map through Sprites. This in itself works fine, except for the positioning of the sprite labels.

Because I'm creating a map the camera can tilt from 0 to 90 degrees, while ideally the label always stays some distance directly above the item it is labelling on the screen. But unfortunately, as sprites are always centred around their origin and that overlaps the item, I have to move the sprite up on the Y world axis and with that the centre location of the sprite changes as the camera is tilted. This looks weird if the item looked at is off centre, and doesn't work too well when the camera is looking straight down.

No jsfiddle handy, but my application at http://leeft.eu/starcitizen/ should give a good impression of what it looks like.

The code of THREE.SpritePlugin suggests to me it should be possible to use "matrixWorld" to shift the sprite some distance up on the screen's Y axis while rendering, but I can't work out how to use that, nor am I entirely sure that's what I need to use in the first place.

Is it possible to shift the sprites up on the screen while rendering, or perhaps change their origin? Or is there maybe some other way I can achieve the same effect?

Three.js r.67


Solution

  • As suggested by WestLangley, I've created a workable solution by changing the sprite position based on the viewing angle though it took me hours to work out the few lines of code needed to get the math working. I've updated my application too, so see that for a live demo.

    With the tilt angle phi and the heading angle theta as computed from the camera in OrbitControls.js the following code computes a sprite offset that does exactly what I want it to:

    // Given:
    // phi = tilt; 0 = top down view, 1.48 = 85 degrees (almost head on)
    // theta = heading; 0 = north, < 0 looking east, > 0 looking west
    
    // Compute an "opposite" angle; note the 'YXZ' axis order is important
    var euler = new THREE.Euler( phi + Math.PI / 2, theta, 0, 'YXZ' );
    // Labels are positioned 5.5 units up the Y axis relative to its parent
    var spriteOffset = new THREE.Vector3( 0, -5.5, 0 );
    // Rotate the offset vector to be opposite to the camera
    spriteOffset.applyMatrix4( new THREE.Matrix4().makeRotationFromEuler( euler ) );
    
    scene.traverse( function ( object ) {
       if ( ( object instanceof THREE.Sprite ) && object.userData.isLabel ) {
          object.position.copy( spriteOffset );
       }
    } );
    

    Note for anyone using this code: that the sprite labels are children of the object group they're referring to, and this only sets a local offset from that parent object.