ya2 · news · projects · code · about

scenes already done
[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:
125 self._fsm.demand('Menu')
5964572b
FC
126
127 def on_menu_enter(self):
128 self._menu_bg = Background()
a9aba267
FC
129 self._menu = Menu(
130 self._fsm, self.lang_mgr, self._options, self._music,
2d1773b1
FC
131 self._pipeline, self.scenes, self._args.functional_test or self._args.functional_ref,
132 self._pos_mgr)
5964572b
FC
133
134 def on_home(self):
135 self._fsm.demand('Menu')
136
137 def on_menu_exit(self):
138 self._menu_bg.destroy()
4071c6d8 139 self._menu.destroy()
5964572b 140
8c9bf90e 141 def on_scene_enter(self, cls):
1be87278 142 self._set_physics()
8c9bf90e 143 self._scene = cls(
e669403e 144 self.world, self.on_home,
31237524 145 self._options['development']['auto_close_instructions'],
9914cfc9 146 self._options['development']['debug_items'],
6168d0c2 147 self.reload,
2d1773b1 148 self.scenes,
ce302b41 149 self._pos_mgr,
7fa58640 150 self._args.functional_test or self._args.functional_ref,
92c29685
FC
151 self._options['development']['mouse_coords'],
152 self.__persistent)
5964572b
FC
153
154 def on_scene_exit(self):
155 self._unset_physics()
156 self._scene.destroy()
8ee66edd 157
9914cfc9
FC
158 def reload(self, cls):
159 self._fsm.demand('Scene', cls)
160
d18f757d 161 def _configure(self, args):
8ee66edd 162 load_prc_file_data('', 'window-title pmachines')
4894bb48 163 load_prc_file_data('', 'framebuffer-srgb true')
a9e8696e 164 load_prc_file_data('', 'sync-video true')
d982c0a5 165 if args.functional_test or args.functional_ref:
addec9c9 166 load_prc_file_data('', 'win-size 1360 768')
d982c0a5 167 # otherwise it is not centered in exwm
9914cfc9
FC
168 # load_prc_file_data('', 'threading-model Cull/Draw')
169 # it freezes when you go to the next scene
a747111f 170 if args.screenshot:
d18f757d
FC
171 load_prc_file_data('', 'window-type offscreen')
172 load_prc_file_data('', 'audio-library-name null')
8ee66edd
FC
173
174 def _parse_args(self):
175 parser = argparse.ArgumentParser()
176 parser.add_argument('--update', action='store_true')
177 parser.add_argument('--version', action='store_true')
9ba5488b 178 parser.add_argument('--optfile')
a747111f 179 parser.add_argument('--screenshot')
361d3942 180 parser.add_argument('--functional-test', action='store_true')
edeef6f9 181 parser.add_argument('--functional-ref', action='store_true')
d6c157a0 182 cmd_line = [arg for arg in iter(sys.argv[1:]) if not arg.startswith('-psn_')]
8ee66edd
FC
183 args = parser.parse_args(cmd_line)
184 return args
185
9ba5488b 186 def _prepare_window(self, args):
8ee66edd
FC
187 data_path = ''
188 if (platform.startswith('win') or platform.startswith('linux')) and (
189 not exists('main.py') or __file__.startswith('/app/bin/')):
190 # it is the deployed version for windows
191 data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
192 home = '/home/flavio' # we must force this for wine
193 if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
194 data_path = home + '/.wine/drive_' + data_path[1:]
195 info('creating dirs: %s' % data_path)
196 makedirs(data_path, exist_ok=True)
9ba5488b
FC
197 optfile = args.optfile if args.optfile else 'options.ini'
198 info('data path: %s' % data_path)
199 info('option file: %s' % optfile)
200 info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
201 default_opt = {
202 'settings': {
a0b33e12 203 'volume': 1,
6fff1464
FC
204 'language': 'en',
205 'fullscreen': 1,
a9aba267 206 'resolution': '',
5fdf77d0
FC
207 'antialiasing': 1,
208 'shadows': 1},
92c29685
FC
209 'save': {
210 'scenes_done': []
211 },
9ba5488b 212 'development': {
a5dc83f4 213 'simplepbr': 1,
9ba5488b 214 'verbose_log': 0,
e669403e
FC
215 'physics_debug': 0,
216 'auto_start': 0,
217 'auto_close_instructions': 0,
31237524 218 'show_buffers': 0,
b41381b2 219 'debug_items': 0,
882c058d 220 'mouse_coords': 0,
b41381b2 221 'fps': 0}}
9ba5488b
FC
222 opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
223 opt_exists = exists(opt_path)
224 self._options = DctFile(
225 LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
226 default_opt)
227 if not opt_exists:
228 self._options.store()
92c29685
FC
229 self.__persistent = Persistent(self._options['save']['scenes_done'], self._options)
230 Scene.scenes_done = self.__persistent.scenes_done
6fff1464
FC
231 res = self._options['settings']['resolution']
232 if res:
233 res = LVector2i(*[int(_res) for _res in res.split('x')])
234 else:
dd32d640 235 resolutions = []
aad6ced2 236 if not self.is_version_run:
dd32d640
FC
237 d_i = base.pipe.get_display_information()
238 def _res(idx):
239 return d_i.get_display_mode_width(idx), \
240 d_i.get_display_mode_height(idx)
241 resolutions = [
242 _res(idx) for idx in range(d_i.get_total_display_modes())]
6e6ca4ad 243 res = sorted(resolutions)[-1]
edeef6f9 244 fullscreen = self._options['settings']['fullscreen']
d982c0a5 245 props = WindowProperties()
edeef6f9 246 if args.functional_test or args.functional_ref:
edeef6f9 247 fullscreen = False
aad6ced2 248 elif not self.is_version_run:
d982c0a5 249 props.set_size(res)
edeef6f9 250 props.set_fullscreen(fullscreen)
7e487769 251 props.set_icon_filename('assets/images/icon/pmachines.ico')
66b856f5 252 if not args.screenshot and not self.is_version_run and base.win:
d18f757d 253 base.win.request_properties(props)
420ce99a 254 #gltf.patch_loader(base.loader)
66b856f5 255 if self._options['development']['simplepbr'] and not self.is_version_run and base.win:
a9aba267 256 self._pipeline = simplepbr.init(
a5dc83f4
FC
257 use_normal_maps=True,
258 use_emission_maps=False,
a9aba267 259 use_occlusion_maps=True,
5fdf77d0
FC
260 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
261 enable_shadows=int(self._options['settings']['shadows']))
94a18c21
FC
262 debug(f'msaa: {self._pipeline.msaa_samples}')
263 debug(f'shadows: {self._pipeline.enable_shadows}')
4894bb48 264 render.setAntialias(AntialiasAttrib.MAuto)
1be87278
FC
265 self.base.set_background_color(0, 0, 0, 1)
266 self.base.disable_mouse()
e669403e
FC
267 if self._options['development']['show_buffers']:
268 base.bufferViewer.toggleEnable()
b41381b2
FC
269 if self._options['development']['fps']:
270 base.set_frame_rate_meter(True)
651713a9
FC
271 #self.base.accept('window-event', self._on_win_evt)
272 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
882c058d
FC
273 if self._options['development']['mouse_coords']:
274 coords_txt = OnscreenText(
275 '', parent=base.a2dTopRight, scale=0.04,
276 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
277 def update_coords(task):
278 txt = '%s %s' % (int(base.win.get_pointer(0).x),
279 int(base.win.get_pointer(0).y))
280 coords_txt['text'] = txt
281 return task.cont
282 taskMgr.add(update_coords, 'update_coords')
283
1be87278 284 def _set_physics(self):
9ba5488b
FC
285 if self._options['development']['physics_debug']:
286 debug_node = BulletDebugNode('Debug')
287 debug_node.show_wireframe(True)
288 debug_node.show_constraints(True)
289 debug_node.show_bounding_boxes(True)
290 debug_node.show_normals(True)
5964572b
FC
291 self._debug_np = render.attach_new_node(debug_node)
292 self._debug_np.show()
1be87278
FC
293 self.world = BulletWorld()
294 self.world.set_gravity((0, 0, -9.81))
9ba5488b 295 if self._options['development']['physics_debug']:
5964572b 296 self.world.set_debug_node(self._debug_np.node())
1be87278
FC
297 def update(task):
298 dt = globalClock.get_dt()
0625cf49 299 self.world.do_physics(dt, 10, 1/180)
1be87278 300 return task.cont
5964572b
FC
301 self._phys_tsk = taskMgr.add(update, 'update')
302
303 def _unset_physics(self):
304 if self._options['development']['physics_debug']:
305 self._debug_np.remove_node()
306 self.world = None
307 taskMgr.remove(self._phys_tsk)
651713a9
FC
308
309 def _on_aspect_ratio_changed(self):
5964572b
FC
310 if self._fsm.state == 'Scene':
311 self._scene.on_aspect_ratio_changed()
2ef21fc3
FC
312
313 def __assert_fps(self, task):
314 if len(self.__fps_lst) > 3:
315 self.__fps_lst.pop(0)
316 self.__fps_lst += [globalClock.average_frame_rate]
317 if len(self.__fps_lst) == 4:
df4f85fc 318 fps_threshold = 55 if cpu_count() >= 4 else 25
c991401b 319 assert any(fps > fps_threshold for fps in self.__fps_lst), 'low fps %s' % self.__fps_lst
2ef21fc3 320 return task.again
aad6ced2
FC
321
322 def run(self):
d6c157a0 323 self.start()
aad6ced2 324 self.base.run()