Need help understanding use of TextRenderer

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

Need help understanding use of TextRenderer

Ryan McFall
I am a mostly self-taught OpenGL programmer teaching Computer Graphics at a small college - so I'm definitely not an expert OpenGL programmer by any means.

I am trying to develop an implementation of the board game "Monopoly", and am trying to write code that can render an individual property.  If you're not familiar with Monopoly, here's an image of the board

Image of a monopoly board

A "property" is one of the individual rectangles with a colored border at the top, such as "Connecticut Avenue" at the bottom left corner of the image.  Rendering the property itself is no problem, obviously.  Adding in the text using JOGL's TextRenderer is proving difficult, however.

The TextRenderer class seems to want to use pixels as the units for world coordinate system, as the documentation mentions that calling beginRendering will set up an orthographic projection based on the width/height passed to beginRenderering.  The documentation suggests passing the width and height of the GLAutoDrawable being drawn into as the parameters for beginRenderering.

If I pass drawable.getWidth() and drawable.getHeight(), and then draw text at (getWidth()/2, getHeight()/2), it draws text whose lower left corner is in the center of the drawable as expected.

I have learned to model domain objects, like the Monopoly properties, in their own coordinate space, usually placing their origin in the center of the object.  Then modeling transformations can be applied to transform the object appropriate based on the coordinate system representing the world being rendered.  For the property, I've modeled it using (0, 0) as the center, with the 4 vertices representing the property's coordinates being the appropriate combinations of (± WIDTH/2, ± HEIGHT/2).

I'm not sure how to model these domain objects independently like this and still use JOGL's TextRenderer code to render the property name and price.

What I have thought to do is to invert both the modelview and projection matrices to obtain a matrix that will allow me to know where in the viewport the property's vertices are being rendered, and using that to help me determine an appropriate (x, y) location to use within the coordinate system set up by TextRenderer.beginRendering.

I haven't yet tried this - is this an appropriate approach, or am I making the problem too difficult?

Thanks in advance for any ideas,
Ryan
Reply | Threaded
Open this post in threaded view
|

Re: Need help understanding use of TextRenderer

Ryan McFall
Sorry, after I thought about it some more, I realized this was easier than I thought.

I obtained the modelview matrix using glGetFloatv, multiplied the desired location within my Property's idea of its coordinate system, then retrieved the viewport and applied the world-to-viewport computations to help me know where I should draw the text.

It's all working now!
Reply | Threaded
Open this post in threaded view
|

Re: Need help understanding use of TextRenderer

gouessej
Administrator
Hello

Feel free to release your source code, it could be valuable for others :)
Julien Gouesse | Personal blog | Website
Reply | Threaded
Open this post in threaded view
|

Re: Need help understanding use of TextRenderer

Ryan McFall

Sure, I'm happy to post the code that I used in case someone stumbles across this question in the future!

First, some building block methods:

This one retrieves the current ModelView matrix - since there may have been calls to glTranslate, glRotate, etc. that position the property somewhere on the board, I need this matrix to help me compute where the property is in screen coordinates.

private static Matrix4d getModelViewMatrix (GL gl) {
  float[] modelViewMatrix = new float[16];
  gl.glGetFloatv(GLMatrixFunc.GL_MODELVIEW_MATRIX, modelViewMatrix, 0);
  Matrix4d mvMatrix = new Matrix4d();
  mvMatrix.set(modelViewMatrix);
  return mvMatrix;
}

Next is getScreenCoordinatesForPoint. It uses the model view matrix from above to transform the (x,y) coordinates in the property's coordinate system (with (0,0) in the center of the property) to the world coordinates where the property is being drawn. Then, it uses a utility method to map those world coordinates into screen coordinates.

private static Vector2i getScreenCoordinatesForPoint(GL gl, Matrix4d mvMatrix, double x, double y) {
  Vector4d point = new Vector4d(x, y, 1.0, 1.0).mul(mvMatrix);
  return GLUtilities.worldToViewport(gl, MonopolyWindow.world, point.x, point.y);
}

Finally, the method to render the text now that I know where it should go in the viewport:

private static void renderPropertyName(Property property, GLCanvas canvas, TextRenderer renderer, Matrix4d mvMatrix) {
        
  LinkedList lines = breakPropertyNameIntoLines(property.getName(), canvas, renderer, mvMatrix);
  final int lineSpacing = 6;
      
  GL2 gl = (GL2) canvas.getGL();
  Vector2i textLocation = getScreenCoordinatesForPoint(gl, mvMatrix, 0, PROPERTY_HEIGHT/2 - HEADER_HEIGHT);

  renderer.beginRendering(Utilities.getCanvasWidth(canvas), Utilities.getCanvasHeight(canvas));
  {
    for (String line : lines) {
      Rectangle2D bounds = renderer.getBounds(line);
      textLocation.y -= (lineSpacing + bounds.getHeight());
      renderer.draw(line, (int) (textLocation.x-bounds.getWidth()/2), textLocation.y);
    }
  }
  renderer.endRendering();
}

