codester's picture

How to display simple 2D text labels at 3D positions?

Hello,

For my current project I need the following funcionality:
The program displays celestial bodies at their positions (3D). This is what I've done so far using OpenTK. I'd now like to display text labels at the positions of the bodies. In general I want to display 2D text at any position in 3D space.
The result should look similar to this. The screenshot was taken from GMAT, an open source project. Of course I already tried to search their code for a solution but there are tot many files containing floods of cryptic c++. I also tried to find a solution in these forums and in the example sources on this page but the only thing I've accomplished was this.

VB code I've used:

 Private Sub drawString(ByVal s As String, ByVal position As Vector3, ByVal color As Color)
	Using printer As New OpenTK.Graphics.TextPrinter
		printer.Print(s, SystemFonts.DefaultFont, color)
	End Using
End Sub

Is there another way to print text? (I've read this but I can't believe that it's that difficult to print a simple string?)

[Mod hint: use <code lang="vbnet"> for to prettify vb.net code]


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

I think gluProject is the answer here. Try this:

Import OpenTK.Graphics
Import OpenTK.Math
 
Public Class Blah
 
    ' Hint: create a single TextPrinter and use it across calls to drawString
    Dim printer As New OpenTK.Graphics.TextPrinter
 
    Private Sub drawString(ByVal s As String, ByVal position As Vector3, ByVal color As Color)
        Dim pos As New Vector4(position, 1.0F)
        Dim modelview As Matrix4
        Dim projection As Matrix4
        Dim modelviewProjection As Matrix4
        Dim viewport As New Single(4)
 
        GL.GetFloat(GetPName.ModelviewMatrix, modelview)
        GL.GetFloat(GetPName.ProjectionMatrix, projection)
        GL.GetInteger(GetPName.Viewport, viewport)
 
        Matrix4.Mult(projection, modelview, modelviewProjection)
        Vectror4.Transform(pos, modelviewProjection, pos)
 
        printer.Begin()
        ' Hint: use SystemFonts.MessageBoxFont to get the native UI font on Windows.
        printer.Print(s, SystemFonts.MessageBoxFont, color, new RectangleF(pos.X, pos.Y, 0, 0))
        printer.End()
    End Sub
 
End Class

This code mimics Glu.Project(): it tries to project the 3d point onto the monitor and print text there.

Disclaimer: didn't test this on a compiler and I've probably multiplied the matrices the wrong way round. Play around with the parameters if this doesn't work at first.

Text printing is a very complex topic. Suffice to say that FreeType (an open source library that renders glyphs) contains 260K lines of code. If you add text layout into the mix, you could easily hit the 500K mark.

codester's picture

Hello,

thanks for helping me but your code doesn't work. It doesn't even compile since GL.GetFloat expects modelview and projection to be a Single and GL.GetInteger requires an integer.

I also tried to use GL.Project but since I don't know where to get the parameters this wasn't a success either.

I don't want to complain but why doesn't TextPrinter make it easier to accomplish such tasks?

the Fiddler's picture

As I said, I didn't run the code through the compiler. Updated code:

    Private Sub drawString(ByVal s As String, ByVal position As Vector3, ByVal color As Color)
        Dim pos As New Vector4(position, 1.0F)
        Dim modelview As Matrix4
        Dim projection As Matrix4
        Dim modelviewProjection As Matrix4
        Dim viewport As New Single(4)
        Dim x As Single
        Dim y As Single
 
        GL.GetFloat(GetPName.ModelviewMatrix, modelview)
        GL.GetFloat(GetPName.ProjectionMatrix, projection)
        GL.GetFloat(GetPName.Viewport, viewport)
 
        Matrix4.Mult(projection, modelview, modelviewProjection)
        Vector4.Transform(pos, modelviewProjection, pos)
        x = viewport(0) + viewport(2) * (pos.X + 1) / 2.0F
        y = viewport(1) + viewport(3) * (pos.Y + 1) / 2.0F
 
        printer.Begin()
        ' Hint: use SystemFonts.MessageBoxFont to get the native UI font on Windows.
        printer.Print(s, SystemFonts.MessageBoxFont, color, new RectangleF(x, y, 0, 0))
        printer.End()
    End Sub

Reference: gluProject

TextPrinter draws 2d text on screen. It's your responsibility to tell it *where* to draw the text. TextPrinter has no idea what kind of projection you are using and, indeed, if you are even using the deprecated OpenGL matrix stack.

entity's picture

I dont think textprinter should care about unprojecting.

Fiddler showed a standard approach of projecting the 3d coordinates to screen space.
My sample code is C# using Glu.Project and a WinForms-Control named glControl1 :

private Vector3 WorldToScreen(Vector3 world)
{
	Vector3 screen;
 
	GL.GetInteger(GetPName.Viewport, Viewport);
	GL.GetDouble(GetPName.ModelviewMatrix, ModelViewMatrix);
	GL.GetDouble(GetPName.ProjectionMatrix, ProjectionMatrix);
 
 
	Glu.Project(world,
	ModelViewMatrix,
	ProjectionMatrix,
	Viewport,
	out screen);
 
	screen.Y = glControl1.Height - screen.Y;
	return screen;
}
 
 
private void Render()
{
	GL.Clear(ClearBufferMask.ColorBufferBit |
		ClearBufferMask.DepthBufferBit);
 
	GL.MatrixMode(MatrixMode.Modelview);
	GL.LoadIdentity();
 
	Glu.LookAt(cameraPosition, Vector3.Zero, Vector3.UnitY);
 
	 Vector3 screen = new Vector3();
 
	screen = WorldToScreen(new Vector3(-1.0f, -1.0f, 4.0f));
 
	printer.Begin();
 
	GL.Translate(screen);
	printer.Print("Text at coordinates (-1,-1,4)", sans_serif, Color.White);
 
	printer.End();
 
	glControl1.SwapBuffers();
}
codester's picture

Thanks again for your help.

Still your code doesn't compile. At least I failed so far. Do you test it with VS 08 Visual Basic compiler? And, just to be sure, we are talking about OpenTK 0.9.7 aren't we? Here's what I get with your code:

Private Overloads Sub drawString(ByVal s As String, ByVal printer As TextPrinter, ByVal position As Vector3, ByVal color As Color)
			Dim pos As New Vector4(position, 1.0F)
			Dim modelview As Matrix4
			Dim projection As Matrix4
			Dim modelviewProjection As Matrix4
			Dim viewport(3) As Single ' I assume this line should create an array of type Single with 4 items?
			Dim x As Single
			Dim y As Single
 
			GL.GetFloat(GetPName.ModelviewMatrix, modelview)' This and the following line cause errors: GL.GetFloat requires "modelview" or "projection" to be either a Single or an array of Singles.
			GL.GetFloat(GetPName.ProjectionMatrix, projection)
			GL.GetFloat(GetPName.Viewport, viewport)
 
			Matrix4.Mult(projection, modelview, modelviewProjection)
			Vector4.Transform(pos, modelviewProjection, pos)
			x = viewport(0) + viewport(2) * (pos.X + 1) / 2.0F
			y = viewport(1) + viewport(3) * (pos.Y + 1) / 2.0F
 
			printer.Begin()
			' Hint: use SystemFonts.MessageBoxFont to get the native UI font on Windows.
			printer.Print(s, SystemFonts.MessageBoxFont, color, New RectangleF(x, y, 0, 0))
			printer.End()
		End Sub
codester's picture

@ entity: In function "WorldToScreen", what are "Viewport", "ModelViewMatrix" and "ProjectionMatrix" declared as? (And where? Or is there something like "Option Explicit Off" (Visual Basic) in C#, too?)

the Fiddler's picture

You are right about viewport, it should be an array of 4 items.

The necessary GetFloat overloads are not available in OpenTK 0.9.7, so you'll have to the calls like this:

GL.GetFloat(GetPName.ModelviewMatrix, modelview.Row0.X)
GL.GetFloat(GetPName.ProjectionMatrix, projection.Row0.X)

This is decidedly non-intuitive, I'll make sure the necessary overloads exist in OpenTK 0.9.8.

The fields in entity's code are presumably Double(16) (projection, modelview) and Integer(4) (viewport) arrays.

Edit: overloads now in SVN.

codester's picture

Thanks for your help. It works very nice

the Fiddler's picture

Indeed, looks very nice.

Judging from the form of the letters, it looks like cleartype is off? If you look closely, "Sol" and "Earth" look somewhat blurry. You can fix this by enabling cleartype (which effectively triples horizontal resolution). Alternatively, you can "snap" text to integer positions - animation will be less smooth, but text will stay crisp even without cleartype:

printer.Print(s, SystemFonts.MessageBoxFont, color, New RectangleF((int)x, (int)y, 0, 0))

For best results, you can combine both approaches: enable cleartype and snap text.

codester's picture

Hi,

I don't see any advantage in cleartype but text snap text looks interesting. Will try it...