ya2 · news · projects · code · about

fixed bug (menu buttons)
[pmachines.git] / pmachines / app.py
CommitLineData
8ee66edd 1import argparse
4894bb48 2import simplepbr
420ce99a 3#import gltf
a2a89363
FC
4from importlib import import_module
5from inspect import isclass
d6c157a0 6from sys import platform, exit
94a18c21 7from logging import info, debug
8ee66edd
FC
8from os.path import exists
9from os import makedirs
df4f85fc 10from multiprocessing import cpu_count
6fff1464 11from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
c991401b 12 WindowProperties, LVector2i, TextNode
1be87278 13from panda3d.bullet import BulletWorld, BulletDebugNode
8ee66edd 14from direct.showbase.ShowBase import ShowBase
882c058d 15from direct.gui.OnscreenText import OnscreenText
5964572b 16from direct.fsm.FSM import FSM
4586cbf6
FC
17from pmachines.audio.music import MusicMgr
18from pmachines.items.background import Background
19from pmachines.gui.menu import Menu
20from pmachines.scene import Scene
21from pmachines.scenes.scene_basketball import SceneBasketBall
22from pmachines.scenes.scene_box import SceneBox
23from pmachines.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
24from pmachines.scenes.scene_domino_box import SceneDominoBox
25from pmachines.scenes.scene_domino import SceneDomino
26from pmachines.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
27from pmachines.scenes.scene_teeter_tooter import SceneTeeterTooter
28from pmachines.posmgr import PositionMgr
92c29685 29from pmachines.persistent import Persistent
b35b1f62
FC
30from ya2.utils.dictfile import DctFile
31from ya2.p3d.p3d import LibP3d
32from ya2.utils.lang import LangMgr
33from ya2.utils.log import LogMgr
34from ya2.utils.functional import FunctionalTest
1bfdf72a
FC
35from ya2.p3d.asserts import assert_threads, assert_tasks, assert_render3d, \
36 assert_render2d, assert_aspect2d, assert_events, assert_buffers
d6c157a0 37import sys
8ee66edd
FC
38
39
5964572b
FC
40class MainFsm(FSM):
41
42 def __init__(self, pmachines):
43 super().__init__('Main FSM')
44 self._pmachines = pmachines
45
46 def enterMenu(self):
47 self._pmachines.on_menu_enter()
48
49 def exitMenu(self):
50 self._pmachines.on_menu_exit()
1bfdf72a 51 self.__do_asserts()
5964572b 52
8c9bf90e
FC
53 def enterScene(self, cls):
54 self._pmachines.on_scene_enter(cls)
5964572b
FC
55
56 def exitScene(self):
57 self._pmachines.on_scene_exit()
1bfdf72a
FC
58 self.__do_asserts()
59
60 def __do_asserts(self):
61 args = self._pmachines._args
46c16ae5 62 if not LibP3d.runtime() or args.functional_test or args.functional_ref:
1bfdf72a
FC
63 assert_threads()
64 assert_tasks()
65 assert_render3d()
66 assert_render2d()
67 assert_aspect2d()
68 assert_events()
69 assert_buffers()
5964572b
FC
70
71
aad6ced2 72class Pmachines:
8ee66edd 73
6168d0c2
FC
74 scenes = [
75 SceneDomino,
76 SceneBox,
77 SceneDominoBox,
78 SceneBasketBall,
79 SceneDominoBoxBasketball,
80 SceneTeeterTooter,
81 SceneTeeterDominoBoxBasketball]
82
8ee66edd 83 def __init__(self):
8ee66edd
FC
84 info('platform: %s' % platform)
85 info('exists main.py: %s' % exists('main.py'))
e982cdde 86 self._args = args = self._parse_args()
d18f757d
FC
87 self._configure(args)
88 self.base = ShowBase()
8ce16d6c 89 self._pipeline = None
aad6ced2
FC
90 self.is_update_run = args.update
91 self.is_version_run = args.version
aed9737a 92 self.log_mgr = LogMgr.init_cls()()
2d1773b1 93 self._pos_mgr = PositionMgr()
8ce16d6c 94 self._prepare_window(args)
8ee66edd
FC
95 if args.update:
96 return
361d3942 97 if args.functional_test:
edeef6f9 98 self._options['settings']['volume'] = 0
e1e44d5c 99 self._music = MusicMgr(self._options['settings']['volume'])
a0b33e12 100 self.lang_mgr = LangMgr(self._options['settings']['language'],
2aaa10d3
FC
101 'pmachines',
102 'assets/locale/')
5964572b 103 self._fsm = MainFsm(self)
d6c157a0
FC
104 if args.functional_test or args.functional_ref:
105 FunctionalTest(args.functional_ref, self._pos_mgr)
106 if not LibP3d.runtime() or args.functional_test or args.functional_ref:
107 self.__fps_lst = []
108 taskMgr.do_method_later(1.0, self.__assert_fps, 'assert_fps')
109
110 def start(self):
111 if self._args.screenshot:
112 cls = [cls for cls in self.scenes if cls.__name__ == self._args.screenshot][0]
11d03d6a 113 scene = cls(BulletWorld(), None, True, False, lambda: None, self.scenes, self._pos_mgr, None, None)
a747111f
FC
114 scene.screenshot()
115 scene.destroy()
63e7aeb2
FC
116 exit()
117 elif self._options['development']['auto_start']:
4586cbf6 118 mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
a2a89363
FC
119 for member in import_module(mod_name).__dict__.values():
120 if isclass(member) and issubclass(member, Scene) and \
121 member != Scene:
122 cls = member
123 self._fsm.demand('Scene', cls)
124 else:
107a889e 125 Scene.scenes_done = self.__persistent.scenes_done
a2a89363 126 self._fsm.demand('Menu')
5964572b
FC
127
128 def on_menu_enter(self):
129 self._menu_bg = Background()
a9aba267
FC
130 self._menu = Menu(
131 self._fsm, self.lang_mgr, self._options, self._music,
2d1773b1
FC
132 self._pipeline, self.scenes, self._args.functional_test or self._args.functional_ref,
133 self._pos_mgr)
5964572b
FC
134
135 def on_home(self):
107a889e 136 Scene.scenes_done = self.__persistent.scenes_done
5964572b
FC
137 self._fsm.demand('Menu')
138
139 def on_menu_exit(self):
140 self._menu_bg.destroy()
4071c6d8 141 self._menu.destroy()
5964572b 142
8c9bf90e 143 def on_scene_enter(self, cls):
1be87278 144 self._set_physics()
8c9bf90e 145 self._scene = cls(
e669403e 146 self.world, self.on_home,
31237524 147 self._options['development']['auto_close_instructions'],
9914cfc9 148 self._options['development']['debug_items'],
6168d0c2 149 self.reload,
2d1773b1 150 self.scenes,
ce302b41 151 self._pos_mgr,
7fa58640 152 self._args.functional_test or self._args.functional_ref,
92c29685
FC
153 self._options['development']['mouse_coords'],
154 self.__persistent)
5964572b
FC
155
156 def on_scene_exit(self):
157 self._unset_physics()
158 self._scene.destroy()
8ee66edd 159
9914cfc9
FC
160 def reload(self, cls):
161 self._fsm.demand('Scene', cls)
162
d18f757d 163 def _configure(self, args):
8ee66edd 164 load_prc_file_data('', 'window-title pmachines')
4894bb48 165 load_prc_file_data('', 'framebuffer-srgb true')
a9e8696e 166 load_prc_file_data('', 'sync-video true')
d982c0a5 167 if args.functional_test or args.functional_ref:
addec9c9 168 load_prc_file_data('', 'win-size 1360 768')
d982c0a5 169 # otherwise it is not centered in exwm
9914cfc9
FC
170 # load_prc_file_data('', 'threading-model Cull/Draw')
171 # it freezes when you go to the next scene
a747111f 172 if args.screenshot:
d18f757d
FC
173 load_prc_file_data('', 'window-type offscreen')
174 load_prc_file_data('', 'audio-library-name null')
8ee66edd
FC
175
176 def _parse_args(self):
177 parser = argparse.ArgumentParser()
178 parser.add_argument('--update', action='store_true')
179 parser.add_argument('--version', action='store_true')
9ba5488b 180 parser.add_argument('--optfile')
a747111f 181 parser.add_argument('--screenshot')
361d3942 182 parser.add_argument('--functional-test', action='store_true')
edeef6f9 183 parser.add_argument('--functional-ref', action='store_true')
d6c157a0 184 cmd_line = [arg for arg in iter(sys.argv[1:]) if not arg.startswith('-psn_')]
8ee66edd
FC
185 args = parser.parse_args(cmd_line)
186 return args
187
9ba5488b 188 def _prepare_window(self, args):
8ee66edd
FC
189 data_path = ''
190 if (platform.startswith('win') or platform.startswith('linux')) and (
191 not exists('main.py') or __file__.startswith('/app/bin/')):
192 # it is the deployed version for windows
193 data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
194 home = '/home/flavio' # we must force this for wine
195 if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
196 data_path = home + '/.wine/drive_' + data_path[1:]
197 info('creating dirs: %s' % data_path)
198 makedirs(data_path, exist_ok=True)
9ba5488b
FC
199 optfile = args.optfile if args.optfile else 'options.ini'
200 info('data path: %s' % data_path)
201 info('option file: %s' % optfile)
202 info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
203 default_opt = {
204 'settings': {
a0b33e12 205 'volume': 1,
6fff1464
FC
206 'language': 'en',
207 'fullscreen': 1,
a9aba267 208 'resolution': '',
5fdf77d0
FC
209 'antialiasing': 1,
210 'shadows': 1},
92c29685
FC
211 'save': {
212 'scenes_done': []
213 },
9ba5488b 214 'development': {
a5dc83f4 215 'simplepbr': 1,
9ba5488b 216 'verbose_log': 0,
e669403e
FC
217 'physics_debug': 0,
218 'auto_start': 0,
219 'auto_close_instructions': 0,
31237524 220 'show_buffers': 0,
b41381b2 221 'debug_items': 0,
882c058d 222 'mouse_coords': 0,
b41381b2 223 'fps': 0}}
9ba5488b
FC
224 opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
225 opt_exists = exists(opt_path)
226 self._options = DctFile(
227 LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
228 default_opt)
229 if not opt_exists:
230 self._options.store()
92c29685
FC
231 self.__persistent = Persistent(self._options['save']['scenes_done'], self._options)
232 Scene.scenes_done = self.__persistent.scenes_done
6fff1464
FC
233 res = self._options['settings']['resolution']
234 if res:
235 res = LVector2i(*[int(_res) for _res in res.split('x')])
236 else:
dd32d640 237 resolutions = []
aad6ced2 238 if not self.is_version_run:
dd32d640
FC
239 d_i = base.pipe.get_display_information()
240 def _res(idx):
241 return d_i.get_display_mode_width(idx), \
242 d_i.get_display_mode_height(idx)
243 resolutions = [
244 _res(idx) for idx in range(d_i.get_total_display_modes())]
6e6ca4ad 245 res = sorted(resolutions)[-1]
edeef6f9 246 fullscreen = self._options['settings']['fullscreen']
d982c0a5 247 props = WindowProperties()
edeef6f9 248 if args.functional_test or args.functional_ref:
edeef6f9 249 fullscreen = False
aad6ced2 250 elif not self.is_version_run:
d982c0a5 251 props.set_size(res)
edeef6f9 252 props.set_fullscreen(fullscreen)
7e487769 253 props.set_icon_filename('assets/images/icon/pmachines.ico')
66b856f5 254 if not args.screenshot and not self.is_version_run and base.win:
d18f757d 255 base.win.request_properties(props)
420ce99a 256 #gltf.patch_loader(base.loader)
66b856f5 257 if self._options['development']['simplepbr'] and not self.is_version_run and base.win:
a9aba267 258 self._pipeline = simplepbr.init(
a5dc83f4
FC
259 use_normal_maps=True,
260 use_emission_maps=False,
a9aba267 261 use_occlusion_maps=True,
5fdf77d0
FC
262 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
263 enable_shadows=int(self._options['settings']['shadows']))
94a18c21
FC
264 debug(f'msaa: {self._pipeline.msaa_samples}')
265 debug(f'shadows: {self._pipeline.enable_shadows}')
4894bb48 266 render.setAntialias(AntialiasAttrib.MAuto)
1be87278
FC
267 self.base.set_background_color(0, 0, 0, 1)
268 self.base.disable_mouse()
e669403e
FC
269 if self._options['development']['show_buffers']:
270 base.bufferViewer.toggleEnable()
b41381b2
FC
271 if self._options['development']['fps']:
272 base.set_frame_rate_meter(True)
651713a9
FC
273 #self.base.accept('window-event', self._on_win_evt)
274 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
882c058d
FC
275 if self._options['development']['mouse_coords']:
276 coords_txt = OnscreenText(
277 '', parent=base.a2dTopRight, scale=0.04,
278 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
279 def update_coords(task):
280 txt = '%s %s' % (int(base.win.get_pointer(0).x),
281 int(base.win.get_pointer(0).y))
282 coords_txt['text'] = txt
283 return task.cont
284 taskMgr.add(update_coords, 'update_coords')
285
1be87278 286 def _set_physics(self):
9ba5488b
FC
287 if self._options['development']['physics_debug']:
288 debug_node = BulletDebugNode('Debug')
289 debug_node.show_wireframe(True)
290 debug_node.show_constraints(True)
291 debug_node.show_bounding_boxes(True)
292 debug_node.show_normals(True)
5964572b
FC
293 self._debug_np = render.attach_new_node(debug_node)
294 self._debug_np.show()
1be87278
FC
295 self.world = BulletWorld()
296 self.world.set_gravity((0, 0, -9.81))
9ba5488b 297 if self._options['development']['physics_debug']:
5964572b 298 self.world.set_debug_node(self._debug_np.node())
1be87278
FC
299 def update(task):
300 dt = globalClock.get_dt()
0625cf49 301 self.world.do_physics(dt, 10, 1/180)
1be87278 302 return task.cont
5964572b
FC
303 self._phys_tsk = taskMgr.add(update, 'update')
304
305 def _unset_physics(self):
306 if self._options['development']['physics_debug']:
307 self._debug_np.remove_node()
308 self.world = None
309 taskMgr.remove(self._phys_tsk)
651713a9
FC
310
311 def _on_aspect_ratio_changed(self):
5964572b
FC
312 if self._fsm.state == 'Scene':
313 self._scene.on_aspect_ratio_changed()
2ef21fc3
FC
314
315 def __assert_fps(self, task):
316 if len(self.__fps_lst) > 3:
317 self.__fps_lst.pop(0)
318 self.__fps_lst += [globalClock.average_frame_rate]
319 if len(self.__fps_lst) == 4:
df4f85fc 320 fps_threshold = 55 if cpu_count() >= 4 else 25
c991401b 321 assert any(fps > fps_threshold for fps in self.__fps_lst), 'low fps %s' % self.__fps_lst
2ef21fc3 322 return task.again
aad6ced2
FC
323
324 def run(self):
d6c157a0 325 self.start()
aad6ced2 326 self.base.run()