Hope that's helpful!

Reply | Threaded
Open this post in threaded view
|

Re: Need help understanding use of TextRenderer

gouessej
Administrator
Ryan McFall wrote
<pre>
private static Matrix4d getModelViewMatrix (GL gl) {
  float[] modelViewMatrix = new float[16];
  gl.glGetFloatv(GLMatrixFunc.GL_MODELVIEW_MATRIX, modelViewMatrix, 0);
  Matrix4d mvMatrix = new Matrix4d();
  mvMatrix.set(modelViewMatrix);
  return mvMatrix;
}
</pre>
I advise you to create a single direct NIO buffer once instead of using an array here, JOGL has to perform the conversion for you under the hood.
Julien Gouesse | Personal blog | Website
Reply | Threaded
Open this post in threaded view
|

Re: Need help understanding use of TextRenderer

Ryan McFall

Thanks, I will take a look at doing that.

I have a follow-up question regarding the use of TextRenderer. I've read through the source code a bit, and also the docs, and since the source talks about textures and vertex buffer arrays, I think it is rendering text by drawing appropriate quads (or a couple of triangles making up a quad) with a texture containing the appropriate glyph for each character.

Nothing I'm reading suggests that changes to the Modelview matrix shouldn't affect the way that text is drawn. However, I have this code:


TextRenderer renderer = new TextRenderer(getFont());
renderer.setColor(Color.black);
		
gl.glMatrixMode(MatrixMode.MODELVIEW.getValue());
gl.glLoadIdentity();
gl.glTranslated(50, 0, 0);
		
renderer.beginRendering(canvas.getWidth(), canvas.getHeight());
renderer.draw("Bottom Left (0, 0)", 0, 0);
renderer.endRendering();

and it draws the string "Bottom left (0, 0)" at the bottom left corner of my drawable, seemingly ignoring my modelview matrix.

However, if I move the code that manipulates the modelview matrix after the call to beginRendering, it does what I expect it to. After seeing this, I looked back at the Javadoc for beginRendering and noticed it does state that it pushes the modelview matrix in addition to the projection matrix.

But the docs don't say anything about beginRendering changing the modelview matrix. My example above seems to indicate that the identity matrix is being loaded into the modelview matrix. I haven't yet found evidence of this in the source, however.

Am I correct about this? If so, I think it would be good if the docs for beginRendering mention this explicitly. It's clear that the projection matrix is going to be changed, but less clear that the modelview matrix will be, at least to me. I guess if it wasn't going to be changed, it wouldn't need to be pushed, but it seems it would be better to state it explicitly.

Reply | Threaded
Open this post in threaded view
|

Re: Need help understanding use of TextRenderer

gouessej
Administrator
Have you tried begin3DRendering() and end3DRendering()?
Julien Gouesse | Personal blog | Website
Reply | Threaded
Open this post in threaded view
|

Re: Need help understanding use of TextRenderer

Ryan McFall

I just tried it with begin3DRenderering and I think that the answer is yes, it works correctly.


gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
gl.glLoadIdentity();
glu.gluOrtho2D(0, canvas.getWidth(), 0, canvas.getHeight());
		
gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glTranslated(50, 25, 0);
		
renderer.begin3DRendering();
renderer.draw(str, 0, 0);
renderer.end3DRendering();

draws the string appropriately translated up and to the right.

Reply | Threaded
Open this post in threaded view
|

Re: Need help understanding use of TextRenderer

Ryan McFall
One reason I struggle with begin3DRendering is that I am unsure of what vertices the draw method uses for the quads that it renders.  Therefore I often end up with something that looks more like ink blots than text, because whatever projection matrix I'm using  is making the rendered quads too large, effectively "zooming in" on the font.



I now recall the draw3D method, which is maybe what I should be using.  This method includes a scaleFactor parameter which I am guessing is designed to address the "ink blot" problem described above.  The above image uses a scaleFactor of 1, with a 2D world coordinate system set up with gluOrtho2D.  Both X and Y values range from -1 to 1.  If I change the scale factor to 0.01 through trial and error, I get this image



The documentation doesn't really say how to use scaleFactor.  How do I calculate an appropriate value for scaleFactor, instead of guessing?