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
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 current_bottom(self
):
51 for item
in self
.items
:
53 curr_bottom
= min(curr_bottom
, item
.get_bottom())
57 [itm
.destroy() for itm
in self
.items
]
59 #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
60 #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
61 self
.items
+= [Shelf(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, mass
=0, pos
=(-1.2, 0, 1), r
=10)]
62 self
.items
+= [Shelf(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, mass
=0, pos
=(1.2, 0, 1), r
=-10)]
63 self
.items
+= [Domino(self
._world
, self
._mouse
_plane
_node
, self
.cb_inst
, self
.current_bottom
, self
.repos
, count
=3)]
64 #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
65 #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
73 self
._unset
_mouse
_plane
()
74 [itm
.destroy() for itm
in self
.items
]
76 self
._side
_panel
.destroy()
77 self
._cursor
.destroy()
78 taskMgr
.remove(self
._scene
_tsk
)
80 def _set_camera(self
):
81 base
.camera
.set_pos(0, -20, 0)
82 base
.camera
.look_at(0, 0, 0)
84 def __load_img_btn(self
, path
, col
):
85 img
= OnscreenImage('assets/buttons/%s.png' % path
)
86 img
.set_transparency(True)
92 def load_images_btn(path
, col
):
95 (.6, .6, .6, 1), # ready
97 (.8, .8, .8, 1), # rollover
103 (.4, .1, .1, .4)]}[col
]
104 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
105 abl
, abr
= base
.a2dBottomLeft
, base
.a2dBottomRight
107 ('home', self
.on_home
, NORMAL
, abl
, 'gray'),
108 ('information', self
._set
_instructions
, NORMAL
, abl
, 'gray'),
109 ('right', self
.on_play
, NORMAL
, abr
, 'green'),
110 ('next', self
.on_next
, DISABLED
, abr
, 'gray'),
111 ('previous', self
.on_prev
, DISABLED
, abr
, 'gray'),
112 ('rewind', self
.reset
, NORMAL
, abr
, 'gray')]
115 for binfo
in btn_info
:
116 imgs
= load_images_btn(binfo
[0], binfo
[4])
117 if binfo
[3] == base
.a2dBottomLeft
:
121 sign
, num
= -1, num_r
123 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
125 image
=imgs
, scale
=.05, pos
=(sign
* (.06 + .11 * num
), 1, .06),
126 parent
=binfo
[3], command
=binfo
[1], state
=binfo
[2], relief
=FLAT
,
127 frameColor
=fcols
[0] if binfo
[2] == NORMAL
else fcols
[1],
128 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
129 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
130 btn
.set_transparency(True)
132 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
, self
.__next
_btn
, \
133 self
.__prev
_btn
, self
.__rewind
_btn
= btns
135 self
._info
_txt
= OnscreenText(
136 '', parent
=base
.a2dTopRight
, scale
=0.04,
137 pos
=(-.03, -.06), fg
=(.9, .9, .9, 1), align
=TextNode
.A_right
)
139 def _unset_gui(self
):
141 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
142 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
143 [btn
.destroy() for btn
in btns
]
145 self
._info
_txt
.destroy()
147 def _set_spotlight(self
, name
, pos
, look_at
, color
, shadows
=False):
148 light
= Spotlight(name
)
150 light
.setLens(PerspectiveLens())
151 light_np
= render
.attach_new_node(light
)
152 light_np
.set_pos(pos
)
153 light_np
.look_at(look_at
)
154 light
.set_color(color
)
155 render
.set_light(light_np
)
158 def _set_lights(self
):
159 alight
= AmbientLight('alight') # for ao
160 alight
.set_color((.15, .15, .15, 1))
161 self
._alnp
= render
.attach_new_node(alight
)
162 render
.set_light(self
._alnp
)
163 self
._key
_light
= self
._set
_spotlight
(
164 'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
165 self
._shadow
_light
= self
._set
_spotlight
(
166 'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
167 self
._shadow
_light
.node().set_shadow_caster(True, 2048, 2048)
168 self
._shadow
_light
.node().get_lens().set_film_size(2048, 2048)
169 self
._shadow
_light
.node().get_lens().set_near_far(1, 256)
170 self
._shadow
_light
.node().set_camera_mask(BitMask32(0x01))
172 def _unset_lights(self
):
173 for light
in [self
._alnp
, self
._key
_light
, self
._shadow
_light
]:
174 render
.clear_light(light
)
177 def _set_input(self
):
178 self
.accept('mouse1', self
.on_click_l
)
179 self
.accept('mouse1-up', self
.on_release
)
180 self
.accept('mouse3', self
.on_click_r
)
181 self
.accept('mouse3-up', self
.on_release
)
183 def _unset_input(self
):
184 for evt
in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
187 def _set_mouse_plane(self
):
188 shape
= BulletPlaneShape((0, -1, 0), 0)
189 #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
190 self
._mouse
_plane
_node
= BulletGhostNode('mouse plane')
191 self
._mouse
_plane
_node
.addShape(shape
)
192 #np = render.attachNewNode(self._mouse_plane_node)
193 #self._world.attachRigidBody(self._mouse_plane_node)
194 self
._world
.attach_ghost(self
._mouse
_plane
_node
)
196 def _unset_mouse_plane(self
):
197 self
._world
.remove_ghost(self
._mouse
_plane
_node
)
200 if not base
.mouseWatcherNode
.has_mouse(): return []
201 p_from
, p_to
= P3dGfxMgr
.world_from_to(base
.mouseWatcherNode
.get_mouse())
202 return self
._world
.ray_test_all(p_from
, p_to
).get_hits()
204 def _update_info(self
, item
):
207 txt
= '%.3f %.3f\n%.3f°' % (
208 item
._np
.get_x(), item
._np
.get_z(), item
._np
.get_r())
209 self
._info
_txt
['text'] = txt
211 def _on_click(self
, method
):
214 for hit
in self
._get
_hits
():
215 if hit
.get_node() == self
._mouse
_plane
_node
:
216 pos
= hit
.get_hit_pos()
217 for hit
in self
._get
_hits
():
218 for item
in [i
for i
in self
.items
if hit
.get_node() == i
.node
and i
.interactable
]:
219 if not self
._item
_active
:
220 self
._item
_active
= item
221 getattr(item
, method
)(pos
)
222 img
= 'move' if method
== 'on_click_l' else 'rotate'
223 if not (img
== 'rotate' and not item
._instantiated
):
224 self
._cursor
.set_image('assets/buttons/%s.png' % img
)
226 def on_click_l(self
):
227 self
._on
_click
('on_click_l')
229 def on_click_r(self
):
230 self
._on
_click
('on_click_r')
232 def on_release(self
):
233 if self
._item
_active
and not self
._item
_active
._first
_command
:
234 self
._commands
= self
._commands
[:self
._command
_idx
]
235 self
._commands
+= [self
._item
_active
]
236 self
._command
_idx
+= 1
237 self
.__prev
_btn
['state'] = NORMAL
238 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
239 self
.__prev
_btn
['frameColor'] = fcols
[0]
240 if self
._item
_active
._command
_idx
== len(self
._item
_active
._commands
) - 1:
241 self
.__next
_btn
['state'] = DISABLED
242 self
.__next
_btn
['frameColor'] = fcols
[1]
243 self
._item
_active
= None
244 [item
.on_release() for item
in self
.items
]
245 self
._cursor
.set_image('assets/buttons/arrowUpLeft.png')
248 for item
in self
.items
:
249 item
.repos_done
= False
250 self
.items
= sorted(self
.items
, key
=lambda itm
: itm
.__class
__.__name
__)
251 [item
.on_aspect_ratio_changed() for item
in self
.items
]
252 self
._side
_panel
.update(self
.items
)
254 for item
in self
.items
:
255 if not item
._instantiated
:
256 max_x
= max(item
._np
.get_x(), max_x
)
257 for item
in self
.items
:
258 if not item
._instantiated
:
261 def on_aspect_ratio_changed(self
):
264 def on_frame(self
, task
):
265 hits
= self
._get
_hits
()
267 for hit
in self
._get
_hits
():
268 if hit
.get_node() == self
._mouse
_plane
_node
:
269 pos
= hit
.get_hit_pos()
270 hit_nodes
= [hit
.get_node() for hit
in hits
]
271 if self
._item
_active
:
272 items_hit
= [self
._item
_active
]
274 items_hit
= [itm
for itm
in self
.items
if itm
.node
in hit_nodes
]
275 items_no_hit
= [itm
for itm
in self
.items
if itm
not in items_hit
]
276 [itm
.on_mouse_on() for itm
in items_hit
]
277 [itm
.on_mouse_off() for itm
in items_no_hit
]
278 if pos
and self
._item
_active
:
279 self
._item
_active
.on_mouse_move(pos
)
281 self
._update
_info
(items_hit
[0] if items_hit
else None)
284 def cb_inst(self
, item
):
288 [itm
.play() for itm
in self
.items
]
291 self
._commands
[self
._command
_idx
].redo()
292 self
._command
_idx
+= 1
293 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
294 self
.__prev
_btn
['state'] = NORMAL
295 self
.__prev
_btn
['frameColor'] = fcols
[0]
296 more_commands
= self
._command
_idx
< len(self
._commands
)
297 self
.__next
_btn
['state'] = NORMAL
if more_commands
else DISABLED
298 self
.__next
_btn
['frameColor'] = fcols
[0] if more_commands
else fcols
[1]
301 self
._command
_idx
-= 1
302 self
._commands
[self
._command
_idx
].undo()
303 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
304 self
.__next
_btn
['state'] = NORMAL
305 self
.__next
_btn
['frameColor'] = fcols
[0]
306 self
.__prev
_btn
['state'] = NORMAL
if self
._command
_idx
else DISABLED
307 self
.__prev
_btn
['frameColor'] = fcols
[0] if self
._command
_idx
else fcols
[1]
312 def _set_instructions(self
):
315 mgr
= TextPropertiesManager
.get_global_ptr()
316 for name
in ['mouse_l', 'mouse_r']:
317 graphic
= OnscreenImage('assets/buttons/%s.png' % name
)
318 graphic
.set_scale(.5)
319 graphic
.get_texture().set_minfilter(Texture
.FTLinearMipmapLinear
)
320 graphic
.get_texture().set_anisotropic_degree(2)
321 mgr
.set_graphic(name
, graphic
)
323 graphic
.set_transparency(True)
324 graphic
.detach_node()
325 frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
326 frameSize
=(-.6, .6, -.3, .3))
327 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
329 font
.set_pixels_per_unit(60)
330 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
331 font
.set_outline((0, 0, 0, 1), .8, .2)
332 txt
= _('keep \5mouse_l\5 pressed to drag an item\n\n'
333 'keep \5mouse_r\5 pressed to rotate an item')
334 self
._txt
= OnscreenText(
335 txt
, parent
=frm
, font
=font
, scale
=0.06, fg
=(.9, .9, .9, 1),
336 align
=TextNode
.A_left
)
337 u_l
= self
._txt
.textNode
.get_upper_left_3d()
338 l_r
= self
._txt
.textNode
.get_lower_right_3d()
339 w
, h
= l_r
[0] - u_l
[0], u_l
[2] - l_r
[2]
342 z
= h
/ 2 - font
.get_line_height() * self
._txt
['scale'][1]
343 z
+= (btn_scale
+ 2 * mar
) / 2
344 self
._txt
['pos'] = -w
/ 2, z
345 u_l
= self
._txt
.textNode
.get_upper_left_3d()
346 l_r
= self
._txt
.textNode
.get_lower_right_3d()
347 c_l_r
= l_r
[0], l_r
[1], l_r
[2] - 2 * mar
- btn_scale
348 fsz
= u_l
[0] - mar
, l_r
[0] + mar
, c_l_r
[2] - mar
, u_l
[2] + mar
349 frm
['frameSize'] = fsz
351 (.6, .6, .6, 1), # ready
352 (1, 1, 1, 1), # press
353 (.8, .8, .8, 1), # rollover
355 imgs
= [self
.__load
_img
_btn
('exitRight', col
) for col
in colors
]
357 image
=imgs
, scale
=btn_scale
,
358 pos
=(l_r
[0] - btn_scale
, 1, l_r
[2] - mar
- btn_scale
),
359 parent
=frm
, command
=self
.__on
_close
_instructions
, extraArgs
=[frm
],
360 relief
=FLAT
, frameColor
=(.6, .6, .6, .08),
361 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
362 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
363 btn
.set_transparency(True)
365 def __store_state(self
):
367 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
368 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
369 self
.__btn
_state
= [btn
['state'] for btn
in btns
]
371 btn
['state'] = DISABLED
372 [itm
.store_state() for itm
in self
.items
]
374 def __restore_state(self
):
376 self
.__home
_btn
, self
.__info
_btn
, self
.__right
_btn
,
377 self
.__next
_btn
, self
.__prev
_btn
, self
.__rewind
_btn
]
378 for btn
, state
in zip(btns
, self
.__btn
_state
):
380 [itm
.restore_state() for itm
in self
.items
]
383 def __on_close_instructions(self
, frm
):
385 self
.__restore
_state
()