Attach Text to a Mesh

Jzone

26-03-2009 19:05:07

Hello, I am new to Ogre/Python-Ogre and am having a bit of difficulty figuring something out. What I would like to do is have a mesh, such as a cube, and attach some text to the mesh so that if the mesh moves, or the camera angle changes the text still appears in the same place on the mesh.

Currently I am able to display the mesh, with a texture, and display the text. My problem is actually attaching the text to the mesh. When I made attempts to attach the text to the node containing the mesh I would just receive errors telling me that the parameters I passed were not correct.

This is the current code I have for creating my scene:
sceneManager = self.root.createSceneManager(ogre.ST_GENERIC, "Default SceneManager")
camera = sceneManager.createCamera('PlayerCam')
camera.setPosition(ogre.Vector3(0, 0, 500))
camera.lookAt(ogre.Vector3(0, 0, -300))
camera.NearClipDistance = 5
viewPort = self.renderWindow.addViewport(camera)

sceneManager.ambientLight = ogre.ColourValue (1, 1, 1)

matManager = ogre.MaterialManager.getSingleton()
bumpMat = matManager.create('BumpMat', ogre.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME)
bumpMat.lightingEnabled = False
tus = bumpMat.getTechnique(0).getPass(0).createTextureUnitState('BumpyMetal.jpg', 0)

cubes_node = sceneManager.getRootSceneNode().createChildSceneNode('CubesNode')

entity = sceneManager.createEntity('Cube', 'cube.mesh')
entity.setMaterialName('BumpMat')
node = cubes_node.createChildSceneNode('CubeNode1', (0, 0, 0))
node.attachObject(entity)

overlayManager = ogre.OverlayManager.getSingleton()
#create panel
panel = overlayManager.createOverlayElement('Panel', 'PanelName')
panel.metricsMode = ogre.GMM_PIXELS
panel.setPosition(10, 10)
panel.setDimensions(0, 0)
panel.materialName="Core/StatsBlockCenter"

textElement = ogre.TextAreaOverlayElement('MessageArea')
textElement.metricsMode = ogre.GMM_PIXELS
textElement.setPosition(10, 10)
textElement.setDimensions(100, 100)
textElement.caption = "Some Text"
textElement.charHeight = 24
textElement.fontName = "BlueHighway"
textElement.colour = ogre.ColourValue(0.0, 1.0, 0.0)

overlay = overlayManager.create('MessageOverlay')
overlay.add2D(panel)
panel.addChild(textElement)
overlay.show()


If you could point me in the right direction, I would really appreciate it.

Oh, and the text needs to be able to change at any time, so simply creating a texture with the text already written on it is no good.

bharling

27-03-2009 15:57:30

Lucky for you I just happen to have probably exactly what you need lying around:

class NodeLabel:
'''A label on the overlay, that follows a NODE around the screen'''
def __init__(self, overlay, text, node, screenDimensions, offset=ogre.Vector3.ZERO):
self.sWidth = screenDimensions[0]
self.sHeight = screenDimensions[1]
elementName = text.replace(" ", "_")
self.panel = ogre.OverlayManager.getSingleton().createOverlayElement("Panel", text.replace(" ", "_"))
#self.panel.materialName = "RedBacker"
self.text = ogre.TextAreaOverlayElement("Label_" + elementName)
self.text.caption = text
self.obj = node
self.screenPosition = ogre.Vector2.ZERO
self.offset = offset
self.panel.metricsMode = ogre.GMM_PIXELS
self.text.metricsMode = ogre.GMM_PIXELS
self.text.setAlignment(ogre.TextAreaOverlayElement.Alignment.Center)
self.text.charHeight = 20
self.text.fontName = "Din_Bold" # "TrebuchetMSBold"
self.text.colourBottom = ogre.ColourValue(1.0, 1.0, 1.0)
self.text.colourTop = ogre.ColourValue(1.0, 1.0, 1.0)
self.panel.setDimensions(160, 70)
self.text.setDimensions(100, 50)
self.text.setPosition(120,10)
# off-screen at first
self.panel.setPosition( -100, -100 )
self.panel.addChild( self.text )
overlay.add2D( self.panel )

def update(self, camera):
inView = getNodeScreenSpaceCoords( self.obj, camera, self.screenPosition )
if inView:
x = (self.sWidth * self.screenPosition.x) - 125
y = (self.sHeight * self.screenPosition.y) + self.offset.y
self.panel.setPosition( x, y )



