I have 3D model which I have created in three.js. Based on some data, I want to create a set of arrows which is decorated by a small text label. These labels should be in 2D
I've published a module on NPM that does that for you: https://github.com/gamestdio/three-text2d
It allows you to have an Sprite
or Mesh
with the rendered text without dealing with canvas manually.
Example:
var Text2D = require('three-text2d').Text2D
var text = new Text2D("Hello world!", { font: '30px Arial', fillStyle: '#000000', antialias: true })
scene.add(text)
The response by @LeeStemkoski was quite useful. Yet there is a slight change that needs to be done to the code in order for the text to follow your arrows around i.e. in case the arrow gets moved around, rotated, etc.
See the next code
var canvas1 = document.createElement('canvas');
var context1 = canvas1.getContext('2d');
context1.font = "Bold 10px Arial";
context1.fillStyle = "rgba(255,0,0,1)";
context1.fillText('Hello, world!', 0, 60);
// 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(50, 10),
material1
);
mesh1.position.set(25, 5, -5);
mesh1.rotation.x = -0.9;
shape.add(mesh1);
// Note that mesh1 gets added to the shape and not to the scene
scene.add(shape)
As you can see, the trick is to add the text (mesh1
) to the shape
(your "arrow") instead of adding it to the scene.
I hope this helps.
If you don't mind that the text will always be on top (e.g. if the object is blocked by something else, its text label will still be visible and on top of everything else), and that the text will not be affected by any rendering like lights/shadow, etc, then HTML is the easiest way to go imho.
Here is some sample code:
var text2 = document.createElement('div');
text2.style.position = 'absolute';
//text2.style.zIndex = 1; // if you still don't see the label, try uncommenting this
text2.style.width = 100;
text2.style.height = 100;
text2.style.backgroundColor = "blue";
text2.innerHTML = "hi there!";
text2.style.top = 200 + 'px';
text2.style.left = 200 + 'px';
document.body.appendChild(text2);
Substitute 200 in the style.top and style.left variables for the y and x coordinates (respectively) you wish to place the text. They will be positive values where (0,0) is the top left of the canvas. For some guidance on how to project from the 3D point in your canvas to a 2D point in pixels in your browser window, use a code snippet like the following:
function toXYCoords (pos) {
var vector = projector.projectVector(pos.clone(), camera);
vector.x = (vector.x + 1)/2 * window.innerWidth;
vector.y = -(vector.y - 1)/2 * window.innerHeight;
return vector;
}
Make sure you have called camera.updateMatrixWorld() beforehand.
From the "geometry / text / shape" of Three js : https://threejs.org/examples/?q=text#webgl_geometry_text_shapes
I simplified the code for just have the strict necessary to add a 2D dynamic text :
var loader = new THREE.FontLoader();
loader.load( 'assets/fonts/helvetiker_regular.typeface.json', function ( font ) {
var textPositions = [[1, 1, 1],
[2, 2, 2]];
var textMessages = ['Text 1', 'Text 2'];
var textSizes = [0.1, 0.2];
var textName = ['text1', 'text2'];
var textsNumber = textPositions.length;
for (var i = 0; i < textsNumber; i++) {
the method 'generateShapes' permit to create the text in shape
var textsShapes = font.generateShapes( textMessages[i], textSizes[i] );
var textsGeometry = new THREE.ShapeBufferGeometry( textsShapes );
var textsMaterial = new THREE.MeshBasicMaterial({color: 0xeeeeee});
var text = new THREE.Mesh(textsGeometry, textsMaterial);
text.position.set(textPositions[i][0], textPositions[i][1], textPositions[i][2]);
text.name = textName[i];
scene.add(text);
}
});
If you actually want to include text (or any image) from a canvas object as part of your 3D scene, check out the sample code at: http://stemkoski.github.com/Three.js/Texture-From-Canvas.html
Update : This method is Deprecated now and is CPU heavy, don't use it.
I found out another only three.js based solution,
var textShapes = THREE.FontUtils.generateShapes( text, options );
var text = new THREE.ShapeGeometry( textShapes );
var textMesh = new THREE.Mesh( text, new THREE.MeshBasicMaterial( { color: 0xff0000 } ) ) ;
scene.add(textMesh);
// Example text options : {'font' : 'helvetiker','weight' : 'normal', 'style' : 'normal','size' : 100,'curveSegments' : 300};
Now to edit this text dynamically,
var textShapes = THREE.FontUtils.generateShapes( text, options );
var text = new THREE.ShapeGeometry( textShapes );
textMesh.geometry = text;
textMesh.geometry.needsUpdate = true;