ya2 · news · projects · code · about

framework for multiple scenes
[pmachines.git] / pmachines / app.py
1 import argparse
2 import simplepbr
3 import gltf
4 from sys import platform, argv
5 from logging import info, debug
6 from os.path import exists
7 from os import makedirs
8 from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
9 Texture, WindowProperties, LVector2i
10 from panda3d.bullet import BulletWorld, BulletDebugNode
11 from direct.showbase.ShowBase import ShowBase
12 from direct.fsm.FSM import FSM
13 from pmachines.music import MusicMgr
14 from pmachines.items.background import Background
15 from pmachines.menu import Menu
16 from lib.dictfile import DctFile
17 from lib.lib.p3d.p3d import LibP3d
18 from lib.engine.lang import LangMgr
19
20
21 class MainFsm(FSM):
22
23 def __init__(self, pmachines):
24 super().__init__('Main FSM')
25 self._pmachines = pmachines
26
27 def enterMenu(self):
28 self._pmachines.on_menu_enter()
29
30 def exitMenu(self):
31 self._pmachines.on_menu_exit()
32
33 def enterScene(self, cls):
34 self._pmachines.on_scene_enter(cls)
35
36 def exitScene(self):
37 self._pmachines.on_scene_exit()
38
39
40 class PmachinesApp:
41
42 def __init__(self):
43 self._configure()
44 self.base = ShowBase()
45 info('platform: %s' % platform)
46 info('exists main.py: %s' % exists('main.py'))
47 args = self._parse_args()
48 self._prepare_window(args)
49 self.updating = args.update
50 self.version = args.version
51 if args.update:
52 return
53 self._music = MusicMgr(self._options['settings']['volume'])
54 self.lang_mgr = LangMgr(self._options['settings']['language'],
55 'pmachines',
56 'assets/locale/')
57 self._fsm = MainFsm(self)
58 tgt_state = 'Scene' if self._options['development']['auto_start'] else 'Menu'
59 self._fsm.demand(tgt_state)
60
61 def on_menu_enter(self):
62 self._menu_bg = Background()
63 self._menu = Menu(
64 self._fsm, self.lang_mgr, self._options, self._music,
65 self._pipeline)
66
67 def on_home(self):
68 self._fsm.demand('Menu')
69
70 def on_menu_exit(self):
71 self._menu_bg.destroy()
72 self._menu.destroy()
73
74 def on_scene_enter(self, cls):
75 self._set_physics()
76 self._scene = cls(
77 self.world, self.on_home,
78 self._options['development']['auto_close_instructions'],
79 self._options['development']['debug_items'])
80
81 def on_scene_exit(self):
82 self._unset_physics()
83 self._scene.destroy()
84
85 def _configure(self):
86 load_prc_file_data('', 'window-title pmachines')
87 load_prc_file_data('', 'framebuffer-srgb true')
88 load_prc_file_data('', 'sync-video true')
89 load_prc_file_data('', 'threading-model Cull/Draw')
90
91 def _parse_args(self):
92 parser = argparse.ArgumentParser()
93 parser.add_argument('--update', action='store_true')
94 parser.add_argument('--version', action='store_true')
95 parser.add_argument('--optfile')
96 cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
97 args = parser.parse_args(cmd_line)
98 return args
99
100 def _prepare_window(self, args):
101 data_path = ''
102 if (platform.startswith('win') or platform.startswith('linux')) and (
103 not exists('main.py') or __file__.startswith('/app/bin/')):
104 # it is the deployed version for windows
105 data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
106 home = '/home/flavio' # we must force this for wine
107 if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
108 data_path = home + '/.wine/drive_' + data_path[1:]
109 info('creating dirs: %s' % data_path)
110 makedirs(data_path, exist_ok=True)
111 optfile = args.optfile if args.optfile else 'options.ini'
112 info('data path: %s' % data_path)
113 info('option file: %s' % optfile)
114 info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
115 default_opt = {
116 'settings': {
117 'volume': 1,
118 'language': 'en',
119 'fullscreen': 1,
120 'resolution': '',
121 'antialiasing': 1,
122 'shadows': 1},
123 'development': {
124 'simplepbr': 1,
125 'verbose_log': 0,
126 'physics_debug': 0,
127 'auto_start': 0,
128 'auto_close_instructions': 0,
129 'show_buffers': 0,
130 'debug_items': 0,
131 'fps': 0}}
132 opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
133 opt_exists = exists(opt_path)
134 self._options = DctFile(
135 LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
136 default_opt)
137 if not opt_exists:
138 self._options.store()
139 res = self._options['settings']['resolution']
140 if res:
141 res = LVector2i(*[int(_res) for _res in res.split('x')])
142 else:
143 d_i = base.pipe.get_display_information()
144 def _res(idx):
145 return d_i.get_display_mode_width(idx), \
146 d_i.get_display_mode_height(idx)
147 resolutions = [
148 _res(idx) for idx in range(d_i.get_total_display_modes())]
149 res = sorted(resolutions)[-1]
150 props = WindowProperties()
151 props.set_size(res)
152 props.set_fullscreen(self._options['settings']['fullscreen'])
153 props.set_icon_filename('assets/icon/pmachines.ico')
154 base.win.request_properties(props)
155 gltf.patch_loader(base.loader)
156 if self._options['development']['simplepbr']:
157 self._pipeline = simplepbr.init(
158 use_normal_maps=True,
159 use_emission_maps=False,
160 use_occlusion_maps=True,
161 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
162 enable_shadows=int(self._options['settings']['shadows']))
163 debug(f'msaa: {self._pipeline.msaa_samples}')
164 debug(f'shadows: {self._pipeline.enable_shadows}')
165 render.setAntialias(AntialiasAttrib.MAuto)
166 self.base.set_background_color(0, 0, 0, 1)
167 self.base.disable_mouse()
168 if self._options['development']['show_buffers']:
169 base.bufferViewer.toggleEnable()
170 if self._options['development']['fps']:
171 base.set_frame_rate_meter(True)
172 #self.base.accept('window-event', self._on_win_evt)
173 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
174
175 def _set_physics(self):
176 if self._options['development']['physics_debug']:
177 debug_node = BulletDebugNode('Debug')
178 debug_node.show_wireframe(True)
179 debug_node.show_constraints(True)
180 debug_node.show_bounding_boxes(True)
181 debug_node.show_normals(True)
182 self._debug_np = render.attach_new_node(debug_node)
183 self._debug_np.show()
184 self.world = BulletWorld()
185 self.world.set_gravity((0, 0, -9.81))
186 if self._options['development']['physics_debug']:
187 self.world.set_debug_node(self._debug_np.node())
188 def update(task):
189 dt = globalClock.get_dt()
190 self.world.do_physics(dt)
191 return task.cont
192 self._phys_tsk = taskMgr.add(update, 'update')
193
194 def _unset_physics(self):
195 if self._options['development']['physics_debug']:
196 self._debug_np.remove_node()
197 self.world = None
198 taskMgr.remove(self._phys_tsk)
199
200 def _on_aspect_ratio_changed(self):
201 if self._fsm.state == 'Scene':
202 self._scene.on_aspect_ratio_changed()