class OverlayLabel:
'''A label on the overlay, that follows a movableObject around the screen'''
def __init__(self, overlay, text, movableObject, screenDimensions, offset=ogre.Vector3.ZERO):
self.sWidth = screenDimensions[0]
self.sHeight = screenDimensions[1]
elementName = text.replace(" ", "_")
self.panel = ogre.OverlayManager.getSingleton().createOverlayElement("Panel", text.replace(" ", "_"))
self.panel.materialName = "Stories/Backer"
self.text = ogre.TextAreaOverlayElement("Label_" + elementName)
self.text.caption = text
self.obj = movableObject
self.screenPosition = ogre.Vector2.ZERO
self.offset = offset
self.panel.metricsMode = ogre.GMM_PIXELS
self.text.metricsMode = ogre.GMM_PIXELS
self.text.setAlignment(ogre.TextAreaOverlayElement.Alignment.Center)
self.text.charHeight = 16
self.text.fontName = "Din" # "TrebuchetMSBold"
self.text.colourBottom = ogre.ColourValue(1.0, 1.0, 1.0)
self.text.colourTop = ogre.ColourValue(1.0, 1.0, 1.0)
self.panel.setDimensions(250, 35)
self.text.setDimensions(100, 50)
self.text.setPosition(125,10)
# off-screen at first
self.panel.setPosition( -100, -100 )
self.panel.addChild( self.text )
overlay.add2D( self.panel )

def update(self, camera):
inView = getScreenSpaceCoords( self.obj, camera, self.screenPosition )
if inView:
x = (self.sWidth * self.screenPosition.x) - 125
y = (self.sHeight * self.screenPosition.y)
self.panel.setPosition( x, y )

def getNodeScreenSpaceCoords( movObj, camera, result ):
if not movObj.inSceneGraph:
return False
## AABB = movObj.getWorldBoundingBox(True)
## #/**
## #* If you need the point above the object instead of the center point:
## #* This snippet derives the average point between the top-most corners of the bounding box
## point = ogre.Vector3(AABB.getCorner(ogre.AxisAlignedBox.FAR_LEFT_TOP)
## + AABB.getCorner(ogre.AxisAlignedBox.FAR_RIGHT_TOP)
## + AABB.getCorner(ogre.AxisAlignedBox.NEAR_LEFT_TOP)
## + AABB.getCorner(ogre.AxisAlignedBox.NEAR_RIGHT_TOP)) / 4;
#*/
# Get the center point of the object's bounding box
#point = AABB.getCenter()
#// Is the camera facing that point? If not, return false
point = movObj.getPosition()
cameraPlane = ogre.Plane(camera.getDerivedOrientation().zAxis(), camera.getDerivedPosition())
if cameraPlane.getSide(point) != ogre.Plane.NEGATIVE_SIDE:
return False
#// Transform the 3D point into screen space
point = camera.getProjectionMatrix() * (camera.getViewMatrix() * point)
#// Transform from coordinate space [-1, 1] to [0, 1] and update in-value
result.x = (point.x / 2) + 0.5
result.y = 1 - ((point.y / 2) + 0.5)
return True


def getScreenSpaceCoords( movObj, camera, result ):
if not movObj.isInScene():
return False
AABB = movObj.getWorldBoundingBox(True)
#/**
#* If you need the point above the object instead of the center point:
#* This snippet derives the average point between the top-most corners of the bounding box
point = ogre.Vector3(AABB.getCorner(ogre.AxisAlignedBox.FAR_LEFT_TOP)
+ AABB.getCorner(ogre.AxisAlignedBox.FAR_RIGHT_TOP)
+ AABB.getCorner(ogre.AxisAlignedBox.NEAR_LEFT_TOP)
+ AABB.getCorner(ogre.AxisAlignedBox.NEAR_RIGHT_TOP)) / 4;
#*/
# Get the center point of the object's bounding box
#point = AABB.getCenter()
#// Is the camera facing that point? If not, return false
cameraPlane = ogre.Plane(camera.getDerivedOrientation().zAxis(), camera.getDerivedPosition())
if cameraPlane.getSide(point) != ogre.Plane.NEGATIVE_SIDE:
return False
#// Transform the 3D point into screen space
point = camera.getProjectionMatrix() * (camera.getViewMatrix() * point)
#// Transform from coordinate space [-1, 1] to [0, 1] and update in-value
result.x = (point.x / 2) + 0.5
result.y = 1 - ((point.y / 2) + 0.5)
return True


usage:



# In the App
screenSize = [ self.viewport.actualWidth, self.viewport.actualHeight ]
# You may already have an overlay, but I'll create one here for clarity
self.labelOverlay = ogre.OverlayManager.getSingleton().create("labels_overlay")

self.thisLabel = NodeLabel( self.labelOverlay, "My Name is Al", mySceneNode, screenSize)

...

# In the frameListener

def frameStarted( self, evt ):
...
self.thisLabel.update( self.camera )
...



That should do the trick ;) Also, just modify the NodeLabel class slightly to enable you to change the text at runtime ( it might not even need any modification )

Jzone

27-03-2009 17:25:28

First, I'd like to say thanks for the quick response. Unfortunately I haven't been able to get this code to work properly yet, perhaps you will know what my problem is.

Initially I was getting an error with the ScreenPosition variable in NodeLabel not having x or y attributes. I was able to figure out that this was caused from setting the variable to ogre.Vector2.ZERO. I changed it to ogre.Vector2(0, 0) and the error stopped, but no text was showing up on the screen.

