ya2 · news · projects · code · about

basketball
[pmachines.git] / pmachines / scene.py
1 from panda3d.core import AmbientLight, DirectionalLight, Point3, Texture, \
2 TextPropertiesManager, TextNode, Spotlight, PerspectiveLens, BitMask32
3 from panda3d.bullet import BulletPlaneShape, BulletGhostNode
4 from direct.gui.OnscreenImage import OnscreenImage
5 from direct.gui.OnscreenText import OnscreenText
6 from direct.gui.DirectGui import DirectButton, DirectFrame
7 from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
8 from direct.showbase.DirectObject import DirectObject
9 from pmachines.items.background import Background
10 from pmachines.items.box import Box
11 from pmachines.items.shelf import Shelf
12 from pmachines.items.domino import Domino
13 from pmachines.items.basketball import Basketball
14 from pmachines.sidepanel import SidePanel
15 from lib.engine.gui.cursor import MouseCursor
16 from lib.lib.p3d.gfx import P3dGfxMgr
17
18
19 class Scene(DirectObject):
20
21 def __init__(self, world, exit_cb, auto_close_instr):
22 super().__init__()
23 self._world = world
24 self._exit_cb = exit_cb
25 self._set_camera()
26 self._cursor = MouseCursor(
27 'assets/buttons/arrowUpLeft.png', (.04, 1, .04), (.5, .5, .5, 1),
28 (.01, .01))
29 self._set_gui()
30 self._set_lights()
31 self._set_input()
32 self._set_mouse_plane()
33 self.items = []
34 self.reset()
35 self._paused = False
36 self._item_active = None
37 if auto_close_instr:
38 self.__store_state()
39 self.__restore_state()
40 else:
41 self._set_instructions()
42 self._bg = Background()
43 self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
44 self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
45
46 def current_bottom(self):
47 curr_bottom = 1
48 for item in self.items:
49 if item.repos_done:
50 curr_bottom = min(curr_bottom, item.get_bottom())
51 return curr_bottom
52
53 def reset(self):
54 [itm.destroy() for itm in self.items]
55 self.items = [Box(self._world, self._mouse_plane_node, 3, self.cb_inst, self.current_bottom, self.repos)]
56 self.items += [Shelf(self._world, self._mouse_plane_node, 3, self.cb_inst, self.current_bottom, self.repos)]
57 self.items += [Domino(self._world, self._mouse_plane_node, 3, self.cb_inst, self.current_bottom, self.repos)]
58 self.items += [Basketball(self._world, self._mouse_plane_node, 3, self.cb_inst, self.current_bottom, self.repos)]
59 self._commands = []
60 self._command_idx = 0
61
62 def destroy(self):
63 self._unset_gui()
64 self._unset_lights()
65 self._unset_input()
66 self._unset_mouse_plane()
67 [itm.destroy() for itm in self.items]
68 self._bg.destroy()
69 self._side_panel.destroy()
70 self._cursor.destroy()
71 taskMgr.remove(self._scene_tsk)
72
73 def _set_camera(self):
74 base.camera.set_pos(0, -20, 0)
75 base.camera.look_at(0, 0, 0)
76
77 def __load_img_btn(self, path, col):
78 img = OnscreenImage('assets/buttons/%s.png' % path)
79 img.set_transparency(True)
80 img.set_color(col)
81 img.detach_node()
82 return img
83
84 def _set_gui(self):
85 def load_images_btn(path, col):
86 colors = {
87 'gray': [
88 (.6, .6, .6, 1), # ready
89 (1, 1, 1, 1), # press
90 (.8, .8, .8, 1), # rollover
91 (.4, .4, .4, .4)],
92 'green': [
93 (.1, .68, .1, 1),
94 (.1, 1, .1, 1),
95 (.1, .84, .1, 1),
96 (.4, .1, .1, .4)]}[col]
97 return [self.__load_img_btn(path, col) for col in colors]
98 abl, abr = base.a2dBottomLeft, base.a2dBottomRight
99 btn_info = [
100 ('home', self.on_home, NORMAL, abl, 'gray'),
101 ('information', self._set_instructions, NORMAL, abl, 'gray'),
102 ('right', self.on_play, NORMAL, abr, 'green'),
103 ('next', self.on_next, DISABLED, abr, 'gray'),
104 ('previous', self.on_prev, DISABLED, abr, 'gray'),
105 ('rewind', self.reset, NORMAL, abr, 'gray')]
106 num_l = num_r = 0
107 btns = []
108 for binfo in btn_info:
109 imgs = load_images_btn(binfo[0], binfo[4])
110 if binfo[3] == base.a2dBottomLeft:
111 sign, num = 1, num_l
112 num_l += 1
113 else:
114 sign, num = -1, num_r
115 num_r += 1
116 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
117 btn = DirectButton(
118 image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
119 parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
120 frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
121 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
122 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
123 btn.set_transparency(True)
124 btns += [btn]
125 self.__home_btn, self.__info_btn, self.__right_btn, self.__next_btn, \
126 self.__prev_btn, self.__rewind_btn = btns
127
128 def _unset_gui(self):
129 btns = [
130 self.__home_btn, self.__info_btn, self.__right_btn,
131 self.__next_btn, self.__prev_btn, self.__rewind_btn]
132 [btn.destroy() for btn in btns]
133
134 def _set_spotlight(self, name, pos, look_at, color, shadows=False):
135 light = Spotlight(name)
136 if shadows:
137 light.setLens(PerspectiveLens())
138 light_np = render.attach_new_node(light)
139 light_np.set_pos(pos)
140 light_np.look_at(look_at)
141 light.set_color(color)
142 render.set_light(light_np)
143 return light_np
144
145 def _set_lights(self):
146 alight = AmbientLight('alight') # for ao
147 alight.set_color((.15, .15, .15, 1))
148 self._alnp = render.attach_new_node(alight)
149 render.set_light(self._alnp)
150 self._key_light = self._set_spotlight(
151 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
152 self._shadow_light = self._set_spotlight(
153 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
154 self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
155 self._shadow_light.node().get_lens().set_film_size(2048, 2048)
156 self._shadow_light.node().get_lens().set_near_far(1, 256)
157 self._shadow_light.node().set_camera_mask(BitMask32(0x01))
158
159 def _unset_lights(self):
160 for light in [self._alnp, self._key_light, self._shadow_light]:
161 render.clear_light(light)
162 light.remove_node()
163
164 def _set_input(self):
165 self.accept('mouse1', self.on_click_l)
166 self.accept('mouse1-up', self.on_release)
167 self.accept('mouse3', self.on_click_r)
168 self.accept('mouse3-up', self.on_release)
169
170 def _unset_input(self):
171 for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
172 self.ignore(evt)
173
174 def _set_mouse_plane(self):
175 shape = BulletPlaneShape((0, -1, 0), 0)
176 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
177 self._mouse_plane_node = BulletGhostNode('mouse plane')
178 self._mouse_plane_node.addShape(shape)
179 #np = render.attachNewNode(self._mouse_plane_node)
180 #self._world.attachRigidBody(self._mouse_plane_node)
181 self._world.attach_ghost(self._mouse_plane_node)
182
183 def _unset_mouse_plane(self):
184 self._world.remove_ghost(self._mouse_plane_node)
185
186 def _get_hits(self):
187 if not base.mouseWatcherNode.has_mouse(): return []
188 p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
189 return self._world.ray_test_all(p_from, p_to).get_hits()
190
191 def _on_click(self, method):
192 if self._paused:
193 return
194 for hit in self._get_hits():
195 if hit.get_node() == self._mouse_plane_node:
196 pos = hit.get_hit_pos()
197 for hit in self._get_hits():
198 for item in [i for i in self.items if hit.get_node() == i.node]:
199 if not self._item_active:
200 self._item_active = item
201 getattr(item, method)(pos)
202 img = 'move' if method == 'on_click_l' else 'rotate'
203 self._cursor.set_image('assets/buttons/%s.png' % img)
204
205 def on_click_l(self):
206 self._on_click('on_click_l')
207
208 def on_click_r(self):
209 self._on_click('on_click_r')
210
211 def on_release(self):
212 if self._item_active and not self._item_active._first_command:
213 self._commands = self._commands[:self._command_idx]
214 self._commands += [self._item_active]
215 self._command_idx += 1
216 self.__prev_btn['state'] = NORMAL
217 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
218 self.__prev_btn['frameColor'] = fcols[0]
219 if self._item_active._command_idx == len(self._item_active._commands) - 1:
220 self.__next_btn['state'] = DISABLED
221 self.__next_btn['frameColor'] = fcols[1]
222 self._item_active = None
223 [item.on_release() for item in self.items]
224 self._cursor.set_image('assets/buttons/arrowUpLeft.png')
225
226 def repos(self):
227 for item in self.items:
228 item.repos_done = False
229 self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
230 [item.on_aspect_ratio_changed() for item in self.items]
231 self._side_panel.update(self.items)
232 max_x = -9
233 for item in self.items:
234 if not item._instantiated:
235 max_x = max(item._np.get_x(), max_x)
236 for item in self.items:
237 if not item._instantiated:
238 item.repos_x(max_x)
239
240 def on_aspect_ratio_changed(self):
241 self.repos()
242
243 def on_frame(self, task):
244 hits = self._get_hits()
245 pos = None
246 for hit in self._get_hits():
247 if hit.get_node() == self._mouse_plane_node:
248 pos = hit.get_hit_pos()
249 hit_nodes = [hit.get_node() for hit in hits]
250 if self._item_active:
251 items_hit = [self._item_active]
252 else:
253 items_hit = [itm for itm in self.items if itm.node in hit_nodes]
254 items_no_hit = [itm for itm in self.items if itm not in items_hit]
255 [itm.on_mouse_on() for itm in items_hit]
256 [itm.on_mouse_off() for itm in items_no_hit]
257 if pos and self._item_active:
258 self._item_active.on_mouse_move(pos)
259 return task.cont
260
261 def cb_inst(self, item):
262 self.items += [item]
263
264 def on_play(self):
265 [itm.play() for itm in self.items]
266
267 def on_next(self):
268 self._commands[self._command_idx].redo()
269 self._command_idx += 1
270 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
271 self.__prev_btn['state'] = NORMAL
272 self.__prev_btn['frameColor'] = fcols[0]
273 more_commands = self._command_idx < len(self._commands)
274 self.__next_btn['state'] = NORMAL if more_commands else DISABLED
275 self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
276
277 def on_prev(self):
278 self._command_idx -= 1
279 self._commands[self._command_idx].undo()
280 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
281 self.__next_btn['state'] = NORMAL
282 self.__next_btn['frameColor'] = fcols[0]
283 self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
284 self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
285
286 def on_home(self):
287 self._exit_cb()
288
289 def _set_instructions(self):
290 self._paused = True
291 self.__store_state()
292 mgr = TextPropertiesManager.get_global_ptr()
293 for name in ['mouse_l', 'mouse_r']:
294 graphic = OnscreenImage('assets/buttons/%s.png' % name)
295 graphic.set_scale(.5)
296 graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
297 graphic.get_texture().set_anisotropic_degree(2)
298 mgr.set_graphic(name, graphic)
299 graphic.set_z(-.2)
300 graphic.set_transparency(True)
301 graphic.detach_node()
302 frm = DirectFrame(frameColor=(.4, .4, .4, .06),
303 frameSize=(-.6, .6, -.3, .3))
304 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
305 font.clear()
306 font.set_pixels_per_unit(60)
307 font.set_minfilter(Texture.FTLinearMipmapLinear)
308 font.set_outline((0, 0, 0, 1), .8, .2)
309 txt = _('keep \5mouse_l\5 pressed to drag an item\n\n'
310 'keep \5mouse_r\5 pressed to rotate an item')
311 self._txt = OnscreenText(
312 txt, parent=frm, font=font, scale=0.06, fg=(.9, .9, .9, 1),
313 align=TextNode.A_left)
314 u_l = self._txt.textNode.get_upper_left_3d()
315 l_r = self._txt.textNode.get_lower_right_3d()
316 w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
317 btn_scale = .05
318 mar = .06 # margin
319 z = h / 2 - font.get_line_height() * self._txt['scale'][1]
320 z += (btn_scale + 2 * mar) / 2
321 self._txt['pos'] = -w / 2, z
322 u_l = self._txt.textNode.get_upper_left_3d()
323 l_r = self._txt.textNode.get_lower_right_3d()
324 c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
325 fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
326 frm['frameSize'] = fsz
327 colors = [
328 (.6, .6, .6, 1), # ready
329 (1, 1, 1, 1), # press
330 (.8, .8, .8, 1), # rollover
331 (.4, .4, .4, .4)]
332 imgs = [self.__load_img_btn('exitRight', col) for col in colors]
333 btn = DirectButton(
334 image=imgs, scale=btn_scale,
335 pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
336 parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
337 relief=FLAT, frameColor=(.6, .6, .6, .08),
338 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
339 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
340 btn.set_transparency(True)
341
342 def __store_state(self):
343 btns = [
344 self.__home_btn, self.__info_btn, self.__right_btn,
345 self.__next_btn, self.__prev_btn, self.__rewind_btn]
346 self.__btn_state = [btn['state'] for btn in btns]
347 for btn in btns:
348 btn['state'] = DISABLED
349 [itm.store_state() for itm in self.items]
350
351 def __restore_state(self):
352 btns = [
353 self.__home_btn, self.__info_btn, self.__right_btn,
354 self.__next_btn, self.__prev_btn, self.__rewind_btn]
355 for btn, state in zip(btns, self.__btn_state):
356 btn['state'] = state
357 [itm.restore_state() for itm in self.items]
358 self._paused = False
359
360 def __on_close_instructions(self, frm):
361 frm.remove_node()
362 self.__restore_state()