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