Friday, September 28, 2012

Positioning Canvas Text on Three.js Sprites

‹prev | My Chain | next›

I don't quite have text sprites in Three.js working to my satisfaction. I can imagine using these things in games so that "+10" replaces a captured game element. The "+10" could then slowly rise above the spot where it was awarded until it fades quickly away. The problem in this scenario is that I am not confident that I could place text exactly where it needs to go.

From list night, I am working with the words "Game Over". Since this phrase is obviously longer than it is tall, I specify the canvas dimension accordingly:
  var canvas = document.createElement('canvas');
  canvas.width = 250;
  canvas.height = 100;
  var context = canvas.getContext('2d');
  context.font = '48px Arial';
  context.fillText("Game Over", 0, 50);
I draw the words in the 2D context of canvas. I make the font size 48 pixels high, which means that I have to shift the words down a little more than 48 pixels from the top so that I can see them.

Unfortunately, using this canvas element as a Three.js texture doesn't quite work. The text is stretched tall:


My problem is that textures in Three.js need to be squares. If they are not squares, Three.js will stretch them until they fit. This explains the undesired appearance of my "Game Over" text. It also tells me how to fix it—make the canvas width and height the same:
  var canvas = document.createElement('canvas');
  canvas.width = 250;
  canvas.height = 250;
  var context = canvas.getContext('2d');
  context.font = '48px Arial';
  context.fillText("Game Over", 0, 50);
This results in a much better looking text sprite:


I still think I need help placing the text correctly in the scene. I am already using scene (not canvas) coordinates by virtue of setting the sprite's useScreenCoordinates property to false. If I make a ball with a radius of 100 at the center of the screen, then try to place the text 100 above the center, then my text ought to be immediately on top of the ball. The result, however is:


My text is a little too high there. My problem is that the text is placed at the very top of the canvas. If I want to move the text baseline to the middle, I need only shift the distance from the top by half the height:
  var canvas = document.createElement('canvas');
  var size = 250;
  canvas.width = size;
  canvas.height = size;
  var context = canvas.getContext('2d');
  context.font = '48px Arial';
  context.fillText("Game Over", 0, size/2);
That gives me the nicely placed text:


That is not quite a robust solution however. If I decrease the font size of "Game Over", the vertical position is still good, but now the horizontal alignment is off:


Per MDN, there is a textAlign property for the canvas context that supports a value of "center". That sounds perfect. Unfortunately, it is less than perfect:


The textAlign property is not within the bounding box of the canvas. Rather, it specifies how text is drawn from the specified coordinates. In this case, my coordinates are zero pixels over and size/2 pixels down the canvas:
  context.fillText("Game Over", 0, size/2);
The text is centered on that x coordinate of zero, which is good—only I need the x coordinate to be in the center of the canvas. In other words, I want size/2 for the x position as well:


So the important points for this exercise—at least the points that confused me at first—are that canvas, like any other Three.js texture, needs to be a square and centering text on that canvas takes a bit of work.

Final version of the code

Day #523

2 comments:

  1. Thank you so much for this post. I'm doing the same thing now and was equally confused. :)

    ReplyDelete
  2. Also helps to draw your background, so you can see the canvas dimensions

    gl.fillStyle = "#888888"; // some neutral or contrast color
    gl.fillRect(0, 0, canvas.width, canvas.height);

    ReplyDelete