ya2 · news · projects · code · about

end frame
[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 self.reload)
105
106 def on_scene_exit(self):
107 self._unset_physics()
108 self._scene.destroy()
109
110 def reload(self, cls):
111 self._fsm.demand('Scene', cls)
112
113 def _configure(self, args):
114 load_prc_file_data('', 'window-title pmachines')
115 load_prc_file_data('', 'framebuffer-srgb true')
116 load_prc_file_data('', 'sync-video true')
117 # load_prc_file_data('', 'threading-model Cull/Draw')
118 # it freezes when you go to the next scene
119 if args.screenshots:
120 load_prc_file_data('', 'window-type offscreen')
121 load_prc_file_data('', 'audio-library-name null')
122
123 def _parse_args(self):
124 parser = argparse.ArgumentParser()
125 parser.add_argument('--update', action='store_true')
126 parser.add_argument('--version', action='store_true')
127 parser.add_argument('--optfile')
128 parser.add_argument('--screenshots', action='store_true')
129 cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
130 args = parser.parse_args(cmd_line)
131 return args
132
133 def _prepare_window(self, args):
134 data_path = ''
135 if (platform.startswith('win') or platform.startswith('linux')) and (
136 not exists('main.py') or __file__.startswith('/app/bin/')):
137 # it is the deployed version for windows
138 data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
139 home = '/home/flavio' # we must force this for wine
140 if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
141 data_path = home + '/.wine/drive_' + data_path[1:]
142 info('creating dirs: %s' % data_path)
143 makedirs(data_path, exist_ok=True)
144 optfile = args.optfile if args.optfile else 'options.ini'
145 info('data path: %s' % data_path)
146 info('option file: %s' % optfile)
147 info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
148 default_opt = {
149 'settings': {
150 'volume': 1,
151 'language': 'en',
152 'fullscreen': 1,
153 'resolution': '',
154 'antialiasing': 1,
155 'shadows': 1},
156 'development': {
157 'simplepbr': 1,
158 'verbose_log': 0,
159 'physics_debug': 0,
160 'auto_start': 0,
161 'auto_close_instructions': 0,
162 'show_buffers': 0,
163 'debug_items': 0,
164 'fps': 0}}
165 opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
166 opt_exists = exists(opt_path)
167 self._options = DctFile(
168 LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
169 default_opt)
170 if not opt_exists:
171 self._options.store()
172 res = self._options['settings']['resolution']
173 if res:
174 res = LVector2i(*[int(_res) for _res in res.split('x')])
175 else:
176 d_i = base.pipe.get_display_information()
177 def _res(idx):
178 return d_i.get_display_mode_width(idx), \
179 d_i.get_display_mode_height(idx)
180 resolutions = [
181 _res(idx) for idx in range(d_i.get_total_display_modes())]
182 res = sorted(resolutions)[-1]
183 props = WindowProperties()
184 props.set_size(res)
185 props.set_fullscreen(self._options['settings']['fullscreen'])
186 props.set_icon_filename('assets/icon/pmachines.ico')
187 if not args.screenshots:
188 base.win.request_properties(props)
189 gltf.patch_loader(base.loader)
190 if self._options['development']['simplepbr']:
191 self._pipeline = simplepbr.init(
192 use_normal_maps=True,
193 use_emission_maps=False,
194 use_occlusion_maps=True,
195 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
196 enable_shadows=int(self._options['settings']['shadows']))
197 debug(f'msaa: {self._pipeline.msaa_samples}')
198 debug(f'shadows: {self._pipeline.enable_shadows}')
199 render.setAntialias(AntialiasAttrib.MAuto)
200 self.base.set_background_color(0, 0, 0, 1)
201 self.base.disable_mouse()
202 if self._options['development']['show_buffers']:
203 base.bufferViewer.toggleEnable()
204 if self._options['development']['fps']:
205 base.set_frame_rate_meter(True)
206 #self.base.accept('window-event', self._on_win_evt)
207 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
208
209 def _set_physics(self):
210 if self._options['development']['physics_debug']:
211 debug_node = BulletDebugNode('Debug')
212 debug_node.show_wireframe(True)
213 debug_node.show_constraints(True)
214 debug_node.show_bounding_boxes(True)
215 debug_node.show_normals(True)
216 self._debug_np = render.attach_new_node(debug_node)
217 self._debug_np.show()
218 self.world = BulletWorld()
219 self.world.set_gravity((0, 0, -9.81))
220 if self._options['development']['physics_debug']:
221 self.world.set_debug_node(self._debug_np.node())
222 def update(task):
223 dt = globalClock.get_dt()
224 self.world.do_physics(dt)
225 return task.cont
226 self._phys_tsk = taskMgr.add(update, 'update')
227
228 def _unset_physics(self):
229 if self._options['development']['physics_debug']:
230 self._debug_np.remove_node()
231 self.world = None
232 taskMgr.remove(self._phys_tsk)
233
234 def _on_aspect_ratio_changed(self):
235 if self._fsm.state == 'Scene':
236 self._scene.on_aspect_ratio_changed()