ya2 · news · projects · code · about

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