ya2 · news · projects · code · about

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