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 _screenshot(self
, task
=None):
50 tex
= Texture('screenshot')
51 buffer = base
.win
.make_texture_buffer('screenshot', 512, 512, tex
, True )
52 cam
= base
.make_camera(buffer)
53 cam
.reparent_to(render
)
54 cam
.node().get_lens().set_fov(base
.camLens
.get_fov())
55 cam
.set_pos(0, -20, 0)
62 use_emission_maps
=False,
63 use_occlusion_maps
=True,
66 base
.graphicsEngine
.renderFrame()
67 base
.graphicsEngine
.renderFrame()
68 # buffer.save_screenshot('assets/scene.png')
70 # frameTexture=buffer.get_texture(), relief=FLAT,
71 # frameSize=(-.2, .2, -.2, .2))
73 def current_bottom(self
):
75 for item
in self
.items
:
77 curr_bottom
= min(curr_bottom
, item
.get_bottom())
81 [itm
.destroy() for itm
in self
.items
]
83 #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
84 #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
85 self
.items
+= [Shelf(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, mass
=0, pos
=(-1.2, 0, -.6))]
86 self
.items
+= [Shelf(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, mass
=0, pos
=(1.2, 0, -.6))]
87 self
.items
+= [Domino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, count
=2)]
88 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)]
89 self
.items
+= [TargetDomino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, pos
=(-.49, 0, -.04), tgt_degrees
=60)]
90 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)]
91 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)]
92 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)]
93 #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
94 #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
97 if hasattr(self
, '_success_txt'):
98 self
._success
_txt
.destroy()
100 self
.__right
_btn
['state'] = NORMAL
106 self
._unset
_mouse
_plane
()
107 [itm
.destroy() for itm
in self
.items
]
109 self
._side
_panel
.destroy()
110 self
._cursor
.destroy()
111 taskMgr
.remove(self
._scene
_tsk
)
113 def _set_camera(self
):
114 base
.camera
.set_pos(0, -20, 0)
115 base
.camera
.look_at(0, 0, 0)
117 def __load_img_btn(self
, path
, col
):
118 img
= OnscreenImage('assets/buttons/%s.png' % path
)
119 img
.set_transparency(True)
125 def load_images_btn(path
, col
):
128 (.6, .6, .6, 1), # ready
129 (1, 1, 1, 1), # press
130 (.8, .8, .8, 1), # rollover
136 (.4, .1, .1, .4)]}[col
]
137 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
138 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
140 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
141 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
142 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
143 ('next', self
.on_next
, DISABLED
, abr
, 'gray'),
144 ('previous', self
.on_prev
, DISABLED
, abr
, 'gray'),
145 ('rewind', self
.reset
, NORMAL
, abr
, 'gray')]
148 for binfo
in btn_info
:
149 imgs
= load_images_btn(binfo
[0], binfo
[4])
150 if binfo
[3] == base
.a2dBottomLeft
:
154 sign
, num
= -1, num_r
156 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
158 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
159 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
160 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
161 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
162 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
163 btn
.set_transparency(True)
165 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
, self
.__next
_btn
, \
166 self
.__prev
_btn
, self
.__rewind
_btn
= btns
168 self
._info
_txt
= OnscreenText(
169 '', parent
=base
.a2dTopRight
, scale
=0.04,
170 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
172 def _unset_gui(self
):
174 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
175 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
176 [btn
.destroy() for btn
in btns
]
178 self
._info
_txt
.destroy()
180 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
181 light
= Spotlight(name
)
183 light
.setLens(PerspectiveLens())
184 light_np
= render
.attach_new_node(light
)
185 light_np
.set_pos(pos
)
186 light_np
.look_at(look_at
)
187 light
.set_color(color
)
188 render
.set_light(light_np
)
191 def _set_lights(self
):
192 alight
= AmbientLight('alight') # for ao
193 alight
.set_color((.15, .15, .15, 1))
194 self
._alnp
= render
.attach_new_node(alight
)
195 render
.set_light(self
._alnp
)
196 self
._key
_light
= self
._set
_spotlight
(
197 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
198 self
._shadow
_light
= self
._set
_spotlight
(
199 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
200 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
201 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
202 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
203 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
205 def _unset_lights(self
):
206 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
207 render
.clear_light(light
)
210 def _set_input(self
):
211 self
.accept('mouse1', self
.on_click_l
)
212 self
.accept('mouse1-up', self
.on_release
)
213 self
.accept('mouse3', self
.on_click_r
)
214 self
.accept('mouse3-up', self
.on_release
)
216 def _unset_input(self
):
217 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
220 def _set_mouse_plane(self
):
221 shape
= BulletPlaneShape((0, -1, 0), 0)
222 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
223 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
224 self
._mouse
_plane
_node
.addShape(shape
)
225 #np = render.attachNewNode(self._mouse_plane_node)
226 #self._world.attachRigidBody(self._mouse_plane_node)
227 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
229 def _unset_mouse_plane(self
):
230 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
233 if not base
.mouseWatcherNode
.has_mouse(): return []
234 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
235 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
237 def _update_info(self
, item
):
240 txt
= '%.3f %.3f\n%.3f°' % (
241 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
242 self
._info
_txt
['text'] = txt
244 def _on_click(self
, method
):
247 for hit
in self
._get
_hits
():
248 if hit
.get_node() == self
._mouse
_plane
_node
:
249 pos
= hit
.get_hit_pos()
250 for hit
in self
._get
_hits
():
251 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
252 if not self
._item
_active
:
253 self
._item
_active
= item
254 getattr(item
, method
)(pos
)
255 img
= 'move' if method
== 'on_click_l' else 'rotate'
256 if not (img
== 'rotate' and not item
._instantiated
):
257 self
._cursor
.set_image('assets/buttons/%s.png' % img
)
259 def on_click_l(self
):
260 self
._on
_click
('on_click_l')
262 def on_click_r(self
):
263 self
._on
_click
('on_click_r')
265 def on_release(self
):
266 if self
._item
_active
and not self
._item
_active
._first
_command
:
267 self
._commands
= self
._commands
[:self
._command
_idx
]
268 self
._commands
+= [self
._item
_active
]
269 self
._command
_idx
+= 1
270 self
.__prev
_btn
['state'] = NORMAL
271 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
272 self
.__prev
_btn
['frameColor'] = fcols
[0]
273 if self
._item
_active
._command
_idx
== len(self
._item
_active
._commands
) - 1:
274 self
.__next
_btn
['state'] = DISABLED
275 self
.__next
_btn
['frameColor'] = fcols
[1]
276 self
._item
_active
= None
277 [item
.on_release() for item
in self
.items
]
278 self
._cursor
.set_image('assets/buttons/arrowUpLeft.png')
281 for item
in self
.items
:
282 item
.repos_done
= False
283 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
284 [item
.on_aspect_ratio_changed() for item
in self
.items
]
285 self
._side
_panel
.update(self
.items
)
286 max_x
= -float('inf')
287 for item
in self
.items
:
288 if not item
._instantiated
:
289 max_x
= max(item
._np
.get_x(), max_x
)
290 for item
in self
.items
:
291 if not item
._instantiated
:
294 def on_aspect_ratio_changed(self
):
297 def on_frame(self
, task
):
298 hits
= self
._get
_hits
()
300 for hit
in self
._get
_hits
():
301 if hit
.get_node() == self
._mouse
_plane
_node
:
302 pos
= hit
.get_hit_pos()
303 hit_nodes
= [hit
.get_node() for hit
in hits
]
304 if self
._item
_active
:
305 items_hit
= [self
._item
_active
]
307 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
308 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
309 [itm
.on_mouse_on() for itm
in items_hit
]
310 [itm
.on_mouse_off() for itm
in items_no_hit
]
311 if pos
and self
._item
_active
:
312 self
._item
_active
.on_mouse_move(pos
)
314 self
._update
_info
(items_hit
[0] if items_hit
else None)
315 if all(itm
.end_condition() for itm
in self
.items
) and not hasattr(self
, '_success_txt'):
316 loader
.load_sfx('assets/audio/sfx/success.ogg').play()
317 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
319 font
.set_pixels_per_unit(60)
320 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
321 font
.set_outline((0, 0, 0, 1), .8, .2)
322 self
._success
_txt
= OnscreenText(
323 _('You win!'), font
=font
, scale
=0.2, fg
=(.9, .9, .9, 1))
326 def cb_inst(self
, item
):
330 self
.__prev
_btn
['state'] = DISABLED
331 self
.__next
_btn
['state'] = DISABLED
332 self
.__right
_btn
['state'] = DISABLED
333 [itm
.play() for itm
in self
.items
]
336 self
._commands
[self
._command
_idx
].redo()
337 self
._command
_idx
+= 1
338 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
339 self
.__prev
_btn
['state'] = NORMAL
340 self
.__prev
_btn
['frameColor'] = fcols
[0]
341 more_commands
= self
._command
_idx
< len(self
._commands
)
342 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
343 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
346 self
._command
_idx
-= 1
347 self
._commands
[self
._command
_idx
].undo()
348 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
349 self
.__next
_btn
['state'] = NORMAL
350 self
.__next
_btn
['frameColor'] = fcols
[0]
351 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
352 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
357 def _set_instructions(self
):
360 mgr
= TextPropertiesManager
.get_global_ptr()
361 for name
in ['mouse_l', 'mouse_r']:
362 graphic
= OnscreenImage('assets/buttons/%s.png' % name
)
363 graphic
.set_scale(.5)
364 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
365 graphic
.get_texture().set_anisotropic_degree(2)
366 mgr
.set_graphic(name
, graphic
)
368 graphic
.set_transparency(True)
369 graphic
.detach_node()
370 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
371 frameSize
=(-.6, .6, -.3, .3))
372 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
374 font
.set_pixels_per_unit(60)
375 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
376 font
.set_outline((0, 0, 0, 1), .8, .2)
377 txt
= _('keep \5mouse_l\5 pressed to drag an item\n\n'
378 'keep \5mouse_r\5 pressed to rotate an item')
379 self
._txt
= OnscreenText(
380 txt
, parent
=frm
, font
=font
, scale
=0.06, fg
=(.9, .9, .9, 1),
381 align
=TextNode
.A_left
)
382 u_l
= self
._txt
.textNode
.get_upper_left_3d()
383 l_r
= self
._txt
.textNode
.get_lower_right_3d()
384 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
387 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
388 z
+= (btn_scale
+ 2 * mar
) / 2
389 self
._txt
['pos'] = -w
/ 2, z
390 u_l
= self
._txt
.textNode
.get_upper_left_3d()
391 l_r
= self
._txt
.textNode
.get_lower_right_3d()
392 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
393 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
394 frm
['frameSize'] = fsz
396 (.6, .6, .6, 1), # ready
397 (1, 1, 1, 1), # press
398 (.8, .8, .8, 1), # rollover
400 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
402 image
=imgs
, scale
=btn_scale
,
403 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
404 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
405 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
406 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
407 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
408 btn
.set_transparency(True)
410 def __store_state(self
):
412 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
413 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
414 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
416 btn
['state'] = DISABLED
417 [itm
.store_state() for itm
in self
.items
]
419 def __restore_state(self
):
421 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
422 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
423 for btn
, state
in zip(btns
, self
.__btn
_state
):
425 [itm
.restore_state() for itm
in self
.items
]
428 def __on_close_instructions(self
, frm
):
430 self
.__restore
_state
()