ya2 · news · projects · code · about

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