main.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. """
  2. Show how to use libRocket in Panda3D.
  3. """
  4. import sys
  5. from panda3d.core import loadPrcFile, loadPrcFileData, Point3,Vec4, Mat4, LoaderOptions # @UnusedImport
  6. from panda3d.core import DirectionalLight, AmbientLight, PointLight
  7. from panda3d.core import Texture, PNMImage
  8. from panda3d.core import PandaSystem
  9. import random
  10. from direct.interval.LerpInterval import LerpHprInterval, LerpPosInterval, LerpFunc
  11. from direct.showbase.ShowBase import ShowBase
  12. # workaround: https://www.panda3d.org/forums/viewtopic.php?t=10062&p=99697#p99054
  13. #from panda3d import rocket
  14. import _rocketcore as rocket
  15. from panda3d.rocket import RocketRegion, RocketInputHandler
  16. loadPrcFileData("", "model-path $MAIN_DIR/assets")
  17. import console
  18. global globalClock
  19. class MyApp(ShowBase):
  20. def __init__(self):
  21. ShowBase.__init__(self)
  22. self.win.setClearColor(Vec4(0.2, 0.2, 0.2, 1))
  23. self.disableMouse()
  24. self.render.setShaderAuto()
  25. dlight = DirectionalLight('dlight')
  26. alight = AmbientLight('alight')
  27. dlnp = self.render.attachNewNode(dlight)
  28. alnp = self.render.attachNewNode(alight)
  29. dlight.setColor((0.8, 0.8, 0.5, 1))
  30. alight.setColor((0.2, 0.2, 0.2, 1))
  31. dlnp.setHpr(0, -60, 0)
  32. self.render.setLight(dlnp)
  33. self.render.setLight(alnp)
  34. # Put lighting on the main scene
  35. plight = PointLight('plight')
  36. plnp = self.render.attachNewNode(plight)
  37. plnp.setPos(0, 0, 10)
  38. self.render.setLight(plnp)
  39. self.render.setLight(alnp)
  40. self.loadRocketFonts()
  41. self.loadingTask = None
  42. #self.startModelLoadingAsync()
  43. self.startModelLoading()
  44. self.inputHandler = RocketInputHandler()
  45. self.mouseWatcher.attachNewNode(self.inputHandler)
  46. self.openLoadingDialog()
  47. def loadRocketFonts(self):
  48. """ Load fonts referenced from e.g. 'font-family' RCSS directives.
  49. Note: the name of the font as used in 'font-family'
  50. is not always the same as the filename;
  51. open the font in your OS to see its display name.
  52. """
  53. rocket.LoadFontFace("modenine.ttf")
  54. def startModelLoading(self):
  55. self.monitorNP = None
  56. self.keyboardNP = None
  57. self.loadingError = False
  58. self.taskMgr.doMethodLater(1, self.loadModels, 'loadModels')
  59. def loadModels(self, task):
  60. self.monitorNP = self.loader.loadModel("monitor")
  61. self.keyboardNP = self.loader.loadModel("takeyga_kb")
  62. def startModelLoadingAsync(self):
  63. """
  64. NOTE: this seems to invoke a few bugs (crashes, sporadic model
  65. reading errors, etc) so is disabled for now...
  66. """
  67. self.monitorNP = None
  68. self.keyboardNP = None
  69. self.loadingError = False
  70. # force the "loading" to take some time after the first run...
  71. options = LoaderOptions()
  72. options.setFlags(options.getFlags() | LoaderOptions.LFNoCache)
  73. def gotMonitorModel(model):
  74. if not model:
  75. self.loadingError = True
  76. self.monitorNP = model
  77. self.loader.loadModel("monitor", loaderOptions=options, callback=gotMonitorModel)
  78. def gotKeyboardModel(model):
  79. if not model:
  80. self.loadingError = True
  81. self.keyboardNP = model
  82. self.loader.loadModel("takeyga_kb", loaderOptions=options, callback=gotKeyboardModel)
  83. def openLoadingDialog(self):
  84. self.userConfirmed = False
  85. self.windowRocketRegion = RocketRegion.make('pandaRocket', self.win)
  86. self.windowRocketRegion.setActive(1)
  87. self.windowRocketRegion.setInputHandler(self.inputHandler)
  88. self.windowContext = self.windowRocketRegion.getContext()
  89. self.loadingDocument = self.windowContext.LoadDocument("loading.rml")
  90. if not self.loadingDocument:
  91. raise AssertionError("did not find loading.rml")
  92. self.loadingDots = 0
  93. el = self.loadingDocument.GetElementById('loadingLabel')
  94. self.loadingText = el.first_child
  95. self.stopLoadingTime = globalClock.getFrameTime() + 3
  96. self.loadingTask = self.taskMgr.add(self.cycleLoading, 'doc changer')
  97. # note: you may encounter errors like 'KeyError: 'document'"
  98. # when invoking events using methods from your own scripts with this
  99. # obvious code:
  100. #
  101. # self.loadingDocument.AddEventListener('aboutToClose',
  102. # self.onLoadingDialogDismissed, True)
  103. #
  104. # A workaround is to define callback methods in standalone Python
  105. # files with event, self, and document defined to None.
  106. #
  107. # see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
  108. #
  109. # Or, use this indirection technique to work around the problem,
  110. # by publishing the app into the context, then accessing it through
  111. # the document's context...
  112. self.windowContext.app = self
  113. self.loadingDocument.AddEventListener('aboutToClose',
  114. 'document.context.app.handleAboutToClose()', True)
  115. self.loadingDocument.Show()
  116. def handleAboutToClose(self):
  117. self.userConfirmed = True
  118. if self.monitorNP and self.keyboardNP:
  119. self.onLoadingDialogDismissed()
  120. def attachCustomRocketEvent(self, document, rocketEventName, pandaHandler, once=False):
  121. # handle custom event
  122. # note: you may encounter errors like 'KeyError: 'document'"
  123. # when invoking events using methods from your own scripts with this
  124. # obvious code:
  125. #
  126. # self.loadingDocument.AddEventListener('aboutToClose',
  127. # self.onLoadingDialogDismissed, True)
  128. #
  129. # see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
  130. # this technique converts Rocket events to Panda3D events
  131. pandaEvent = 'panda.' + rocketEventName
  132. document.AddEventListener(
  133. rocketEventName,
  134. "messenger.send('" + pandaEvent + "', [event])")
  135. if once:
  136. self.acceptOnce(pandaEvent, pandaHandler)
  137. else:
  138. self.accept(pandaEvent, pandaHandler)
  139. def cycleLoading(self, task):
  140. """
  141. Update the "loading" text in the initial window until
  142. the user presses Space, Enter, or Escape or clicks (see loading.rxml)
  143. or sufficient time has elapsed (self.stopLoadingTime).
  144. """
  145. text = self.loadingText
  146. now = globalClock.getFrameTime()
  147. if self.monitorNP and self.keyboardNP:
  148. text.text = "Ready"
  149. if now > self.stopLoadingTime or self.userConfirmed:
  150. self.onLoadingDialogDismissed()
  151. return task.done
  152. elif self.loadingError:
  153. text.text = "Assets not found"
  154. else:
  155. count = 5
  156. intv = int(now * 4) % count # @UndefinedVariable
  157. text.text = "Loading" + ("." * (1+intv)) + (" " * (2 - intv))
  158. return task.cont
  159. def onLoadingDialogDismissed(self):
  160. """ Once a models are loaded, stop 'loading' and proceed to 'start' """
  161. if self.loadingDocument:
  162. if self.loadingTask:
  163. self.taskMgr.remove(self.loadingTask)
  164. self.loadingTask = None
  165. self.showStarting()
  166. def fadeOut(self, element, time):
  167. """ Example updating RCSS attributes from code
  168. by modifying the 'color' RCSS attribute to slowly
  169. change from solid to transparent.
  170. element: the Rocket element whose style to modify
  171. time: time in seconds for fadeout
  172. """
  173. # get the current color from RCSS effective style
  174. color = element.style.color
  175. # convert to RGBA form
  176. prefix = color[:color.rindex(',')+1].replace('rgb(', 'rgba(')
  177. def updateAlpha(t):
  178. # another way of setting style on a specific element
  179. attr = 'color: ' + prefix + str(int(t)) +');'
  180. element.SetAttribute('style', attr)
  181. alphaInterval = LerpFunc(updateAlpha,
  182. duration=time,
  183. fromData=255,
  184. toData=0,
  185. blendType='easeIn')
  186. return alphaInterval
  187. def showStarting(self):
  188. """ Models are loaded, so update the dialog,
  189. fade out, then transition to the console. """
  190. self.loadingText.text = 'Starting...'
  191. alphaInterval = self.fadeOut(self.loadingText, 0.5)
  192. alphaInterval.setDoneEvent('fadeOutFinished')
  193. def fadeOutFinished():
  194. if self.loadingDocument:
  195. self.loadingDocument.Close()
  196. self.loadingDocument = None
  197. self.createConsole()
  198. self.accept('fadeOutFinished', fadeOutFinished)
  199. alphaInterval.start()
  200. def createConsole(self):
  201. """ Create the in-world console, which displays
  202. a RocketRegion in a GraphicsBuffer, which appears
  203. in a Texture on the monitor model. """
  204. self.monitorNP.reparentTo(self.render)
  205. self.monitorNP.setScale(1.5)
  206. self.keyboardNP.reparentTo(self.render)
  207. self.keyboardNP.setHpr(-90, 0, 15)
  208. self.keyboardNP.setScale(20)
  209. self.placeItems()
  210. self.setupRocketConsole()
  211. # re-enable mouse
  212. mat=Mat4(self.camera.getMat())
  213. mat.invertInPlace()
  214. self.mouseInterfaceNode.setMat(mat)
  215. self.enableMouse()
  216. def placeItems(self):
  217. self.camera.setPos(0, -20, 0)
  218. self.camera.setHpr(0, 0, 0)
  219. self.monitorNP.setPos(0, 0, 1)
  220. self.keyboardNP.setPos(0, -5, -2.5)
  221. def setupRocketConsole(self):
  222. """
  223. Place a new rocket window onto a texture
  224. bound to the front of the monitor.
  225. """
  226. self.win.setClearColor(Vec4(0.5, 0.5, 0.8, 1))
  227. faceplate = self.monitorNP.find("**/Faceplate")
  228. assert faceplate
  229. mybuffer = self.win.makeTextureBuffer("Console Buffer", 1024, 512)
  230. tex = mybuffer.getTexture()
  231. tex.setMagfilter(Texture.FTLinear)
  232. tex.setMinfilter(Texture.FTLinear)
  233. faceplate.setTexture(tex, 1)
  234. self.rocketConsole = RocketRegion.make('console', mybuffer)
  235. self.rocketConsole.setInputHandler(self.inputHandler)
  236. self.consoleContext = self.rocketConsole.getContext()
  237. self.console = console.Console(self, self.consoleContext, 40, 13, self.handleCommand)
  238. self.console.addLine("Panda DOS")
  239. self.console.addLine("type 'help'")
  240. self.console.addLine("")
  241. self.console.allowEditing(True)
  242. def handleCommand(self, command):
  243. if command is None:
  244. # hack for Ctrl-Break
  245. self.spewInProgress = False
  246. self.console.addLine("*** break ***")
  247. self.console.allowEditing(True)
  248. return
  249. command = command.strip()
  250. if not command:
  251. return
  252. tokens = [x.strip() for x in command.split(' ')]
  253. command = tokens[0].lower()
  254. if command == 'help':
  255. self.console.addLines([
  256. "Sorry, this is utter fakery.",
  257. "You won't get much more",
  258. "out of this simulation unless",
  259. "you program it yourself. :)"
  260. ])
  261. elif command == 'dir':
  262. self.console.addLines([
  263. "Directory of C:\\:",
  264. "HELP COM 72 05-06-2015 14:07",
  265. "DIR COM 121 05-06-2015 14:11",
  266. "SPEW COM 666 05-06-2015 15:02",
  267. " 2 Files(s) 859 Bytes.",
  268. " 0 Dirs(s) 7333 Bytes free.",
  269. ""])
  270. elif command == 'cls':
  271. self.console.cls()
  272. elif command == 'echo':
  273. self.console.addLine(' '.join(tokens[1:]))
  274. elif command == 'ver':
  275. self.console.addLine('Panda DOS v0.01 in Panda3D ' + PandaSystem.getVersionString())
  276. elif command == 'spew':
  277. self.startSpew()
  278. elif command == 'exit':
  279. self.console.setPrompt("System is shutting down NOW!")
  280. self.terminateMonitor()
  281. else:
  282. self.console.addLine("command not found")
  283. def startSpew(self):
  284. self.console.allowEditing(False)
  285. self.console.addLine("LINE NOISE 1.0")
  286. self.console.addLine("")
  287. self.spewInProgress = True
  288. # note: spewage always occurs in 'doMethodLater';
  289. # time.sleep() would be pointless since the whole
  290. # UI would be frozen during the wait.
  291. self.queueSpew(2)
  292. def queueSpew(self, delay=0.1):
  293. self.taskMgr.doMethodLater(delay, self.spew, 'spew')
  294. def spew(self, task):
  295. # generate random spewage, just like on TV!
  296. if not self.spewInProgress:
  297. return
  298. def randchr():
  299. return chr(int(random.random() < 0.25 and 32 or random.randint(32, 127)))
  300. line = ''.join([randchr() for _ in range(40) ])
  301. self.console.addLine(line)
  302. self.queueSpew()
  303. def terminateMonitor(self):
  304. alphaInterval = self.fadeOut(self.console.getTextContainer(), 2)
  305. alphaInterval.setDoneEvent('fadeOutFinished')
  306. def fadeOutFinished():
  307. sys.exit(0)
  308. self.accept('fadeOutFinished', fadeOutFinished)
  309. alphaInterval.start()
  310. app = MyApp()
  311. app.run()