Filed in: Resources.ProgrammingCodeSnipplets3d-textbubble · Modified on : Fri, 21 Aug 09
makes a billboarded (allways facing the camera) text window, with borders and background.
changes:
06.07.2009: cleaned up a bit, now 2d and 3d(billboarded) classes, complex and simple versions.

{{:codesnipplets:panda3d:codesnipplets:panda3d:3d-textbubble:textbubble.png|}}
without alignment and window callbacks
from pandac.PandaModules import TextNode, CardMaker, Vec3, Vec4, NodePath
# size and border of the textnode
BORDER_SIZE = 1.5
CARD_BACKGROUND_SIZE = Vec4(0.1, 0.2, 0.5, 0.1)
CARD_FRAME_SIZE = Vec4(0.1, 0.1, 0.1, 0.1)
# colors of the border, background and text
BORDER_COLOR = Vec4(0.3, 0.3, 0.3, 1.0)
BACKGROUND_COLOR = Vec4(1.0, 1.0, 1.0, 0.7)
FONT_COLOR = Vec4(0.0, 0.0, 0.0, 1.0)
class InfoText(NodePath):
def __init__(self, parent):
self.parent = parent
NodePath.__init__(self, 'InfoTextNodePath')
self.reparentTo(self.parent)
# create the textnode
self.textNode = TextNode('InfoTextNode')
self.textNode.setTextColor(FONT_COLOR)
self.textNodePath = NodePath.attachNewNode(self, self.textNode)
# background of the textnode
self.textNode.setCardColor(BACKGROUND_COLOR)
self.textNode.setCardDecal(1) # required if in 3d space (z-fighting)
self.textNode.setCardAsMargin(*CARD_BACKGROUND_SIZE) # left, right, bottom, top
# frame of the textnode
self.textNode.setFrameAsMargin(*(CARD_BACKGROUND_SIZE+CARD_FRAME_SIZE))
self.textNode.setFrameLineWidth(BORDER_SIZE)
self.textNode.setFrameColor(BORDER_COLOR)
self.setText('default\ntext')
def setText(self, text):
self.textNode.setText(text)
class InfoTextBillaboarded(InfoText):
def __init__(self, parent):
# make the text viewing the camera
self.billboardNodePath = parent.attachNewNode('InfoTextBillaboardedNode')
self.billboardNodePath.setBillboardPointEye()
#self.billboardNodePath.setZ(0.1)
#self.billboardNodePath.setScale(.025)
InfoText.__init__(self, self.billboardNodePath)
def setText(self, text):
InfoText.setText(self, text)
# move the center of rotation to the lower left corner
# this is not entirely correct, the x-coordinate is a bit too small
frameSize = self.textNode.getFrameActual()
pos = Vec3(frameSize.getX(),0,-frameSize.getZ()) # * NodePath.getScale(self, self.parent)
self.textNodePath.setPos(self, pos)
if __name__ == '__main__':
print "I: infoText2.__main__: Example Usage, zoom out to see everything"
from direct.directbase import DirectStart
mdl = loader.loadModel('misc/sphere.egg')
mdl.reparentTo(render)
mdl.setScale(0.02)
a = InfoText(aspect2d)
a.setScale(0.05)
a.setText('2d attached\nInfoText')
b = InfoTextBillaboarded(render)
b.setScale(0.05)
b.setText('3d billaboarded\nInfoTextBillaboarded')
run()
with alignment and window callbacks
from pandac.PandaModules import TextNode, CardMaker, Vec3, Vec4, NodePath
from direct.showbase.DirectObject import DirectObject
# size and border of the textnode
BORDER_SIZE = 1.5
CARD_BACKGROUND_SIZE = Vec4(0.1, 0.2, 0.5, 0.1)
CARD_FRAME_SIZE = Vec4(0.1, 0.1, 0.1, 0.1)
# colors of the border, background and text
BORDER_COLOR = Vec4(0.3, 0.3, 0.3, 1.0)
BACKGROUND_COLOR = Vec4(1.0, 1.0, 1.0, 0.7)
FONT_COLOR = Vec4(0.0, 0.0, 0.0, 1.0)
# vertical alignment
INFOTEXT_FRAME_VALIGN_TOP = 1
INFOTEXT_FRAME_VALIGN_CENTER = .5
INFOTEXT_FRAME_VALIGN_BOTTOM = 0
INFOTEXT_FRAME_HALIGN_LEFT = 0
INFOTEXT_FRAME_HALIGN_CENTER = 0.5
INFOTEXT_FRAME_HALIGN_RIGHT = 1
INFOTEXT_WINDOW_VALIGN_TOP = 1
INFOTEXT_WINDOW_VALIGN_CENTER = 0
INFOTEXT_WINDOW_VALIGN_BOTTOM = -1
INFOTEXT_WINDOW_HALIGN_LEFT = -1
INFOTEXT_WINDOW_HALIGN_CENTER = 0
INFOTEXT_WINDOW_HALIGN_RIGHT = 1
class InfoText(NodePath, DirectObject):
def __init__(self, parent):
self.parent = parent
self.verticalFrameAlign = INFOTEXT_FRAME_VALIGN_TOP
self.horizontalFrameAlign = INFOTEXT_FRAME_HALIGN_LEFT
NodePath.__init__(self, 'InfoTextNodePath')
self.reparentTo(self.parent)
# create the textnode
self.textNode = TextNode('InfoTextNode')
self.textNode.setTextColor(FONT_COLOR)
self.textNodePath = NodePath.attachNewNode(self, self.textNode)
# background of the textnode
self.textNode.setCardColor(BACKGROUND_COLOR)
self.textNode.setCardDecal(1) # required if in 3d space (z-fighting)
self.textNode.setCardAsMargin(*CARD_BACKGROUND_SIZE) # left, right, bottom, top
# frame of the textnode
self.textNode.setFrameAsMargin(*(CARD_BACKGROUND_SIZE+CARD_FRAME_SIZE))
self.textNode.setFrameLineWidth(BORDER_SIZE)
self.textNode.setFrameColor(BORDER_COLOR)
self.verticalWindowAlign = INFOTEXT_WINDOW_VALIGN_BOTTOM
self.horizontalWindowAlign = INFOTEXT_WINDOW_HALIGN_LEFT
DirectObject.__init__(self)
self.accept('window-event', self.repositionText)
self.setText('default\ntext')
def setText(self, text):
self.textNode.setText(text)
self.repositionText()
def repositionText(self, window=None):
if window is None:
# requires panda3d to be started
window = base.win
# this calculates the frame position relative to the window
wp = window.getProperties()
aspect = wp.getXSize() / float(wp.getYSize())
if aspect > 1.0:
windowWidth = wp.getXSize() / float(wp.getYSize()) * self.horizontalWindowAlign
windowHeight = 1.0 * self.verticalWindowAlign
else:
windowWidth = 1.0 * self.horizontalWindowAlign
windowHeight = wp.getYSize() / float(wp.getXSize()) * self.verticalWindowAlign
windowPos = self.textNodePath.getRelativeVector(self.parent, Vec3(windowWidth, 0, windowHeight))
# this calculates the frame position relative to itself's size
frameSize = self.textNode.getFrameActual()
pos = Vec3(-frameSize.getX(),0,-frameSize.getZ())
framePos = Vec3( (frameSize.getY()-frameSize.getX()) * self.horizontalFrameAlign,
0,
(frameSize.getW()-frameSize.getZ()) * self.verticalFrameAlign)
self.textNodePath.setPos(self, pos-framePos+windowPos)
def setFrameVAlign(self, alignment):
self.verticalFrameAlign = alignment
self.repositionText()
def setFrameHAlign(self, alignment):
self.horizontalFrameAlign = alignment
self.repositionText()
def setWindowVAlign(self, alignment):
self.verticalWindowAlign = alignment
self.repositionText()
def setWindowHAlign(self, alignment):
self.horizontalWindowAlign = alignment
self.repositionText()
class InfoTextBillboarded(InfoText):
def __init__(self, parent):
# make the text viewing the camera
self.billboardNodePath = parent.attachNewNode('InfoTextBillboardedNode')
self.billboardNodePath.setBillboardPointEye()
InfoText.__init__(self, self.billboardNodePath)
def setText(self, text):
InfoText.setText(self, text)
if __name__ == '__main__':
print "I: infoText2.__main__: Example Usage, zoom out to see everything"
from direct.directbase import DirectStart
mdl = loader.loadModel('misc/sphere.egg')
mdl.reparentTo(render)
mdl.setScale(0.02)
a = InfoText(aspect2d)
a.setScale(0.05)
a.setFrameHAlign(INFOTEXT_FRAME_HALIGN_LEFT)
a.setFrameVAlign(INFOTEXT_FRAME_VALIGN_TOP)
a.setWindowHAlign(INFOTEXT_WINDOW_HALIGN_LEFT)
a.setWindowVAlign(INFOTEXT_WINDOW_VALIGN_TOP)
a.setText('2d attached\nInfoText\nframe aligned LT\nwindow aligned LT')
b = InfoText(aspect2d)
b.setScale(0.05)
b.setFrameHAlign(INFOTEXT_FRAME_HALIGN_RIGHT)
b.setFrameVAlign(INFOTEXT_FRAME_VALIGN_TOP)
b.setWindowHAlign(INFOTEXT_WINDOW_HALIGN_RIGHT)
b.setWindowVAlign(INFOTEXT_WINDOW_VALIGN_TOP)
b.setText('2d attached\nInfoText\naligned RT\nwindow aligned RT')
c = InfoText(aspect2d)
c.setScale(0.05)
c.setFrameHAlign(INFOTEXT_FRAME_HALIGN_RIGHT)
c.setFrameVAlign(INFOTEXT_FRAME_VALIGN_BOTTOM)
c.setWindowHAlign(INFOTEXT_WINDOW_HALIGN_RIGHT)
c.setWindowVAlign(INFOTEXT_WINDOW_VALIGN_BOTTOM)
c.setText('2d attached\nInfoText\naligned RB\nwindow aligned RB')
d = InfoText(aspect2d)
d.setScale(0.05)
d.setFrameHAlign(INFOTEXT_FRAME_HALIGN_LEFT)
d.setFrameVAlign(INFOTEXT_FRAME_VALIGN_BOTTOM)
d.setWindowHAlign(INFOTEXT_WINDOW_HALIGN_LEFT)
d.setWindowVAlign(INFOTEXT_WINDOW_VALIGN_BOTTOM)
d.setText('2d attached\nInfoText\naligned LB\nwindow aligned LB')
e = InfoTextBillboarded(render)
e.setScale(.5)
e.setFrameHAlign(INFOTEXT_FRAME_HALIGN_RIGHT)
e.setFrameVAlign(INFOTEXT_FRAME_VALIGN_BOTTOM)
e.setText('3d billboarded\nInfoTextBillboarded')
run()