8adf4f747c53a6f9293085a9a5db1f0d66443e5e
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 on_frame(self
, task
):
299 hits
= self
._get
_hits
()
301 for hit
in self
._get
_hits
():
302 if hit
.get_node() == self
._mouse
_plane
_node
:
303 pos
= hit
.get_hit_pos()
304 hit_nodes
= [hit
.get_node() for hit
in hits
]
305 if self
._item
_active
:
306 items_hit
= [self
._item
_active
]
308 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
309 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
310 [itm
.on_mouse_on() for itm
in items_hit
]
311 [itm
.on_mouse_off() for itm
in items_no_hit
]
312 if pos
and self
._item
_active
:
313 self
._item
_active
.on_mouse_move(pos
)
315 self
._update
_info
(items_hit
[0] if items_hit
else None)
316 if all(itm
.end_condition() for itm
in self
.items
) and not self
._paused
:
318 if any(itm
._overlapping
for itm
in self
.items
):
319 self
._cursor
.cursor_img
.img
.set_color(.9, .1, .1, 1)
321 self
._cursor
.cursor_img
.img
.set_color(.9, .9, .9, 1)
324 def cb_inst(self
, item
):
328 self
.__prev
_btn
['state'] = DISABLED
329 self
.__next
_btn
['state'] = DISABLED
330 self
.__right
_btn
['state'] = DISABLED
331 [itm
.play() for itm
in self
.items
]
334 self
._commands
[self
._command
_idx
].redo()
335 self
._command
_idx
+= 1
336 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
337 self
.__prev
_btn
['state'] = NORMAL
338 self
.__prev
_btn
['frameColor'] = fcols
[0]
339 more_commands
= self
._command
_idx
< len(self
._commands
)
340 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
341 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
344 self
._command
_idx
-= 1
345 self
._commands
[self
._command
_idx
].undo()
346 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
347 self
.__next
_btn
['state'] = NORMAL
348 self
.__next
_btn
['frameColor'] = fcols
[0]
349 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
350 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
355 def _set_instructions(self
):
358 mgr
= TextPropertiesManager
.get_global_ptr()
359 for name
in ['mouse_l', 'mouse_r']:
360 graphic
= OnscreenImage('assets/buttons/%s.png' % name
)
361 graphic
.set_scale(.5)
362 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
363 graphic
.get_texture().set_anisotropic_degree(2)
364 mgr
.set_graphic(name
, graphic
)
366 graphic
.set_transparency(True)
367 graphic
.detach_node()
368 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
369 frameSize
=(-.6, .6, -.3, .3))
370 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
372 font
.set_pixels_per_unit(60)
373 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
374 font
.set_outline((0, 0, 0, 1), .8, .2)
375 self
._txt
= OnscreenText(
376 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
377 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
378 u_l
= self
._txt
.textNode
.get_upper_left_3d()
379 l_r
= self
._txt
.textNode
.get_lower_right_3d()
380 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
383 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
384 z
+= (btn_scale
+ 2 * mar
) / 2
385 self
._txt
['pos'] = -w
/ 2, z
386 u_l
= self
._txt
.textNode
.get_upper_left_3d()
387 l_r
= self
._txt
.textNode
.get_lower_right_3d()
388 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
389 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
390 frm
['frameSize'] = fsz
392 (.6, .6, .6, 1), # ready
393 (1, 1, 1, 1), # press
394 (.8, .8, .8, 1), # rollover
396 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
398 image
=imgs
, scale
=btn_scale
,
399 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
400 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
401 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
402 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
403 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
404 btn
.set_transparency(True)
407 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
410 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
411 frameSize
=(-.6, .6, -.3, .3))
412 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
414 font
.set_pixels_per_unit(60)
415 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
416 font
.set_outline((0, 0, 0, 1), .8, .2)
417 self
._txt
= OnscreenText(
420 font
=font
, scale
=0.2,
422 u_l
= self
._txt
.textNode
.get_upper_left_3d()
423 l_r
= self
._txt
.textNode
.get_lower_right_3d()
424 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
427 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
428 z
+= (btn_scale
+ 2 * mar
) / 2
429 self
._txt
['pos'] = 0, z
430 u_l
= self
._txt
.textNode
.get_upper_left_3d()
431 l_r
= self
._txt
.textNode
.get_lower_right_3d()
432 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
433 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
434 frm
['frameSize'] = fsz
436 (.6, .6, .6, 1), # ready
437 (1, 1, 1, 1), # press
438 (.8, .8, .8, 1), # rollover
440 imgs
= [self
.__load
_img
_btn
('home', col
) for col
in colors
]
442 image
=imgs
, scale
=btn_scale
,
443 pos
=(-2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
444 parent
=frm
, command
=self
._on
_end
_home
, extraArgs
=[frm
],
445 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
446 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
447 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
448 btn
.set_transparency(True)
449 imgs
= [self
.__load
_img
_btn
('rewind', col
) for col
in colors
]
451 image
=imgs
, scale
=btn_scale
,
452 pos
=(0, 1, l_r
[2] - mar
- btn_scale
),
453 parent
=frm
, command
=self
._on
_restart
, extraArgs
=[frm
],
454 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
455 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
456 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
457 btn
.set_transparency(True)
459 for _file
in glob('pmachines/scenes/*.py'):
460 _fn
= _file
.replace('.py', '').replace('/', '.')
461 for member
in import_module(_fn
).__dict
__.values():
462 if isclass(member
) and issubclass(member
, Scene
) and \
465 scenes
= sorted(scenes
, key
=lambda elm
: elm
.sorting
)
466 enabled
= scenes
.index(self
.__class
__) < len(scenes
) - 1
468 next_scene
= scenes
[scenes
.index(self
.__class
__) + 1]
471 imgs
= [self
.__load
_img
_btn
('right', col
) for col
in colors
]
473 image
=imgs
, scale
=btn_scale
,
474 pos
=(2.8 * btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
475 parent
=frm
, command
=self
._on
_next
_scene
,
476 extraArgs
=[frm
, next_scene
], relief
=FLAT
,
477 frameColor
=(.6, .6, .6, .08),
478 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
479 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
480 btn
['state'] = NORMAL
if enabled
else DISABLED
481 btn
.set_transparency(True)
483 def _on_restart(self
, frm
):
484 self
.__on
_close
_instructions
(frm
)
487 def _on_end_home(self
, frm
):
488 self
.__on
_close
_instructions
(frm
)
491 def _on_next_scene(self
, frm
, scene
):
492 self
.__on
_close
_instructions
(frm
)
493 self
._reload
_cb
(scene
)
495 def __store_state(self
):
497 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
498 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
499 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
501 btn
['state'] = DISABLED
502 [itm
.store_state() for itm
in self
.items
]
504 def __restore_state(self
):
506 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
507 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
508 for btn
, state
in zip(btns
, self
.__btn
_state
):
510 [itm
.restore_state() for itm
in self
.items
]
513 def __on_close_instructions(self
, frm
):
515 self
.__restore
_state
()