I tried moving the location that the text should appear, changing the size, color, etc. but no matter what I couldn't get it to show. I'm not sure what else to mess with at the moment, but I'll keep playing around with it.

There are currently no errors, and I just dropped your code into my script, placing your sample usage into my frame listener and setup scene methods. (replacing mySceneNode with the node I was using for testing)

The original error trimmed for brevity:
self.thisLabel.update( self.camera )
File "Test.py", line 36, in update
inView = getNodeScreenSpaceCoords( self.obj, camera, self.screenPosition )
File "Test.py", line 101, in getNodeScreenSpaceCoords
result.x = (point.x / 2) + 0.5
AttributeError: 'property' object has no attribute 'x'

bharling

28-03-2009 09:18:53

Hmm, this is rather odd :(

it did work back in python-ogre 1.2, but have just tested it again and indeed its not working for some reason. Have been through and done the basic API changes ie: ogre.Vector3.ZERO needs to be ogre.Vector3().ZERO. but still no label. Something else to try would be to swap the font, as its currently set to use Din which you probably dont have.

Will have a look at it today and see if I can work out whats happened - sorry about that, should have tested before posting ( I wrote that code some time ago now ! )

bharling

28-03-2009 09:26:20

Right, got it working, though something is still amiss if you supply an offset to the label.

Generally though this works on my system:

class NodeLabel:
'''A label on the overlay, that follows a NODE around the screen'''
def __init__(self, overlay, text, node, screenDimensions, offset=ogre.Vector3(0,0,0)):
self.sWidth = screenDimensions[0]
self.sHeight = screenDimensions[1]
elementName = text.replace(" ", "_")
self.panel = ogre.OverlayManager.getSingleton().createOverlayElement("Panel", text.replace(" ", "_"))
#self.panel.materialName = "RedBacker"
self.text = ogre.TextAreaOverlayElement("Label_" + elementName)
self.text.caption = text
self.obj = node
self.screenPosition = ogre.Vector2(0,0)
self.offset = offset
self.panel.metricsMode = ogre.GMM_PIXELS
self.text.metricsMode = ogre.GMM_PIXELS
self.text.setAlignment(ogre.TextAreaOverlayElement.Alignment.Center)
self.text.charHeight = 50
self.text.fontName = "BlueHighway" # "TrebuchetMSBold"
self.text.colourBottom = ogre.ColourValue(1.0, 1.0, 1.0)
self.text.colourTop = ogre.ColourValue(1.0, 1.0, 1.0)
self.panel.setDimensions(160, 70)
self.text.setDimensions(100, 50)
self.text.setPosition(120,10)
# off-screen at first
self.panel.setPosition( 100, 100 )
self.panel.addChild( self.text )
overlay.add2D( self.panel )

def update(self, camera):
inView = getNodeScreenSpaceCoords( self.obj, camera, self.screenPosition )
#print self.screenPosition
if inView:
x = (self.sWidth * self.screenPosition.x) - 125
y = (self.sHeight * self.screenPosition.y) + self.offset.y
self.panel.setPosition( x, y )

def getNodeScreenSpaceCoords( movObj, camera, result ):
if not movObj.inSceneGraph:
return False
point = movObj.getPosition()
cameraPlane = ogre.Plane(camera.getDerivedOrientation().zAxis(), camera.getDerivedPosition())
if cameraPlane.getSide(point) != ogre.Plane.NEGATIVE_SIDE:
return False
#// Transform the 3D point into screen space
point = camera.getProjectionMatrix() * (camera.getViewMatrix() * point)
#// Transform from coordinate space [-1, 1] to [0, 1] and update in-value
result.x = (point.x / 2) + 0.5
result.y = 1 - ((point.y / 2) + 0.5)
return True


App:

self.labelOverlay = ogre.OverlayManager.getSingleton().create("label_overlay")
self.label = NodeLabel(self.labelOverlay, "Hello I'm A Node Label", headNode, [self.viewport.actualWidth, self.viewport.actualHeight])
self.labelOverlay.show()


FrameListener.frameStarted ( you need to pass the self.label to the framelistener so it knows about it, or you could create the label in the framelistener ):

self.label.update( self.camera )

As I said, the positioning does go wrong if you supply an offset, I'll look into that as well as its useful to be able to have the label appear above an object rather than slapped over it.

Jzone

30-03-2009 15:18:26

Thank you very much. This will save me a great deal of time and headache. And for the offset, I'll have a play with it myself and see if I can't get it working. For now though, just having the text attached to the node is a huge step in the right direction.

Also, I noticed this thread while doing some digging around recently and was wondering if there was a python implementation of it (or something similar)? I would like to be able to play around with some 3d text, but I'm not sure if I'm quite up to the task of converting the C++ code to Python yet. Especially if it has already been done.

Thanks again.

bharling

31-03-2009 12:29:15

AFAIK that is not converted to python-ogre yet. It would require to be wrapped as part of the binary package probably, as it uses freeType directly from C++, plus it would probably be very slow in pure python.