411 lines
13 KiB
Python
411 lines
13 KiB
Python
|
"""
|
||
|
Show how to use libRocket in Panda3D.
|
||
|
"""
|
||
|
import sys
|
||
|
from panda3d.core import loadPrcFile, loadPrcFileData, Point3,Vec4, Mat4, LoaderOptions # @UnusedImport
|
||
|
from panda3d.core import DirectionalLight, AmbientLight, PointLight
|
||
|
from panda3d.core import Texture, PNMImage
|
||
|
from panda3d.core import PandaSystem
|
||
|
import random
|
||
|
from direct.interval.LerpInterval import LerpHprInterval, LerpPosInterval, LerpFunc
|
||
|
from direct.showbase.ShowBase import ShowBase
|
||
|
|
||
|
# workaround: https://www.panda3d.org/forums/viewtopic.php?t=10062&p=99697#p99054
|
||
|
#from panda3d import rocket
|
||
|
import _rocketcore as rocket
|
||
|
|
||
|
from panda3d.rocket import RocketRegion, RocketInputHandler
|
||
|
|
||
|
loadPrcFileData("", "model-path $MAIN_DIR/assets")
|
||
|
|
||
|
import console
|
||
|
|
||
|
global globalClock
|
||
|
|
||
|
class MyApp(ShowBase):
|
||
|
|
||
|
def __init__(self):
|
||
|
ShowBase.__init__(self)
|
||
|
|
||
|
self.win.setClearColor(Vec4(0.2, 0.2, 0.2, 1))
|
||
|
|
||
|
self.disableMouse()
|
||
|
|
||
|
self.render.setShaderAuto()
|
||
|
|
||
|
dlight = DirectionalLight('dlight')
|
||
|
alight = AmbientLight('alight')
|
||
|
dlnp = self.render.attachNewNode(dlight)
|
||
|
alnp = self.render.attachNewNode(alight)
|
||
|
dlight.setColor((0.8, 0.8, 0.5, 1))
|
||
|
alight.setColor((0.2, 0.2, 0.2, 1))
|
||
|
dlnp.setHpr(0, -60, 0)
|
||
|
self.render.setLight(dlnp)
|
||
|
self.render.setLight(alnp)
|
||
|
|
||
|
# Put lighting on the main scene
|
||
|
plight = PointLight('plight')
|
||
|
plnp = self.render.attachNewNode(plight)
|
||
|
plnp.setPos(0, 0, 10)
|
||
|
self.render.setLight(plnp)
|
||
|
self.render.setLight(alnp)
|
||
|
|
||
|
self.loadRocketFonts()
|
||
|
|
||
|
self.loadingTask = None
|
||
|
|
||
|
#self.startModelLoadingAsync()
|
||
|
self.startModelLoading()
|
||
|
|
||
|
self.inputHandler = RocketInputHandler()
|
||
|
self.mouseWatcher.attachNewNode(self.inputHandler)
|
||
|
|
||
|
self.openLoadingDialog()
|
||
|
|
||
|
def loadRocketFonts(self):
|
||
|
""" Load fonts referenced from e.g. 'font-family' RCSS directives.
|
||
|
|
||
|
Note: the name of the font as used in 'font-family'
|
||
|
is not always the same as the filename;
|
||
|
open the font in your OS to see its display name.
|
||
|
"""
|
||
|
rocket.LoadFontFace("modenine.ttf")
|
||
|
|
||
|
|
||
|
def startModelLoading(self):
|
||
|
self.monitorNP = None
|
||
|
self.keyboardNP = None
|
||
|
self.loadingError = False
|
||
|
|
||
|
self.taskMgr.doMethodLater(1, self.loadModels, 'loadModels')
|
||
|
|
||
|
def loadModels(self, task):
|
||
|
self.monitorNP = self.loader.loadModel("monitor")
|
||
|
self.keyboardNP = self.loader.loadModel("takeyga_kb")
|
||
|
|
||
|
def startModelLoadingAsync(self):
|
||
|
"""
|
||
|
NOTE: this seems to invoke a few bugs (crashes, sporadic model
|
||
|
reading errors, etc) so is disabled for now...
|
||
|
"""
|
||
|
self.monitorNP = None
|
||
|
self.keyboardNP = None
|
||
|
self.loadingError = False
|
||
|
|
||
|
# force the "loading" to take some time after the first run...
|
||
|
options = LoaderOptions()
|
||
|
options.setFlags(options.getFlags() | LoaderOptions.LFNoCache)
|
||
|
|
||
|
def gotMonitorModel(model):
|
||
|
if not model:
|
||
|
self.loadingError = True
|
||
|
self.monitorNP = model
|
||
|
|
||
|
self.loader.loadModel("monitor", loaderOptions=options, callback=gotMonitorModel)
|
||
|
|
||
|
def gotKeyboardModel(model):
|
||
|
if not model:
|
||
|
self.loadingError = True
|
||
|
self.keyboardNP = model
|
||
|
|
||
|
self.loader.loadModel("takeyga_kb", loaderOptions=options, callback=gotKeyboardModel)
|
||
|
|
||
|
def openLoadingDialog(self):
|
||
|
self.userConfirmed = False
|
||
|
|
||
|
self.windowRocketRegion = RocketRegion.make('pandaRocket', self.win)
|
||
|
self.windowRocketRegion.setActive(1)
|
||
|
|
||
|
self.windowRocketRegion.setInputHandler(self.inputHandler)
|
||
|
|
||
|
self.windowContext = self.windowRocketRegion.getContext()
|
||
|
|
||
|
self.loadingDocument = self.windowContext.LoadDocument("loading.rml")
|
||
|
if not self.loadingDocument:
|
||
|
raise AssertionError("did not find loading.rml")
|
||
|
|
||
|
self.loadingDots = 0
|
||
|
el = self.loadingDocument.GetElementById('loadingLabel')
|
||
|
self.loadingText = el.first_child
|
||
|
self.stopLoadingTime = globalClock.getFrameTime() + 3
|
||
|
self.loadingTask = self.taskMgr.add(self.cycleLoading, 'doc changer')
|
||
|
|
||
|
|
||
|
# note: you may encounter errors like 'KeyError: 'document'"
|
||
|
# when invoking events using methods from your own scripts with this
|
||
|
# obvious code:
|
||
|
#
|
||
|
# self.loadingDocument.AddEventListener('aboutToClose',
|
||
|
# self.onLoadingDialogDismissed, True)
|
||
|
#
|
||
|
# A workaround is to define callback methods in standalone Python
|
||
|
# files with event, self, and document defined to None.
|
||
|
#
|
||
|
# see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
|
||
|
#
|
||
|
|
||
|
# Or, use this indirection technique to work around the problem,
|
||
|
# by publishing the app into the context, then accessing it through
|
||
|
# the document's context...
|
||
|
|
||
|
self.windowContext.app = self
|
||
|
self.loadingDocument.AddEventListener('aboutToClose',
|
||
|
'document.context.app.handleAboutToClose()', True)
|
||
|
|
||
|
self.loadingDocument.Show()
|
||
|
|
||
|
def handleAboutToClose(self):
|
||
|
self.userConfirmed = True
|
||
|
if self.monitorNP and self.keyboardNP:
|
||
|
self.onLoadingDialogDismissed()
|
||
|
|
||
|
def attachCustomRocketEvent(self, document, rocketEventName, pandaHandler, once=False):
|
||
|
# handle custom event
|
||
|
|
||
|
# note: you may encounter errors like 'KeyError: 'document'"
|
||
|
# when invoking events using methods from your own scripts with this
|
||
|
# obvious code:
|
||
|
#
|
||
|
# self.loadingDocument.AddEventListener('aboutToClose',
|
||
|
# self.onLoadingDialogDismissed, True)
|
||
|
#
|
||
|
# see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
|
||
|
|
||
|
|
||
|
# this technique converts Rocket events to Panda3D events
|
||
|
|
||
|
pandaEvent = 'panda.' + rocketEventName
|
||
|
|
||
|
document.AddEventListener(
|
||
|
rocketEventName,
|
||
|
"messenger.send('" + pandaEvent + "', [event])")
|
||
|
|
||
|
if once:
|
||
|
self.acceptOnce(pandaEvent, pandaHandler)
|
||
|
else:
|
||
|
self.accept(pandaEvent, pandaHandler)
|
||
|
|
||
|
|
||
|
def cycleLoading(self, task):
|
||
|
"""
|
||
|
Update the "loading" text in the initial window until
|
||
|
the user presses Space, Enter, or Escape or clicks (see loading.rxml)
|
||
|
or sufficient time has elapsed (self.stopLoadingTime).
|
||
|
"""
|
||
|
text = self.loadingText
|
||
|
|
||
|
now = globalClock.getFrameTime()
|
||
|
if self.monitorNP and self.keyboardNP:
|
||
|
text.text = "Ready"
|
||
|
if now > self.stopLoadingTime or self.userConfirmed:
|
||
|
self.onLoadingDialogDismissed()
|
||
|
return task.done
|
||
|
elif self.loadingError:
|
||
|
text.text = "Assets not found"
|
||
|
else:
|
||
|
count = 5
|
||
|
intv = int(now * 4) % count # @UndefinedVariable
|
||
|
text.text = "Loading" + ("." * (1+intv)) + (" " * (2 - intv))
|
||
|
|
||
|
return task.cont
|
||
|
|
||
|
def onLoadingDialogDismissed(self):
|
||
|
""" Once a models are loaded, stop 'loading' and proceed to 'start' """
|
||
|
if self.loadingDocument:
|
||
|
if self.loadingTask:
|
||
|
self.taskMgr.remove(self.loadingTask)
|
||
|
self.loadingTask = None
|
||
|
|
||
|
self.showStarting()
|
||
|
|
||
|
def fadeOut(self, element, time):
|
||
|
""" Example updating RCSS attributes from code
|
||
|
by modifying the 'color' RCSS attribute to slowly
|
||
|
change from solid to transparent.
|
||
|
|
||
|
element: the Rocket element whose style to modify
|
||
|
time: time in seconds for fadeout
|
||
|
"""
|
||
|
|
||
|
# get the current color from RCSS effective style
|
||
|
color = element.style.color
|
||
|
# convert to RGBA form
|
||
|
prefix = color[:color.rindex(',')+1].replace('rgb(', 'rgba(')
|
||
|
|
||
|
def updateAlpha(t):
|
||
|
# another way of setting style on a specific element
|
||
|
attr = 'color: ' + prefix + str(int(t)) +');'
|
||
|
element.SetAttribute('style', attr)
|
||
|
|
||
|
alphaInterval = LerpFunc(updateAlpha,
|
||
|
duration=time,
|
||
|
fromData=255,
|
||
|
toData=0,
|
||
|
blendType='easeIn')
|
||
|
|
||
|
return alphaInterval
|
||
|
|
||
|
def showStarting(self):
|
||
|
""" Models are loaded, so update the dialog,
|
||
|
fade out, then transition to the console. """
|
||
|
self.loadingText.text = 'Starting...'
|
||
|
|
||
|
alphaInterval = self.fadeOut(self.loadingText, 0.5)
|
||
|
alphaInterval.setDoneEvent('fadeOutFinished')
|
||
|
|
||
|
def fadeOutFinished():
|
||
|
if self.loadingDocument:
|
||
|
self.loadingDocument.Close()
|
||
|
self.loadingDocument = None
|
||
|
self.createConsole()
|
||
|
|
||
|
self.accept('fadeOutFinished', fadeOutFinished)
|
||
|
|
||
|
alphaInterval.start()
|
||
|
|
||
|
def createConsole(self):
|
||
|
""" Create the in-world console, which displays
|
||
|
a RocketRegion in a GraphicsBuffer, which appears
|
||
|
in a Texture on the monitor model. """
|
||
|
|
||
|
self.monitorNP.reparentTo(self.render)
|
||
|
self.monitorNP.setScale(1.5)
|
||
|
|
||
|
self.keyboardNP.reparentTo(self.render)
|
||
|
self.keyboardNP.setHpr(-90, 0, 15)
|
||
|
self.keyboardNP.setScale(20)
|
||
|
|
||
|
self.placeItems()
|
||
|
|
||
|
self.setupRocketConsole()
|
||
|
|
||
|
# re-enable mouse
|
||
|
mat=Mat4(self.camera.getMat())
|
||
|
mat.invertInPlace()
|
||
|
self.mouseInterfaceNode.setMat(mat)
|
||
|
self.enableMouse()
|
||
|
|
||
|
def placeItems(self):
|
||
|
self.camera.setPos(0, -20, 0)
|
||
|
self.camera.setHpr(0, 0, 0)
|
||
|
self.monitorNP.setPos(0, 0, 1)
|
||
|
self.keyboardNP.setPos(0, -5, -2.5)
|
||
|
|
||
|
|
||
|
def setupRocketConsole(self):
|
||
|
"""
|
||
|
Place a new rocket window onto a texture
|
||
|
bound to the front of the monitor.
|
||
|
"""
|
||
|
self.win.setClearColor(Vec4(0.5, 0.5, 0.8, 1))
|
||
|
|
||
|
faceplate = self.monitorNP.find("**/Faceplate")
|
||
|
assert faceplate
|
||
|
|
||
|
mybuffer = self.win.makeTextureBuffer("Console Buffer", 1024, 512)
|
||
|
tex = mybuffer.getTexture()
|
||
|
tex.setMagfilter(Texture.FTLinear)
|
||
|
tex.setMinfilter(Texture.FTLinear)
|
||
|
|
||
|
faceplate.setTexture(tex, 1)
|
||
|
|
||
|
self.rocketConsole = RocketRegion.make('console', mybuffer)
|
||
|
self.rocketConsole.setInputHandler(self.inputHandler)
|
||
|
|
||
|
self.consoleContext = self.rocketConsole.getContext()
|
||
|
self.console = console.Console(self, self.consoleContext, 40, 13, self.handleCommand)
|
||
|
|
||
|
self.console.addLine("Panda DOS")
|
||
|
self.console.addLine("type 'help'")
|
||
|
self.console.addLine("")
|
||
|
|
||
|
self.console.allowEditing(True)
|
||
|
|
||
|
def handleCommand(self, command):
|
||
|
if command is None:
|
||
|
# hack for Ctrl-Break
|
||
|
self.spewInProgress = False
|
||
|
self.console.addLine("*** break ***")
|
||
|
self.console.allowEditing(True)
|
||
|
return
|
||
|
|
||
|
command = command.strip()
|
||
|
if not command:
|
||
|
return
|
||
|
|
||
|
tokens = [x.strip() for x in command.split(' ')]
|
||
|
command = tokens[0].lower()
|
||
|
|
||
|
if command == 'help':
|
||
|
self.console.addLines([
|
||
|
"Sorry, this is utter fakery.",
|
||
|
"You won't get much more",
|
||
|
"out of this simulation unless",
|
||
|
"you program it yourself. :)"
|
||
|
])
|
||
|
elif command == 'dir':
|
||
|
self.console.addLines([
|
||
|
"Directory of C:\\:",
|
||
|
"HELP COM 72 05-06-2015 14:07",
|
||
|
"DIR COM 121 05-06-2015 14:11",
|
||
|
"SPEW COM 666 05-06-2015 15:02",
|
||
|
" 2 Files(s) 859 Bytes.",
|
||
|
" 0 Dirs(s) 7333 Bytes free.",
|
||
|
""])
|
||
|
elif command == 'cls':
|
||
|
self.console.cls()
|
||
|
elif command == 'echo':
|
||
|
self.console.addLine(' '.join(tokens[1:]))
|
||
|
elif command == 'ver':
|
||
|
self.console.addLine('Panda DOS v0.01 in Panda3D ' + PandaSystem.getVersionString())
|
||
|
elif command == 'spew':
|
||
|
self.startSpew()
|
||
|
elif command == 'exit':
|
||
|
self.console.setPrompt("System is shutting down NOW!")
|
||
|
self.terminateMonitor()
|
||
|
else:
|
||
|
self.console.addLine("command not found")
|
||
|
|
||
|
def startSpew(self):
|
||
|
self.console.allowEditing(False)
|
||
|
self.console.addLine("LINE NOISE 1.0")
|
||
|
self.console.addLine("")
|
||
|
|
||
|
self.spewInProgress = True
|
||
|
|
||
|
# note: spewage always occurs in 'doMethodLater';
|
||
|
# time.sleep() would be pointless since the whole
|
||
|
# UI would be frozen during the wait.
|
||
|
self.queueSpew(2)
|
||
|
|
||
|
def queueSpew(self, delay=0.1):
|
||
|
self.taskMgr.doMethodLater(delay, self.spew, 'spew')
|
||
|
|
||
|
def spew(self, task):
|
||
|
# generate random spewage, just like on TV!
|
||
|
if not self.spewInProgress:
|
||
|
return
|
||
|
|
||
|
def randchr():
|
||
|
return chr(int(random.random() < 0.25 and 32 or random.randint(32, 127)))
|
||
|
|
||
|
line = ''.join([randchr() for _ in range(40) ])
|
||
|
|
||
|
self.console.addLine(line)
|
||
|
self.queueSpew()
|
||
|
|
||
|
def terminateMonitor(self):
|
||
|
alphaInterval = self.fadeOut(self.console.getTextContainer(), 2)
|
||
|
|
||
|
alphaInterval.setDoneEvent('fadeOutFinished')
|
||
|
|
||
|
def fadeOutFinished():
|
||
|
sys.exit(0)
|
||
|
|
||
|
self.accept('fadeOutFinished', fadeOutFinished)
|
||
|
|
||
|
alphaInterval.start()
|
||
|
|
||
|
app = MyApp()
|
||
|
app.run()
|