1 from os
.path
import exists
2 from panda3d
.core
import AmbientLight
, DirectionalLight
, Point3
, Texture
, \
3 TextPropertiesManager
, TextNode
, Spotlight
, PerspectiveLens
, BitMask32
4 from panda3d
.bullet
import BulletPlaneShape
, BulletGhostNode
5 from direct
.gui
.OnscreenImage
import OnscreenImage
6 from direct
.gui
.OnscreenText
import OnscreenText
7 from direct
.gui
.DirectGui
import DirectButton
, DirectFrame
8 from direct
.gui
.DirectGuiGlobals
import FLAT
, DISABLED
, NORMAL
9 from direct
.showbase
.DirectObject
import DirectObject
10 from pmachines
.items
.background
import Background
11 from pmachines
.items
.box
import Box
12 from pmachines
.items
.shelf
import Shelf
13 from pmachines
.items
.domino
import Domino
, TargetDomino
14 from pmachines
.items
.basketball
import Basketball
15 from pmachines
.items
.teetertooter
import TeeterTooter
16 from pmachines
.sidepanel
import SidePanel
17 from lib
.engine
.gui
.cursor
import MouseCursor
18 from lib
.lib
.p3d
.gfx
import P3dGfxMgr
21 class Scene(DirectObject
):
23 def __init__(self
, world
, exit_cb
, auto_close_instr
, dbg_items
):
26 self
._exit
_cb
= exit_cb
27 self
._dbg
_items
= dbg_items
29 self
._cursor
= MouseCursor(
30 'assets/buttons/arrowUpLeft.png', (.04, 1, .04), (.5, .5, .5, 1),
35 self
._set
_mouse
_plane
()
39 self
._item
_active
= None
42 self
.__restore
_state
()
44 self
._set
_instructions
()
45 self
._bg
= Background()
46 self
._side
_panel
= SidePanel(world
, self
._mouse
_plane
_node
, (-5, 4), (-3, 1), 1, self
.items
)
47 self
._scene
_tsk
= taskMgr
.add(self
.on_frame
, 'on_frame')
49 def current_bottom(self
):
51 for item
in self
.items
:
53 curr_bottom
= min(curr_bottom
, item
.get_bottom())
57 [itm
.destroy() for itm
in self
.items
]
59 #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
60 #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
61 self
.items
+= [Shelf(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, mass
=0, pos
=(-1.2, 0, -.6))]
62 self
.items
+= [Shelf(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, mass
=0, pos
=(1.2, 0, -.6))]
63 self
.items
+= [Domino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, count
=2)]
64 self
.items
+= [TargetDomino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, pos
=(-1.14, 0, -.04), tgt_degrees
=60)]
65 self
.items
+= [TargetDomino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, pos
=(-.49, 0, -.04), tgt_degrees
=60)]
66 self
.items
+= [TargetDomino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, pos
=(0.94, 0, -.04), tgt_degrees
=60)]
67 self
.items
+= [TargetDomino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, pos
=(1.55, 0, -.04), tgt_degrees
=60)]
68 self
.items
+= [TargetDomino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, pos
=(2.09, 0, -.04), tgt_degrees
=88)]
69 #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
70 #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
73 if hasattr(self
, '_success_txt'):
74 self
._success
_txt
.destroy()
76 self
.__right
_btn
['state'] = NORMAL
82 self
._unset
_mouse
_plane
()
83 [itm
.destroy() for itm
in self
.items
]
85 self
._side
_panel
.destroy()
86 self
._cursor
.destroy()
87 taskMgr
.remove(self
._scene
_tsk
)
89 def _set_camera(self
):
90 base
.camera
.set_pos(0, -20, 0)
91 base
.camera
.look_at(0, 0, 0)
93 def __load_img_btn(self
, path
, col
):
94 img
= OnscreenImage('assets/buttons/%s.png' % path
)
95 img
.set_transparency(True)
101 def load_images_btn(path
, col
):
104 (.6, .6, .6, 1), # ready
105 (1, 1, 1, 1), # press
106 (.8, .8, .8, 1), # rollover
112 (.4, .1, .1, .4)]}[col
]
113 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
114 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
116 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
117 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
118 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
119 ('next', self
.on_next
, DISABLED
, abr
, 'gray'),
120 ('previous', self
.on_prev
, DISABLED
, abr
, 'gray'),
121 ('rewind', self
.reset
, NORMAL
, abr
, 'gray')]
124 for binfo
in btn_info
:
125 imgs
= load_images_btn(binfo
[0], binfo
[4])
126 if binfo
[3] == base
.a2dBottomLeft
:
130 sign
, num
= -1, num_r
132 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
134 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
135 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
136 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
137 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
138 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
139 btn
.set_transparency(True)
141 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
, self
.__next
_btn
, \
142 self
.__prev
_btn
, self
.__rewind
_btn
= btns
144 self
._info
_txt
= OnscreenText(
145 '', parent
=base
.a2dTopRight
, scale
=0.04,
146 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
148 def _unset_gui(self
):
150 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
151 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
152 [btn
.destroy() for btn
in btns
]
154 self
._info
_txt
.destroy()
156 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
157 light
= Spotlight(name
)
159 light
.setLens(PerspectiveLens())
160 light_np
= render
.attach_new_node(light
)
161 light_np
.set_pos(pos
)
162 light_np
.look_at(look_at
)
163 light
.set_color(color
)
164 render
.set_light(light_np
)
167 def _set_lights(self
):
168 alight
= AmbientLight('alight') # for ao
169 alight
.set_color((.15, .15, .15, 1))
170 self
._alnp
= render
.attach_new_node(alight
)
171 render
.set_light(self
._alnp
)
172 self
._key
_light
= self
._set
_spotlight
(
173 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
174 self
._shadow
_light
= self
._set
_spotlight
(
175 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
176 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
177 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
178 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
179 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
181 def _unset_lights(self
):
182 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
183 render
.clear_light(light
)
186 def _set_input(self
):
187 self
.accept('mouse1', self
.on_click_l
)
188 self
.accept('mouse1-up', self
.on_release
)
189 self
.accept('mouse3', self
.on_click_r
)
190 self
.accept('mouse3-up', self
.on_release
)
192 def _unset_input(self
):
193 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
196 def _set_mouse_plane(self
):
197 shape
= BulletPlaneShape((0, -1, 0), 0)
198 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
199 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
200 self
._mouse
_plane
_node
.addShape(shape
)
201 #np = render.attachNewNode(self._mouse_plane_node)
202 #self._world.attachRigidBody(self._mouse_plane_node)
203 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
205 def _unset_mouse_plane(self
):
206 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
209 if not base
.mouseWatcherNode
.has_mouse(): return []
210 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
211 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
213 def _update_info(self
, item
):
216 txt
= '%.3f %.3f\n%.3f°' % (
217 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
218 self
._info
_txt
['text'] = txt
220 def _on_click(self
, method
):
223 for hit
in self
._get
_hits
():
224 if hit
.get_node() == self
._mouse
_plane
_node
:
225 pos
= hit
.get_hit_pos()
226 for hit
in self
._get
_hits
():
227 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
228 if not self
._item
_active
:
229 self
._item
_active
= item
230 getattr(item
, method
)(pos
)
231 img
= 'move' if method
== 'on_click_l' else 'rotate'
232 if not (img
== 'rotate' and not item
._instantiated
):
233 self
._cursor
.set_image('assets/buttons/%s.png' % img
)
235 def on_click_l(self
):
236 self
._on
_click
('on_click_l')
238 def on_click_r(self
):
239 self
._on
_click
('on_click_r')
241 def on_release(self
):
242 if self
._item
_active
and not self
._item
_active
._first
_command
:
243 self
._commands
= self
._commands
[:self
._command
_idx
]
244 self
._commands
+= [self
._item
_active
]
245 self
._command
_idx
+= 1
246 self
.__prev
_btn
['state'] = NORMAL
247 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
248 self
.__prev
_btn
['frameColor'] = fcols
[0]
249 if self
._item
_active
._command
_idx
== len(self
._item
_active
._commands
) - 1:
250 self
.__next
_btn
['state'] = DISABLED
251 self
.__next
_btn
['frameColor'] = fcols
[1]
252 self
._item
_active
= None
253 [item
.on_release() for item
in self
.items
]
254 self
._cursor
.set_image('assets/buttons/arrowUpLeft.png')
257 for item
in self
.items
:
258 item
.repos_done
= False
259 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
260 [item
.on_aspect_ratio_changed() for item
in self
.items
]
261 self
._side
_panel
.update(self
.items
)
263 for item
in self
.items
:
264 if not item
._instantiated
:
265 max_x
= max(item
._np
.get_x(), max_x
)
266 for item
in self
.items
:
267 if not item
._instantiated
:
270 def on_aspect_ratio_changed(self
):
273 def on_frame(self
, task
):
274 hits
= self
._get
_hits
()
276 for hit
in self
._get
_hits
():
277 if hit
.get_node() == self
._mouse
_plane
_node
:
278 pos
= hit
.get_hit_pos()
279 hit_nodes
= [hit
.get_node() for hit
in hits
]
280 if self
._item
_active
:
281 items_hit
= [self
._item
_active
]
283 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
284 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
285 [itm
.on_mouse_on() for itm
in items_hit
]
286 [itm
.on_mouse_off() for itm
in items_no_hit
]
287 if pos
and self
._item
_active
:
288 self
._item
_active
.on_mouse_move(pos
)
290 self
._update
_info
(items_hit
[0] if items_hit
else None)
291 if all(itm
.end_condition() for itm
in self
.items
) and not hasattr(self
, '_success_txt'):
292 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
293 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
295 font
.set_pixels_per_unit(60)
296 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
297 font
.set_outline((0, 0, 0, 1), .8, .2)
298 self
._success
_txt
= OnscreenText(
299 _('You win!'), font
=font
, scale
=0.2, fg
=(.9, .9, .9, 1))
302 def cb_inst(self
, item
):
306 self
.__prev
_btn
['state'] = DISABLED
307 self
.__next
_btn
['state'] = DISABLED
308 self
.__right
_btn
['state'] = DISABLED
309 [itm
.play() for itm
in self
.items
]
312 self
._commands
[self
._command
_idx
].redo()
313 self
._command
_idx
+= 1
314 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
315 self
.__prev
_btn
['state'] = NORMAL
316 self
.__prev
_btn
['frameColor'] = fcols
[0]
317 more_commands
= self
._command
_idx
< len(self
._commands
)
318 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
319 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
322 self
._command
_idx
-= 1
323 self
._commands
[self
._command
_idx
].undo()
324 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
325 self
.__next
_btn
['state'] = NORMAL
326 self
.__next
_btn
['frameColor'] = fcols
[0]
327 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
328 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
333 def _set_instructions(self
):
336 mgr
= TextPropertiesManager
.get_global_ptr()
337 for name
in ['mouse_l', 'mouse_r']:
338 graphic
= OnscreenImage('assets/buttons/%s.png' % name
)
339 graphic
.set_scale(.5)
340 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
341 graphic
.get_texture().set_anisotropic_degree(2)
342 mgr
.set_graphic(name
, graphic
)
344 graphic
.set_transparency(True)
345 graphic
.detach_node()
346 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
347 frameSize
=(-.6, .6, -.3, .3))
348 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
350 font
.set_pixels_per_unit(60)
351 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
352 font
.set_outline((0, 0, 0, 1), .8, .2)
353 txt
= _('keep \5mouse_l\5 pressed to drag an item\n\n'
354 'keep \5mouse_r\5 pressed to rotate an item')
355 self
._txt
= OnscreenText(
356 txt
, parent
=frm
, font
=font
, scale
=0.06, fg
=(.9, .9, .9, 1),
357 align
=TextNode
.A_left
)
358 u_l
= self
._txt
.textNode
.get_upper_left_3d()
359 l_r
= self
._txt
.textNode
.get_lower_right_3d()
360 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
363 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
364 z
+= (btn_scale
+ 2 * mar
) / 2
365 self
._txt
['pos'] = -w
/ 2, z
366 u_l
= self
._txt
.textNode
.get_upper_left_3d()
367 l_r
= self
._txt
.textNode
.get_lower_right_3d()
368 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
369 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
370 frm
['frameSize'] = fsz
372 (.6, .6, .6, 1), # ready
373 (1, 1, 1, 1), # press
374 (.8, .8, .8, 1), # rollover
376 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
378 image
=imgs
, scale
=btn_scale
,
379 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
380 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
381 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
382 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
383 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
384 btn
.set_transparency(True)
386 def __store_state(self
):
388 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
389 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
390 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
392 btn
['state'] = DISABLED
393 [itm
.store_state() for itm
in self
.items
]
395 def __restore_state(self
):
397 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
398 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
399 for btn
, state
in zip(btns
, self
.__btn
_state
):
401 [itm
.restore_state() for itm
in self
.items
]
404 def __on_close_instructions(self
, frm
):
406 self
.__restore
_state
()