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
.sidepanel
import SidePanel
12 from lib
.engine
.gui
.cursor
import MouseCursor
13 from lib
.lib
.p3d
.gfx
import P3dGfxMgr
16 class Scene(DirectObject
):
18 def __init__(self
, world
, exit_cb
, auto_close_instr
, dbg_items
):
21 self
._exit
_cb
= exit_cb
22 self
._dbg
_items
= dbg_items
24 self
._cursor
= MouseCursor(
25 'assets/buttons/arrowUpLeft.png', (.04, 1, .04), (.5, .5, .5, 1),
30 self
._set
_mouse
_plane
()
34 self
._item
_active
= None
37 self
.__restore
_state
()
39 self
._set
_instructions
()
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')
54 def screenshot(self
, task
=None):
55 tex
= Texture('screenshot')
56 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
57 cam
= base
.make_camera(buffer)
58 cam
.reparent_to(render
)
59 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
60 cam
.set_pos(0, -20, 0)
67 use_emission_maps
=False,
68 use_occlusion_maps
=True,
71 base
.graphicsEngine
.renderFrame()
72 base
.graphicsEngine
.renderFrame()
73 fname
= self
.__class
__.__name
__
74 buffer.save_screenshot('assets/images/scenes/%s.png' % fname
)
76 # frameTexture=buffer.get_texture(), relief=FLAT,
77 # frameSize=(-.2, .2, -.2, .2))
78 return buffer.get_texture()
80 def current_bottom(self
):
82 for item
in self
.items
:
84 curr_bottom
= min(curr_bottom
, item
.get_bottom())
88 [itm
.destroy() for itm
in self
.items
]
92 if hasattr(self
, '_success_txt'):
93 self
._success
_txt
.destroy()
95 self
.__right
_btn
['state'] = NORMAL
101 self
._unset
_mouse
_plane
()
102 [itm
.destroy() for itm
in self
.items
]
104 self
._side
_panel
.destroy()
105 self
._cursor
.destroy()
106 taskMgr
.remove(self
._scene
_tsk
)
107 if hasattr(self
, '_success_txt'):
108 self
._success
_txt
.destroy()
110 def _set_camera(self
):
111 base
.camera
.set_pos(0, -20, 0)
112 base
.camera
.look_at(0, 0, 0)
114 def __load_img_btn(self
, path
, col
):
115 img
= OnscreenImage('assets/buttons/%s.png' % path
)
116 img
.set_transparency(True)
122 def load_images_btn(path
, col
):
125 (.6, .6, .6, 1), # ready
126 (1, 1, 1, 1), # press
127 (.8, .8, .8, 1), # rollover
133 (.4, .1, .1, .4)]}[col
]
134 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
135 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
137 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
138 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
139 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
140 ('next', self
.on_next
, DISABLED
, abr
, 'gray'),
141 ('previous', self
.on_prev
, DISABLED
, abr
, 'gray'),
142 ('rewind', self
.reset
, NORMAL
, abr
, 'gray')]
145 for binfo
in btn_info
:
146 imgs
= load_images_btn(binfo
[0], binfo
[4])
147 if binfo
[3] == base
.a2dBottomLeft
:
151 sign
, num
= -1, num_r
153 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
155 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
156 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
157 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
158 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
159 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
160 btn
.set_transparency(True)
162 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
, self
.__next
_btn
, \
163 self
.__prev
_btn
, self
.__rewind
_btn
= btns
165 self
._info
_txt
= OnscreenText(
166 '', parent
=base
.a2dTopRight
, scale
=0.04,
167 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
169 def _unset_gui(self
):
171 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
172 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
173 [btn
.destroy() for btn
in btns
]
175 self
._info
_txt
.destroy()
177 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
178 light
= Spotlight(name
)
180 light
.setLens(PerspectiveLens())
181 light_np
= render
.attach_new_node(light
)
182 light_np
.set_pos(pos
)
183 light_np
.look_at(look_at
)
184 light
.set_color(color
)
185 render
.set_light(light_np
)
188 def _set_lights(self
):
189 alight
= AmbientLight('alight') # for ao
190 alight
.set_color((.15, .15, .15, 1))
191 self
._alnp
= render
.attach_new_node(alight
)
192 render
.set_light(self
._alnp
)
193 self
._key
_light
= self
._set
_spotlight
(
194 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
195 self
._shadow
_light
= self
._set
_spotlight
(
196 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
197 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
198 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
199 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
200 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
202 def _unset_lights(self
):
203 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
204 render
.clear_light(light
)
207 def _set_input(self
):
208 self
.accept('mouse1', self
.on_click_l
)
209 self
.accept('mouse1-up', self
.on_release
)
210 self
.accept('mouse3', self
.on_click_r
)
211 self
.accept('mouse3-up', self
.on_release
)
213 def _unset_input(self
):
214 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
217 def _set_mouse_plane(self
):
218 shape
= BulletPlaneShape((0, -1, 0), 0)
219 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
220 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
221 self
._mouse
_plane
_node
.addShape(shape
)
222 #np = render.attachNewNode(self._mouse_plane_node)
223 #self._world.attachRigidBody(self._mouse_plane_node)
224 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
226 def _unset_mouse_plane(self
):
227 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
230 if not base
.mouseWatcherNode
.has_mouse(): return []
231 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
232 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
234 def _update_info(self
, item
):
237 txt
= '%.3f %.3f\n%.3f°' % (
238 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
239 self
._info
_txt
['text'] = txt
241 def _on_click(self
, method
):
244 for hit
in self
._get
_hits
():
245 if hit
.get_node() == self
._mouse
_plane
_node
:
246 pos
= hit
.get_hit_pos()
247 for hit
in self
._get
_hits
():
248 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
249 if not self
._item
_active
:
250 self
._item
_active
= item
251 getattr(item
, method
)(pos
)
252 img
= 'move' if method
== 'on_click_l' else 'rotate'
253 if not (img
== 'rotate' and not item
._instantiated
):
254 self
._cursor
.set_image('assets/buttons/%s.png' % img
)
256 def on_click_l(self
):
257 self
._on
_click
('on_click_l')
259 def on_click_r(self
):
260 self
._on
_click
('on_click_r')
262 def on_release(self
):
263 if self
._item
_active
and not self
._item
_active
._first
_command
:
264 self
._commands
= self
._commands
[:self
._command
_idx
]
265 self
._commands
+= [self
._item
_active
]
266 self
._command
_idx
+= 1
267 self
.__prev
_btn
['state'] = NORMAL
268 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
269 self
.__prev
_btn
['frameColor'] = fcols
[0]
270 if self
._item
_active
._command
_idx
== len(self
._item
_active
._commands
) - 1:
271 self
.__next
_btn
['state'] = DISABLED
272 self
.__next
_btn
['frameColor'] = fcols
[1]
273 self
._item
_active
= None
274 [item
.on_release() for item
in self
.items
]
275 self
._cursor
.set_image('assets/buttons/arrowUpLeft.png')
278 for item
in self
.items
:
279 item
.repos_done
= False
280 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
281 [item
.on_aspect_ratio_changed() for item
in self
.items
]
282 self
._side
_panel
.update(self
.items
)
283 max_x
= -float('inf')
284 for item
in self
.items
:
285 if not item
._instantiated
:
286 max_x
= max(item
._np
.get_x(), max_x
)
287 for item
in self
.items
:
288 if not item
._instantiated
:
291 def on_aspect_ratio_changed(self
):
294 def on_frame(self
, task
):
295 hits
= self
._get
_hits
()
297 for hit
in self
._get
_hits
():
298 if hit
.get_node() == self
._mouse
_plane
_node
:
299 pos
= hit
.get_hit_pos()
300 hit_nodes
= [hit
.get_node() for hit
in hits
]
301 if self
._item
_active
:
302 items_hit
= [self
._item
_active
]
304 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
305 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
306 [itm
.on_mouse_on() for itm
in items_hit
]
307 [itm
.on_mouse_off() for itm
in items_no_hit
]
308 if pos
and self
._item
_active
:
309 self
._item
_active
.on_mouse_move(pos
)
311 self
._update
_info
(items_hit
[0] if items_hit
else None)
312 if all(itm
.end_condition() for itm
in self
.items
) and not hasattr(self
, '_success_txt'):
313 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
314 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
316 font
.set_pixels_per_unit(60)
317 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
318 font
.set_outline((0, 0, 0, 1), .8, .2)
319 self
._success
_txt
= OnscreenText(
320 _('You win!'), font
=font
, scale
=0.2, fg
=(.9, .9, .9, 1))
323 def cb_inst(self
, item
):
327 self
.__prev
_btn
['state'] = DISABLED
328 self
.__next
_btn
['state'] = DISABLED
329 self
.__right
_btn
['state'] = DISABLED
330 [itm
.play() for itm
in self
.items
]
333 self
._commands
[self
._command
_idx
].redo()
334 self
._command
_idx
+= 1
335 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
336 self
.__prev
_btn
['state'] = NORMAL
337 self
.__prev
_btn
['frameColor'] = fcols
[0]
338 more_commands
= self
._command
_idx
< len(self
._commands
)
339 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
340 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
343 self
._command
_idx
-= 1
344 self
._commands
[self
._command
_idx
].undo()
345 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
346 self
.__next
_btn
['state'] = NORMAL
347 self
.__next
_btn
['frameColor'] = fcols
[0]
348 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
349 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
354 def _set_instructions(self
):
357 mgr
= TextPropertiesManager
.get_global_ptr()
358 for name
in ['mouse_l', 'mouse_r']:
359 graphic
= OnscreenImage('assets/buttons/%s.png' % name
)
360 graphic
.set_scale(.5)
361 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
362 graphic
.get_texture().set_anisotropic_degree(2)
363 mgr
.set_graphic(name
, graphic
)
365 graphic
.set_transparency(True)
366 graphic
.detach_node()
367 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
368 frameSize
=(-.6, .6, -.3, .3))
369 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
371 font
.set_pixels_per_unit(60)
372 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
373 font
.set_outline((0, 0, 0, 1), .8, .2)
374 self
._txt
= OnscreenText(
375 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
376 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
377 u_l
= self
._txt
.textNode
.get_upper_left_3d()
378 l_r
= self
._txt
.textNode
.get_lower_right_3d()
379 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
382 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
383 z
+= (btn_scale
+ 2 * mar
) / 2
384 self
._txt
['pos'] = -w
/ 2, z
385 u_l
= self
._txt
.textNode
.get_upper_left_3d()
386 l_r
= self
._txt
.textNode
.get_lower_right_3d()
387 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
388 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
389 frm
['frameSize'] = fsz
391 (.6, .6, .6, 1), # ready
392 (1, 1, 1, 1), # press
393 (.8, .8, .8, 1), # rollover
395 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
397 image
=imgs
, scale
=btn_scale
,
398 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
399 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
400 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
401 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
402 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
403 btn
.set_transparency(True)
405 def __store_state(self
):
407 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
408 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
409 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
411 btn
['state'] = DISABLED
412 [itm
.store_state() for itm
in self
.items
]
414 def __restore_state(self
):
416 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
417 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
418 for btn
, state
in zip(btns
, self
.__btn
_state
):
420 [itm
.restore_state() for itm
in self
.items
]
423 def __on_close_instructions(self
, frm
):
425 self
.__restore
_state
()