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 # buffer.save_screenshot('assets/scene.png')
75 # frameTexture=buffer.get_texture(), relief=FLAT,
76 # frameSize=(-.2, .2, -.2, .2))
77 return buffer.get_texture()
79 def current_bottom(self
):
81 for item
in self
.items
:
83 curr_bottom
= min(curr_bottom
, item
.get_bottom())
87 [itm
.destroy() for itm
in self
.items
]
91 if hasattr(self
, '_success_txt'):
92 self
._success
_txt
.destroy()
94 self
.__right
_btn
['state'] = NORMAL
100 self
._unset
_mouse
_plane
()
101 [itm
.destroy() for itm
in self
.items
]
103 self
._side
_panel
.destroy()
104 self
._cursor
.destroy()
105 taskMgr
.remove(self
._scene
_tsk
)
106 if hasattr(self
, '_success_txt'):
107 self
._success
_txt
.destroy()
109 def _set_camera(self
):
110 base
.camera
.set_pos(0, -20, 0)
111 base
.camera
.look_at(0, 0, 0)
113 def __load_img_btn(self
, path
, col
):
114 img
= OnscreenImage('assets/buttons/%s.png' % path
)
115 img
.set_transparency(True)
121 def load_images_btn(path
, col
):
124 (.6, .6, .6, 1), # ready
125 (1, 1, 1, 1), # press
126 (.8, .8, .8, 1), # rollover
132 (.4, .1, .1, .4)]}[col
]
133 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
134 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
136 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
137 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
138 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
139 ('next', self
.on_next
, DISABLED
, abr
, 'gray'),
140 ('previous', self
.on_prev
, DISABLED
, abr
, 'gray'),
141 ('rewind', self
.reset
, NORMAL
, abr
, 'gray')]
144 for binfo
in btn_info
:
145 imgs
= load_images_btn(binfo
[0], binfo
[4])
146 if binfo
[3] == base
.a2dBottomLeft
:
150 sign
, num
= -1, num_r
152 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
154 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
155 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
156 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
157 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
158 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
159 btn
.set_transparency(True)
161 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
, self
.__next
_btn
, \
162 self
.__prev
_btn
, self
.__rewind
_btn
= btns
164 self
._info
_txt
= OnscreenText(
165 '', parent
=base
.a2dTopRight
, scale
=0.04,
166 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
168 def _unset_gui(self
):
170 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
171 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
172 [btn
.destroy() for btn
in btns
]
174 self
._info
_txt
.destroy()
176 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
177 light
= Spotlight(name
)
179 light
.setLens(PerspectiveLens())
180 light_np
= render
.attach_new_node(light
)
181 light_np
.set_pos(pos
)
182 light_np
.look_at(look_at
)
183 light
.set_color(color
)
184 render
.set_light(light_np
)
187 def _set_lights(self
):
188 alight
= AmbientLight('alight') # for ao
189 alight
.set_color((.15, .15, .15, 1))
190 self
._alnp
= render
.attach_new_node(alight
)
191 render
.set_light(self
._alnp
)
192 self
._key
_light
= self
._set
_spotlight
(
193 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
194 self
._shadow
_light
= self
._set
_spotlight
(
195 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
196 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
197 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
198 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
199 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
201 def _unset_lights(self
):
202 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
203 render
.clear_light(light
)
206 def _set_input(self
):
207 self
.accept('mouse1', self
.on_click_l
)
208 self
.accept('mouse1-up', self
.on_release
)
209 self
.accept('mouse3', self
.on_click_r
)
210 self
.accept('mouse3-up', self
.on_release
)
212 def _unset_input(self
):
213 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
216 def _set_mouse_plane(self
):
217 shape
= BulletPlaneShape((0, -1, 0), 0)
218 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
219 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
220 self
._mouse
_plane
_node
.addShape(shape
)
221 #np = render.attachNewNode(self._mouse_plane_node)
222 #self._world.attachRigidBody(self._mouse_plane_node)
223 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
225 def _unset_mouse_plane(self
):
226 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
229 if not base
.mouseWatcherNode
.has_mouse(): return []
230 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
231 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
233 def _update_info(self
, item
):
236 txt
= '%.3f %.3f\n%.3f°' % (
237 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
238 self
._info
_txt
['text'] = txt
240 def _on_click(self
, method
):
243 for hit
in self
._get
_hits
():
244 if hit
.get_node() == self
._mouse
_plane
_node
:
245 pos
= hit
.get_hit_pos()
246 for hit
in self
._get
_hits
():
247 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
248 if not self
._item
_active
:
249 self
._item
_active
= item
250 getattr(item
, method
)(pos
)
251 img
= 'move' if method
== 'on_click_l' else 'rotate'
252 if not (img
== 'rotate' and not item
._instantiated
):
253 self
._cursor
.set_image('assets/buttons/%s.png' % img
)
255 def on_click_l(self
):
256 self
._on
_click
('on_click_l')
258 def on_click_r(self
):
259 self
._on
_click
('on_click_r')
261 def on_release(self
):
262 if self
._item
_active
and not self
._item
_active
._first
_command
:
263 self
._commands
= self
._commands
[:self
._command
_idx
]
264 self
._commands
+= [self
._item
_active
]
265 self
._command
_idx
+= 1
266 self
.__prev
_btn
['state'] = NORMAL
267 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
268 self
.__prev
_btn
['frameColor'] = fcols
[0]
269 if self
._item
_active
._command
_idx
== len(self
._item
_active
._commands
) - 1:
270 self
.__next
_btn
['state'] = DISABLED
271 self
.__next
_btn
['frameColor'] = fcols
[1]
272 self
._item
_active
= None
273 [item
.on_release() for item
in self
.items
]
274 self
._cursor
.set_image('assets/buttons/arrowUpLeft.png')
277 for item
in self
.items
:
278 item
.repos_done
= False
279 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
280 [item
.on_aspect_ratio_changed() for item
in self
.items
]
281 self
._side
_panel
.update(self
.items
)
282 max_x
= -float('inf')
283 for item
in self
.items
:
284 if not item
._instantiated
:
285 max_x
= max(item
._np
.get_x(), max_x
)
286 for item
in self
.items
:
287 if not item
._instantiated
:
290 def on_aspect_ratio_changed(self
):
293 def on_frame(self
, task
):
294 hits
= self
._get
_hits
()
296 for hit
in self
._get
_hits
():
297 if hit
.get_node() == self
._mouse
_plane
_node
:
298 pos
= hit
.get_hit_pos()
299 hit_nodes
= [hit
.get_node() for hit
in hits
]
300 if self
._item
_active
:
301 items_hit
= [self
._item
_active
]
303 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
304 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
305 [itm
.on_mouse_on() for itm
in items_hit
]
306 [itm
.on_mouse_off() for itm
in items_no_hit
]
307 if pos
and self
._item
_active
:
308 self
._item
_active
.on_mouse_move(pos
)
310 self
._update
_info
(items_hit
[0] if items_hit
else None)
311 if all(itm
.end_condition() for itm
in self
.items
) and not hasattr(self
, '_success_txt'):
312 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
313 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
315 font
.set_pixels_per_unit(60)
316 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
317 font
.set_outline((0, 0, 0, 1), .8, .2)
318 self
._success
_txt
= OnscreenText(
319 _('You win!'), font
=font
, scale
=0.2, fg
=(.9, .9, .9, 1))
322 def cb_inst(self
, item
):
326 self
.__prev
_btn
['state'] = DISABLED
327 self
.__next
_btn
['state'] = DISABLED
328 self
.__right
_btn
['state'] = DISABLED
329 [itm
.play() for itm
in self
.items
]
332 self
._commands
[self
._command
_idx
].redo()
333 self
._command
_idx
+= 1
334 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
335 self
.__prev
_btn
['state'] = NORMAL
336 self
.__prev
_btn
['frameColor'] = fcols
[0]
337 more_commands
= self
._command
_idx
< len(self
._commands
)
338 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
339 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
342 self
._command
_idx
-= 1
343 self
._commands
[self
._command
_idx
].undo()
344 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
345 self
.__next
_btn
['state'] = NORMAL
346 self
.__next
_btn
['frameColor'] = fcols
[0]
347 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
348 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
353 def _set_instructions(self
):
356 mgr
= TextPropertiesManager
.get_global_ptr()
357 for name
in ['mouse_l', 'mouse_r']:
358 graphic
= OnscreenImage('assets/buttons/%s.png' % name
)
359 graphic
.set_scale(.5)
360 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
361 graphic
.get_texture().set_anisotropic_degree(2)
362 mgr
.set_graphic(name
, graphic
)
364 graphic
.set_transparency(True)
365 graphic
.detach_node()
366 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
367 frameSize
=(-.6, .6, -.3, .3))
368 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
370 font
.set_pixels_per_unit(60)
371 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
372 font
.set_outline((0, 0, 0, 1), .8, .2)
373 self
._txt
= OnscreenText(
374 self
._instr
_txt
(), parent
=frm
, font
=font
, scale
=0.06,
375 fg
=(.9, .9, .9, 1), align
=TextNode
.A_left
)
376 u_l
= self
._txt
.textNode
.get_upper_left_3d()
377 l_r
= self
._txt
.textNode
.get_lower_right_3d()
378 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
381 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
382 z
+= (btn_scale
+ 2 * mar
) / 2
383 self
._txt
['pos'] = -w
/ 2, z
384 u_l
= self
._txt
.textNode
.get_upper_left_3d()
385 l_r
= self
._txt
.textNode
.get_lower_right_3d()
386 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
387 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
388 frm
['frameSize'] = fsz
390 (.6, .6, .6, 1), # ready
391 (1, 1, 1, 1), # press
392 (.8, .8, .8, 1), # rollover
394 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
396 image
=imgs
, scale
=btn_scale
,
397 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
398 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
399 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
400 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
401 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
402 btn
.set_transparency(True)
404 def __store_state(self
):
406 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
407 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
408 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
410 btn
['state'] = DISABLED
411 [itm
.store_state() for itm
in self
.items
]
413 def __restore_state(self
):
415 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
416 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
417 for btn
, state
in zip(btns
, self
.__btn
_state
):
419 [itm
.restore_state() for itm
in self
.items
]
422 def __on_close_instructions(self
, frm
):
424 self
.__restore
_state
()