Reputation: 14440
I have a BoxGeometry in the scene, and I'd like to add the size of the edges on the edges, like so:
I found this article, using an older version of three.js, which contained this snippet:
function makeTextSprite(message, opts) {
var parameters = opts || {};
var fontface = parameters.fontface || 'Helvetica';
var fontsize = parameters.fontsize || 70;
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.font = fontsize + "px " + fontface;
// get size data (height depends only on font size)
var metrics = context.measureText(message);
var textWidth = metrics.width;
// canvas contents will be used for a texture
var texture = new THREE.Texture(canvas)
texture.minFilter = THREE.LinearFilter;
texture.needsUpdate = true;
var spriteMaterial = new THREE.SpriteMaterial({
map: texture,
useScreenCoordinates: false
});
var sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(100, 50, 1.0);
return sprite;
}
(Seems like the same snippet is used in this answer)
However, if I'm reading the history properly, this hasn't worked for a while, since r.64
, according to this answer. When I try to set the position of the sprite, the position seems to be being set in 2d rather than 3d -- the text doesn't move across the canvas when I rotate the camera (when I set position to 0,0,0
).
That stackoverflow answer links to this demo but I'm not sure how to adapt their code because they load an image texture, not text, and it seems like my issue might be with creating a suitable texture which can be assigned to the sprite and properly moved in 3D space.
I'd love to see an updated version of the snippet above for newer versions of three.js (I'm using r.91
). I think the same call signature as the above snippet would be nice, i.e.
sprite = makeTextSprite("Hello World", {});
sprite.position.set( 0,0,0 )
sprites.add( sprite );
would create a sprite reading "Hello World" centered at 0,0,0 in the scene.
Upvotes: 0
Views: 5497
Reputation: 14440
I found a version of the snippet above using sprites.
function makeTextSprite(message, opts) {
var parameters = opts || {};
var fontface = parameters.fontface || 'Helvetica';
var fontsize = parameters.fontsize || 120;
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.font = fontsize + "px " + fontface;
// get size data (height depends only on font size)
var metrics = context.measureText(message);
var textWidth = metrics.width;
// text color
context.fillStyle = 'rgba(0, 0, 0, 1.0)';
context.fillText(message, 0, fontsize);
// canvas contents will be used for a texture
var texture = new THREE.Texture(canvas)
texture.minFilter = THREE.LinearFilter;
texture.needsUpdate = true;
var spriteMaterial = new THREE.SpriteMaterial({ map: texture });
var sprite = new THREE.Sprite( spriteMaterial );
sprite.scale.set( 10, 5, 1.0 );
sprite.center.set( 0,1 );
return sprite;
}
The key line ended up being sprite.center.set( 0,1 );
.
Use like this:
sprite = makeTextSprite("bla");
sprite.position.copy( ... );
scene.add( sprite );
However, this solution doesn't work for SVGRenderer
, because that renderer doesn't support textures, so I'm now looking for a THREE.js SVG solution to the original problem.
Upvotes: 1
Reputation: 2541
I recommend to use THREE.CSS2DObject for it. You can set it's 3d position and control it's content using HTML (including css, you can size things with pixels rather than world units). Content will be always oriented (faced) to camera.
let el = document.createElement("div");
el.className = cssClassName;
el.textContent = "blabla";
el.style.visibility = "visible";
let obj = new THREE.CSS2DObject(el);
obj.position.copy(...)
But this approach is damn slow (ok for < 50 objects). If you need many of such objects - you need to use separate rendering pass: render extra HUD scene with same WebGL renderer and special HUD OrthographicCamera, draw using canvas 3d context procedural texture:
...setup:
let self = this;
self.renderers.WebGL.autoClear = false;
let w = self.outputContainer.offsetWidth || self.parentElem.get(0).offsetWidth;
let h = self.outputContainer.offsetHeight || self.parentElem.get(0).offsetHeight;
let fov = 75;
let aspectRatio = w / h;
let near = 1;
let far = 20000000;
self.camera = new THREE.PerspectiveCamera(fov, aspectRatio, near, far);
let hw = w / 2;
let hh = h / 2;
self.cameraHUD = new THREE.OrthographicCamera(-hw, hw, hh, -hh, 0, w);
... drawing:
function updateHUD(callback: (context: CanvasRenderingContext2D) => void) {
let self = this;
self.sceneHUD.children.slice(0).forEach(obj => {
if (!(obj instanceof THREE.OrthographicCamera)) {
self.sceneHUD.remove(obj);
}
});
let canvasEl = self.renderers.WebGL.domElement;
let bounds = canvasEl.getBoundingClientRect(); //canvasEl.offsetLeft, canvasEl.offsetTop
let canvasElHUD = self.canvasElHUD;
if (!canvasElHUD) {
canvasElHUD = document.createElement("canvas");
self.canvasElHUD = canvasElHUD;
// Get 2D context and draw.
self.bitmapHUD = canvasElHUD.getContext("2d");
// Create texture from rendered graphics.
self.textureHUD = new THREE.Texture(canvasElHUD);
self.textureHUD.minFilter = THREE.NearestFilter;
// Create HUD material.
self.materialHUD = new THREE.MeshBasicMaterial({ map: self.textureHUD });
self.materialHUD.transparent = true;
}
//set dimensions to fit the screen.
if (canvasElHUD.width != bounds.width) {
canvasElHUD.width = bounds.width;
}
if (canvasElHUD.height != bounds.height) {
canvasElHUD.height = bounds.height;
}
if (callback) {
callback(self.bitmapHUD); //custom draw
}
self.textureHUD.needsUpdate = true;
// Create plane to render the HUD. This plane fill the whole screen.
let planeGeometry = new THREE.PlaneGeometry(bounds.width, bounds.height);
let planeMesh = new THREE.Mesh(planeGeometry, self.materialHUD);
self.sceneHUD.add(planeMesh);
return planeMesh;
}
...
self.updateHUD(bitmap => {
bitmap.clearRect(0, 0, bounds.width, bounds.height); //clear 2D canvas
bitmap.strokeStyle = "green";
bitmap.lineWidth = 1;
bitmap.beginPath();
let width = p2.x - p1.x;
let height = p2.y - p1.y;
bitmap.rect(p1.x, p1.y, width, height);
bitmap.stroke();
});
... and rendering:
self.renderers.WebGL.clear();
self.renderers.WebGL.render(self.scene, camera);
self.renderers.WebGL.clearDepth();
self.renderers.WebGL.render(self.sceneOverlay, camera);
self.renderers.WebGL.clearDepth();
self.renderers.WebGL.render(self.sceneHUD, self.cameraHUD);
self.renderers.CSS2D.render(self.scene, self.camera);
self.renderers.CSS2D.render(self.sceneOverlay, self.camera);
Upvotes: 1