ya2 · news · projects · code · about

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