13

I'm working on a solar system in three.js and am curious if there is an easy way to make the labels for the planets I have below all show up the same size regardless of how far they are from the camera? I can't seem to find a solution to this. I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that. Seems like there would be an easier way to accomplish this?

Thanks!

enter image description here

Updated with answer from prisoner849. Works excellent!

enter image description here

Rankinstudio
  • 591
  • 6
  • 19
  • Hello rankind! [I did it!](https://jsfiddle.net/0L1rpayz/#&togetherjs=AdRbTa6nMg) I want you to see if this's really what you want, then I'll post as an answer. –  Nov 06 '16 at 07:46

4 Answers4

23

I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that.

And it's very simple. Let's say, a THREE.Sprite() object (label) is a child of a THREE.Mesh() object (planet), then in your animation loop you need to do

var scaleVector = new THREE.Vector3();
var scaleFactor = 4;
var sprite = planet.children[0];
var scale = scaleVector.subVectors(planet.position, camera.position).length() / scaleFactor;
sprite.scale.set(scale, scale, 1); 

I've made a very simple example of the Solar System, using this technique.

prisoner849
  • 16,894
  • 4
  • 34
  • 68
6

For the benefit of future visitors, the transform controls example does exactly this:

https://threejs.org/examples/misc_controls_transform.html

Here's how its done in the example code:

var factor;
if ( this.camera.isOrthographicCamera ) {
 factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
} else {
 factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
}
handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 );
sipickles
  • 1,637
  • 1
  • 19
  • 30
3

Finally I found the answer to your question:

First, create a DOM Element:

<div class="element">Not Earth</div>

Then set CSS styles for it:

.element {position: absolute; top:0; left:0; color: white}
//        |-------------------------------|  |-----------|
//            make the element on top of       canvas is 
//               the canvas                   black, so text
//                                            must be white

After that, create moveDom() function and run it every time you render the scene requestAnimationFrame()

  • geometry is the geometry of the mesh
  • cube is the mesh you want to create label
    var moveDom = function(){
    vector = geometry.vertices[0].clone();
    vector.applyMatrix4(cube.matrix);
    vector.project(camera);
    vector.x = (vector.x * innerWidth/2) + innerWidth/2;
    vector.y = -(vector.y * innerHeight/2) + innerHeight/2;
    //Get the DOM element and apply transforms on it
    document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
    document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
    };

You can create a for loop to set label for all the mesh in your scene.

Because this trick only set 2D position of DOM Element, the size of label is the same even if you zoom (the label is not part of three.js scene).

Full test case: https://jsfiddle.net/0L1rpayz/1/

var renderer, scene, camera, cube, vector, geometry;

var ww = window.innerWidth,
 wh = window.innerHeight;

function init(){

 renderer = new THREE.WebGLRenderer({canvas : document.getElementById('scene')});
 renderer.setSize(ww,wh);

 scene = new THREE.Scene();

 camera = new THREE.PerspectiveCamera(50,ww/wh, 0.1, 10000 );
 camera.position.set(0,0,500);
 scene.add(camera);

 light = new THREE.DirectionalLight(0xffffff, 1);
 light.position.set( 0, 0, 500 );
 scene.add(light);

 //Vector use to get position of vertice
 vector = new THREE.Vector3();

 //Generate Not Earth
 geometry = new THREE.BoxGeometry(50,50,50);
 var material = new THREE.MeshLambertMaterial({color: 0x00ff00});
  cube = new THREE.Mesh(geometry, material);
  scene.add(cube);
 //Render my scene
 render();
}
var moveDom = function(){
    vector = geometry.vertices[0].clone();
    vector.applyMatrix4(cube.matrix);
    vector.project(camera);
   vector.x = (vector.x * ww/2) + ww/2;
   vector.y = -(vector.y * wh/2) + wh/2;

    //Get the DOM element and apply transforms on it
    document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
    document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};


var counter = 0;
var render = function (a) {
 requestAnimationFrame(render);

 counter++;

 //Move my cubes
  cube.position.x = Math.cos((counter+1*150)/200)*(ww/6+1*80);
  cube.position.y = Math.sin((counter+1*150)/200)*(70+1*80);
  cube.rotation.x += .001*1+.002;
  cube.rotation.y += .001*1+.02;

  //Move my dom elements
  moveDom();
 
 renderer.render(scene, camera);
};

init();
body,html, canvas{width:100%;height:100%;padding:0;margin:0;overflow: hidden;}
.element{color:white;position:absolute;top:0;left:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<!-- My scene -->
 <canvas id="scene"></canvas>
 <div class="element">
    <h1>Not Earth</h1>
 </div>

If you downvote this, please tell me why. I will try my best to improve my posts.

  • 1
    I've spent hours trying to get this to work. Looks like you got it. Thank you! I'm sure this will help many people. :) – Rankinstudio Nov 06 '16 at 16:19
  • This way is optimised for action buttons (in my project), not label but I tell you because I think it's useful. I learned that trick here: https://codepen.io/collection/DrxLEd/ I stopped using three.js now, because documentation is poor. Good luck with your project! –  Nov 07 '16 at 13:11
  • I have definitely ran into the poor documentation issue as well, which is why I'm posting here a lot lol. Good help on here though. Thanks for your input :) – Rankinstudio Nov 07 '16 at 14:51
  • Only problem I see with this is that the label won't be hidden by objects in front of it. Depending on whether that's the desired behaviour or not that might be important. – Okarin May 01 '18 at 10:44
1

If you are using spriteMaterial to present your text, you could try to set the sizeAttenuation attribute to false.

var spriteMaterial = new THREE.SpriteMaterial( { map: spriteMap, color: 0xffffff, sizeAttenuation:false } );

See more information from here: https://threejs.org/docs/index.html#api/en/materials/SpriteMaterial.sizeAttenuation

RUI LI
  • 31
  • 2