f27e7b43c927698052aeee943a7e1a2c6996dcac
1 from os
.path
import exists
3 from importlib
import import_module
4 from inspect
import isclass
5 from panda3d
.core
import AmbientLight
, DirectionalLight
, Point3
, Texture
, \
6 TextPropertiesManager
, TextNode
, Spotlight
, PerspectiveLens
, BitMask32
7 from panda3d
.bullet
import BulletPlaneShape
, BulletGhostNode
8 from direct
.gui
.OnscreenImage
import OnscreenImage
9 from direct
.gui
.OnscreenText
import OnscreenText
10 from direct
.gui
.DirectGui
import DirectButton
, DirectFrame
11 from direct
.gui
.DirectGuiGlobals
import FLAT
, DISABLED
, NORMAL
12 from direct
.showbase
.DirectObject
import DirectObject
13 from pmachines
.items
.background
import Background
14 from pmachines
.sidepanel
import SidePanel
15 from lib
.engine
.gui
.cursor
import MouseCursor
16 from lib
.lib
.p3d
.gfx
import P3dGfxMgr
19 class Scene(DirectObject
):
21 def __init__(self
, world
, exit_cb
, auto_close_instr
, dbg_items
, reload_cb
):
24 self
._exit
_cb
= exit_cb
25 self
._dbg
_items
= dbg_items
26 self
._reload
_cb
= reload_cb
28 self
._cursor
= MouseCursor(
29 'assets/buttons/arrowUpLeft.png', (.04, 1, .04), (.5, .5, .5, 1),
34 self
._set
_mouse
_plane
()
38 self
._item
_active
= None
41 self
.__restore
_state
()
43 self
._set
_instructions
()
44 self
._bg
= Background()
45 self
._side
_panel
= SidePanel(world
, self
._mouse
_plane
_node
, (-5, 4), (-3, 1), 1, self
.items
)
46 self
._scene
_tsk
= taskMgr
.add(self
.on_frame
, 'on_frame')
58 def screenshot(self
, task
=None):
59 tex
= Texture('screenshot')
60 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
61 cam
= base
.make_camera(buffer)
62 cam
.reparent_to(render
)
63 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
64 cam
.set_pos(0, -20, 0)
71 use_emission_maps
=False,
72 use_occlusion_maps
=True,
75 base
.graphicsEngine
.renderFrame()
76 base
.graphicsEngine
.renderFrame()
77 fname
= self
.__class
__.__name
__
78 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
80 # frameTexture=buffer.get_texture(), relief=FLAT,
81 # frameSize=(-.2, .2, -.2, .2))
82 return buffer.get_texture()
84 def current_bottom(self
):
86 for item
in self
.items
:
88 curr_bottom
= min(curr_bottom
, item
.get_bottom())
92 [itm
.destroy() for itm
in self
.items
]
96 if hasattr(self
, '_success_txt'):
97 self
._success
_txt
.destroy()
99 self
.__right
_btn
['state'] = NORMAL
105 self
._unset
_mouse
_plane
()
106 [itm
.destroy() for itm
in self
.items
]
108 self
._side
_panel
.destroy()
109 self
._cursor
.destroy()
110 taskMgr
.remove(self
._scene
_tsk
)
111 if hasattr(self
, '_success_txt'):
112 self
._success
_txt
.destroy()
114 def _set_camera(self
):
115 base
.camera
.set_pos(0, -20, 0)
116 base
.camera
.look_at(0, 0, 0)
118 def __load_img_btn(self
, path
, col
):
119 img
= OnscreenImage('assets/buttons/%s.png' % path
)
120 img
.set_transparency(True)
126 def load_images_btn(path
, col
):
129 (.6, .6, .6, 1), # ready
130 (1, 1, 1, 1), # press
131 (.8, .8, .8, 1), # rollover
137 (.4, .1, .1, .4)]}[col
]
138 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
139 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
141 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
142 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
143 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
144 ('next', self
.on_next
, DISABLED
, abr
, 'gray'),
145 ('previous', self
.on_prev
, DISABLED
, abr
, 'gray'),
146 ('rewind', self
.reset
, NORMAL
, abr
, 'gray')]
149 for binfo
in btn_info
:
150 imgs
= load_images_btn(binfo
[0], binfo
[4])
151 if binfo
[3] == base
.a2dBottomLeft
:
155 sign
, num
= -1, num_r
157 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
159 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
160 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
161 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
162 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
163 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
164 btn
.set_transparency(True)
166 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
, self
.__next
_btn
, \
167 self
.__prev
_btn
, self
.__rewind
_btn
= btns
169 self
._info
_txt
= OnscreenText(
170 '', parent
=base
.a2dTopRight
, scale
=0.04,
171 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
173 def _unset_gui(self
):
175 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
176 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
177 [btn
.destroy() for btn
in btns
]
179 self
._info
_txt
.destroy()
181 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
182 light
= Spotlight(name
)
184 light
.setLens(PerspectiveLens())
185 light_np
= render
.attach_new_node(light
)
186 light_np
.set_pos(pos
)
187 light_np
.look_at(look_at
)
188 light
.set_color(color
)
189 render
.set_light(light_np
)
192 def _set_lights(self
):
193 alight
= AmbientLight('alight') # for ao
194 alight
.set_color((.15, .15, .15, 1))
195 self
._alnp
= render
.attach_new_node(alight
)
196 render
.set_light(self
._alnp
)
197 self
._key
_light
= self
._set
_spotlight
(
198 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
199 self
._shadow
_light
= self
._set
_spotlight
(
200 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
201 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
202 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
203 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
204 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
206 def _unset_lights(self
):
207 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
208 render
.clear_light(light
)
211 def _set_input(self
):
212 self
.accept('mouse1', self
.on_click_l
)
213 self
.accept('mouse1-up', self
.on_release
)
214 self
.accept('mouse3', self
.on_click_r
)
215 self
.accept('mouse3-up', self
.on_release
)
217 def _unset_input(self
):
218 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
221 def _set_mouse_plane(self
):
222 shape
= BulletPlaneShape((0, -1, 0), 0)
223 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
224 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
225 self
._mouse
_plane
_node
.addShape(shape
)
226 #np = render.attachNewNode(self._mouse_plane_node)
227 #self._world.attachRigidBody(self._mouse_plane_node)
228 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
230 def _unset_mouse_plane(self
):
231 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
234 if not base
.mouseWatcherNode
.has_mouse(): return []
235 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
236 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
238 def _update_info(self
, item
):
241 txt
= '%.3f %.3f\n%.3f°' % (
242 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
243 self
._info
_txt
['text'] = txt
245 def _on_click(self
, method
):
248 for hit
in self
._get
_hits
():
249 if hit
.get_node() == self
._mouse
_plane
_node
:
250 pos
= hit
.get_hit_pos()
251 for hit
in self
._get
_hits
():
252 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
253 if not self
._item
_active
:
254 self
._item
_active
= item
255 getattr(item
, method
)(pos
)
256 img
= 'move' if method
== 'on_click_l' else 'rotate'
257 if not (img
== 'rotate' and not item
._instantiated
):
258 self
._cursor
.set_image('assets/buttons/%s.png' % img
)
260 def on_click_l(self
):
261 self
._on
_click
('on_click_l')
263 def on_click_r(self
):
264 self
._on
_click
('on_click_r')
266 def on_release(self
):
267 if self
._item
_active
and not self
._item
_active
._first
_command
:
268 self
._commands
= self
._commands
[:self
._command
_idx
]
269 self
._commands
+= [self
._item
_active
]
270 self
._command
_idx
+= 1
271 self
.__prev
_btn
['state'] = NORMAL
272 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
273 self
.__prev
_btn
['frameColor'] = fcols
[0]
274 if self
._item
_active
._command
_idx
== len(self
._item
_active
._commands
) - 1:
275 self
.__next
_btn
['state'] = DISABLED
276 self
.__next
_btn
['frameColor'] = fcols
[1]
277 self
._item
_active
= None
278 [item
.on_release() for item
in self
.items
]
279 self
._cursor
.set_image('assets/buttons/arrowUpLeft.png')
282 for item
in self
.items
:
283 item
.repos_done
= False
284 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
285 [item
.on_aspect_ratio_changed() for item
in self
.items
]
286 self
._side
_panel
.update(self
.items
)
287 max_x
= -float('inf')
288 for item
in self
.items
:
289 if not item
._instantiated
:
290 max_x
= max(item
._np
.get_x(), max_x
)
291 for item
in self
.items
:
292 if not item
._instantiated
:
295 def on_aspect_ratio_changed(self
):
298 def _end_condition(self
):
301 def on_frame(self
, task
):
302 hits
= self
._get
_hits
()
304 for hit
in self
._get
_hits
():
305 if hit
.get_node() == self
._mouse
_plane
_node
:
306 pos
= hit
.get_hit_pos()
307 hit_nodes
= [hit
.get_node() for hit
in hits
]
308 if self
._item
_active
:
309 items_hit
= [self
._item
_active
]
311 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
312 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
313 [itm
.on_mouse_on() for itm
in items_hit
]
314 [itm
.on_mouse_off() for itm
in items_no_hit
]
315 if pos
and self
._item
_active
:
316 self
._item
_active
.on_mouse_move(pos
)
318 self
._update
_info
(items_hit
[0] if items_hit
else None)
319 if self
._end
_condition
():
321 if any(itm
._overlapping
for itm
in self
.items
):
322 self
._cursor
.cursor_img
.img
.set_color(.9, .1, .1, 1)
324 self
._cursor
.cursor_img
.img
.set_color(.9, .9, .9, 1)
327 def cb_inst(self
, item
):
331 self
.__prev
_btn
['state'] = DISABLED
332 self
.__next
_btn
['state'] = DISABLED
333 self
.__right
_btn
['state'] = DISABLED
334 [itm
.play() for itm
in self
.items
]
337 self
._commands
[self
._command
_idx
].redo()
338 self
._command
_idx
+= 1
339 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
340 self
.__prev
_btn
['state'] = NORMAL
341 self
.__prev
_btn
['frameColor'] = fcols
[0]
342 more_commands
= self
._command
_idx
< len(self
._commands
)
343 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
344 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
347 self
._command
_idx
-= 1
348 self
._commands
[self
._command
_idx
].undo()
349 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
350 self
.__next
_btn
['state'] = NORMAL
351 self
.__next
_btn
['frameColor'] = fcols
[0]
352 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
353 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
358 def _set_instructions(self
):
361 mgr
= TextPropertiesManager
.get_global_ptr()
362 for name
in ['mouse_l', 'mouse_r']:
363 graphic
= OnscreenImage('assets/buttons/%s.png' % name
)
364 graphic
.set_scale(.5)
365 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
366 graphic
.get_texture().set_anisotropic_degree(2)
367 mgr
.set_graphic(name
, graphic
)
369 graphic
.set_transparency(True)
370 graphic
.detach_node()
371 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
372 frameSize
=(-.6, .6, -.3, .3))
373 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
375 font
.set_pixels_per_unit(60)
376 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
377 font
.set_outline((0, 0, 0, 1), .8, .2)
378 self
._txt
= OnscreenText(
379 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
380 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
381 u_l
= self
._txt
.textNode
.get_upper_left_3d()
382 l_r
= self
._txt
.textNode
.get_lower_right_3d()
383 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
386 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
387 z
+= (btn_scale
+ 2 * mar
) / 2
388 self
._txt
['pos'] = -w
/ 2, z
389 u_l
= self
._txt
.textNode
.get_upper_left_3d()
390 l_r
= self
._txt
.textNode
.get_lower_right_3d()
391 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
392 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
393 frm
['frameSize'] = fsz
395 (.6, .6, .6, 1), # ready
396 (1, 1, 1, 1), # press
397 (.8, .8, .8, 1), # rollover
399 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
401 image
=imgs
, scale
=btn_scale
,
402 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
403 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
404 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
405 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
406 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
407 btn
.set_transparency(True)
410 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
413 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
414 frameSize
=(-.6, .6, -.3, .3))
415 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
417 font
.set_pixels_per_unit(60)
418 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
419 font
.set_outline((0, 0, 0, 1), .8, .2)
420 self
._txt
= OnscreenText(
423 font
=font
, scale
=0.2,
425 u_l
= self
._txt
.textNode
.get_upper_left_3d()
426 l_r
= self
._txt
.textNode
.get_lower_right_3d()
427 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
430 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
431 z
+= (btn_scale
+ 2 * mar
) / 2
432 self
._txt
['pos'] = 0, z
433 u_l
= self
._txt
.textNode
.get_upper_left_3d()
434 l_r
= self
._txt
.textNode
.get_lower_right_3d()
435 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
436 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
437 frm
['frameSize'] = fsz
439 (.6, .6, .6, 1), # ready
440 (1, 1, 1, 1), # press
441 (.8, .8, .8, 1), # rollover
443 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
445 image
=imgs
, scale
=btn_scale
,
446 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
447 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
448 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
449 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
450 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
451 btn
.set_transparency(True)
452 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
454 image
=imgs
, scale
=btn_scale
,
455 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
456 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
457 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
458 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
459 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
460 btn
.set_transparency(True)
462 for _file
in glob('pmachines/scenes/*.py'):
463 _fn
= _file
.replace('.py', '').replace('/', '.')
464 for member
in import_module(_fn
).__dict
__.values():
465 if isclass(member
) and issubclass(member
, Scene
) and \
468 scenes
= sorted(scenes
, key
=lambda elm
: elm
.sorting
)
469 enabled
= scenes
.index(self
.__class
__) < len(scenes
) - 1
471 next_scene
= scenes
[scenes
.index(self
.__class
__) + 1]
474 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
476 image
=imgs
, scale
=btn_scale
,
477 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
478 parent
=frm
, command
=self
._on
_next
_scene
,
479 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
480 frameColor
=(.6, .6, .6, .08),
481 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
482 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
483 btn
['state'] = NORMAL
if enabled
else DISABLED
484 btn
.set_transparency(True)
486 def _on_restart(self
, frm
):
487 self
.__on
_close
_instructions
(frm
)
490 def _on_end_home(self
, frm
):
491 self
.__on
_close
_instructions
(frm
)
494 def _on_next_scene(self
, frm
, scene
):
495 self
.__on
_close
_instructions
(frm
)
496 self
._reload
_cb
(scene
)
498 def __store_state(self
):
500 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
501 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
502 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
504 btn
['state'] = DISABLED
505 [itm
.store_state() for itm
in self
.items
]
507 def __restore_state(self
):
509 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
510 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
511 for btn
, state
in zip(btns
, self
.__btn
_state
):
513 [itm
.restore_state() for itm
in self
.items
]
516 def __on_close_instructions(self
, frm
):
518 self
.__restore
_state
()