ya2 · news · projects · code · about

removed GameObject
[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, TextNode
13 from panda3d.bullet import BulletWorld, BulletDebugNode
14 from direct.showbase.ShowBase import ShowBase
15 from direct.gui.OnscreenText import OnscreenText
16 from direct.fsm.FSM import FSM
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
21 from pmachines.scenes.scene_basketball import SceneBasketBall
22 from pmachines.scenes.scene_box import SceneBox
23 from pmachines.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
24 from pmachines.scenes.scene_domino_box import SceneDominoBox
25 from pmachines.scenes.scene_domino import SceneDomino
26 from pmachines.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
27 from pmachines.scenes.scene_teeter_tooter import SceneTeeterTooter
28 from pmachines.posmgr import PositionMgr
29 from ya2.utils.dictfile import DctFile
30 from ya2.p3d.p3d import LibP3d
31 from ya2.utils.lang import LangMgr
32 from ya2.utils.log import LogMgr
33 from ya2.utils.functional import FunctionalTest
34
35
36 class 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
48 def enterScene(self, cls):
49 self._pmachines.on_scene_enter(cls)
50
51 def exitScene(self):
52 self._pmachines.on_scene_exit()
53
54
55 class PmachinesApp:
56
57 scenes = [
58 SceneDomino,
59 SceneBox,
60 SceneDominoBox,
61 SceneBasketBall,
62 SceneDominoBoxBasketball,
63 SceneTeeterTooter,
64 SceneTeeterDominoBoxBasketball]
65
66 def __init__(self):
67 info('platform: %s' % platform)
68 info('exists main.py: %s' % exists('main.py'))
69 self._args = args = self._parse_args()
70 self._configure(args)
71 self.base = ShowBase()
72 self._pipeline = None
73 self.updating = args.update
74 self.version = args.version
75 self.log_mgr = LogMgr.init_cls()()
76 self._pos_mgr = PositionMgr()
77 self._prepare_window(args)
78 if args.update:
79 return
80 if args.functional_test:
81 self._options['settings']['volume'] = 0
82 self._music = MusicMgr(self._options['settings']['volume'])
83 self.lang_mgr = LangMgr(self._options['settings']['language'],
84 'pmachines',
85 'assets/locale/')
86 self._fsm = MainFsm(self)
87 if args.screenshot:
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)
90 scene.screenshot()
91 scene.destroy()
92 exit()
93 elif self._options['development']['auto_start']:
94 mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
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')
102 if args.functional_test or args.functional_ref:
103 FunctionalTest(args.functional_ref, self._pos_mgr)
104
105 def on_menu_enter(self):
106 self._menu_bg = Background()
107 self._menu = Menu(
108 self._fsm, self.lang_mgr, self._options, self._music,
109 self._pipeline, self.scenes, self._args.functional_test or self._args.functional_ref,
110 self._pos_mgr)
111
112 def on_home(self):
113 self._fsm.demand('Menu')
114
115 def on_menu_exit(self):
116 self._menu_bg.destroy()
117 self._menu.destroy()
118
119 def on_scene_enter(self, cls):
120 self._set_physics()
121 self._scene = cls(
122 self.world, self.on_home,
123 self._options['development']['auto_close_instructions'],
124 self._options['development']['debug_items'],
125 self.reload,
126 self.scenes,
127 self._pos_mgr,
128 self._args.functional_test or self._args.functional_ref)
129
130 def on_scene_exit(self):
131 self._unset_physics()
132 self._scene.destroy()
133
134 def reload(self, cls):
135 self._fsm.demand('Scene', cls)
136
137 def _configure(self, args):
138 load_prc_file_data('', 'window-title pmachines')
139 load_prc_file_data('', 'framebuffer-srgb true')
140 load_prc_file_data('', 'sync-video true')
141 if args.functional_test or args.functional_ref:
142 load_prc_file_data('', 'win-size 1360 768')
143 # otherwise it is not centered in exwm
144 # load_prc_file_data('', 'threading-model Cull/Draw')
145 # it freezes when you go to the next scene
146 if args.screenshot:
147 load_prc_file_data('', 'window-type offscreen')
148 load_prc_file_data('', 'audio-library-name null')
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')
154 parser.add_argument('--optfile')
155 parser.add_argument('--screenshot')
156 parser.add_argument('--functional-test', action='store_true')
157 parser.add_argument('--functional-ref', action='store_true')
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
162 def _prepare_window(self, args):
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)
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': {
179 'volume': 1,
180 'language': 'en',
181 'fullscreen': 1,
182 'resolution': '',
183 'antialiasing': 1,
184 'shadows': 1},
185 'development': {
186 'simplepbr': 1,
187 'verbose_log': 0,
188 'physics_debug': 0,
189 'auto_start': 0,
190 'auto_close_instructions': 0,
191 'show_buffers': 0,
192 'debug_items': 0,
193 'mouse_coords': 0,
194 'fps': 0}}
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()
202 res = self._options['settings']['resolution']
203 if res:
204 res = LVector2i(*[int(_res) for _res in res.split('x')])
205 else:
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())]
214 res = sorted(resolutions)[-1]
215 fullscreen = self._options['settings']['fullscreen']
216 props = WindowProperties()
217 if args.functional_test or args.functional_ref:
218 fullscreen = False
219 else:
220 props.set_size(res)
221 props.set_fullscreen(fullscreen)
222 props.set_icon_filename('assets/images/icon/pmachines.ico')
223 if not args.screenshot and not self.version:
224 base.win.request_properties(props)
225 #gltf.patch_loader(base.loader)
226 if self._options['development']['simplepbr'] and not self.version:
227 self._pipeline = simplepbr.init(
228 use_normal_maps=True,
229 use_emission_maps=False,
230 use_occlusion_maps=True,
231 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
232 enable_shadows=int(self._options['settings']['shadows']))
233 debug(f'msaa: {self._pipeline.msaa_samples}')
234 debug(f'shadows: {self._pipeline.enable_shadows}')
235 render.setAntialias(AntialiasAttrib.MAuto)
236 self.base.set_background_color(0, 0, 0, 1)
237 self.base.disable_mouse()
238 if self._options['development']['show_buffers']:
239 base.bufferViewer.toggleEnable()
240 if self._options['development']['fps']:
241 base.set_frame_rate_meter(True)
242 #self.base.accept('window-event', self._on_win_evt)
243 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
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
255 def _set_physics(self):
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)
262 self._debug_np = render.attach_new_node(debug_node)
263 self._debug_np.show()
264 self.world = BulletWorld()
265 self.world.set_gravity((0, 0, -9.81))
266 if self._options['development']['physics_debug']:
267 self.world.set_debug_node(self._debug_np.node())
268 def update(task):
269 dt = globalClock.get_dt()
270 self.world.do_physics(dt, 10, 1/180)
271 return task.cont
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)
279
280 def _on_aspect_ratio_changed(self):
281 if self._fsm.state == 'Scene':
282 self._scene.on_aspect_ratio_changed()