1 from panda3d
.core
import CullFaceAttrib
, Point3
, Texture
, \
3 from panda3d
.bullet
import BulletRigidBodyNode
, BulletGhostNode
4 from direct
.gui
.OnscreenText
import OnscreenText
5 from direct
.showbase
.DirectObject
import DirectObject
6 from ya2
.p3d
.gfx
import P3dGfxMgr
, set_srgb
11 def __init__(self
, pos
, rot
):
16 class ItemStrategy
: pass
19 class FixedStrategy(ItemStrategy
):
21 def win_condition(self
):
25 class StillStrategy(ItemStrategy
):
27 def __init__(self
, np
):
32 def win_condition(self
):
33 self
._positions
+= [self
._np
.get_pos()]
34 self
._rotations
+= [self
._np
.get_hpr()]
35 if len(self
._positions
) > 30:
36 self
._positions
.pop(0)
37 if len(self
._rotations
) > 30:
38 self
._rotations
.pop(0)
39 if len(self
._positions
) < 28:
41 avg_x
= sum(pos
.x
for pos
in self
._positions
) / len(self
._positions
)
42 avg_y
= sum(pos
.y
for pos
in self
._positions
) / len(self
._positions
)
43 avg_z
= sum(pos
.z
for pos
in self
._positions
) / len(self
._positions
)
44 avg_h
= sum(rot
.x
for rot
in self
._rotations
) / len(self
._rotations
)
45 avg_p
= sum(rot
.y
for rot
in self
._rotations
) / len(self
._rotations
)
46 avg_r
= sum(rot
.z
for rot
in self
._rotations
) / len(self
._rotations
)
47 avg_pos
= Point3(avg_x
, avg_y
, avg_z
)
48 avg_rot
= Point3(avg_h
, avg_p
, avg_r
)
49 return all((pos
- avg_pos
).length() < .1 for pos
in self
._positions
) and \
50 all((rot
- avg_rot
).length() < 1 for rot
in self
._rotations
)
53 class Item(DirectObject
):
55 def __init__(self
, world
, plane_node
, cb_inst
, curr_bottom
, scene_repos
, model_path
, json
, model_scale
=1, exp_num_contacts
=1, mass
=1, pos
=(0, 0, 0), r
=0, count
=0, restitution
=.5, friction
=.5):
58 self
._plane
_node
= plane_node
60 self
._cb
_inst
= cb_inst
62 self
._overlapping
= False
63 self
._first
_command
= True
64 self
.repos_done
= False
69 self
.strategy
= FixedStrategy()
70 self
._exp
_num
_contacts
= exp_num_contacts
71 self
._curr
_bottom
= curr_bottom
72 self
._scene
_repos
= scene_repos
73 self
._model
_scale
= model_scale
74 self
._model
_path
= model_path
76 self
._command
_idx
= -1
77 self
._restitution
= restitution
78 self
._friction
= friction
82 self
.node
= BulletGhostNode(self
.__class
__.__name
__)
84 self
.node
= BulletRigidBodyNode(self
.__class
__.__name
__)
85 self
._set
_shape
(count
)
86 self
._np
= render
.attach_new_node(self
.node
)
88 world
.attach_ghost(self
.node
)
90 world
.attach_rigid_body(self
.node
)
91 self
._model
= loader
.load_model(model_path
)
93 self
._model
.flatten_light()
94 self
._model
.reparent_to(self
._np
)
95 self
._np
.set_scale(model_scale
)
96 self
._np
.flatten_strong()
97 self
._set
_outline
_model
()
98 set_srgb(self
._outline
_model
)
99 self
._model
.hide(BitMask32(0x01))
100 self
._outline
_model
.hide(BitMask32(0x01))
101 self
._start
_drag
_pos
= None
102 self
._prev
_rot
_info
= None
103 self
._last
_nonoverlapping
_pos
= None
104 self
._last
_nonoverlapping
_rot
= None
105 self
._instantiated
= not count
106 self
._box
_tsk
= taskMgr
.add(self
.on_frame
, 'on_frame')
110 self
._np
.set_pos(pos
)
112 self
.__editing
= False
113 self
.accept('editor-start', self
.__editor
_start
)
114 self
.accept('editor-stop', self
.__editor
_stop
)
116 def _set_shape(self
, apply_scale
=True):
119 def __editor_start(self
):
120 self
.__editing
= True
122 def __editor_stop(self
):
123 self
.__editing
= False
126 def interactable(self
):
127 return self
.__editing
or self
.count
129 def set_strategy(self
, strategy
):
130 self
.strategy
= strategy
133 p_from
, p_to
= P3dGfxMgr
.world_from_to((-1, 1))
134 for hit
in self
._world
.ray_test_all(p_from
, p_to
).get_hits():
135 if hit
.get_node() == self
._plane
_node
:
136 pos
= hit
.get_hit_pos()
137 #corner = P3dGfxMgr.screen_coord(pos)
138 bounds
= self
._np
.get_tight_bounds()
139 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
140 self
._np
.set_pos(pos
)
141 plane
= Plane(Vec3(0, 1, 0), bounds
[0][1])
142 pos3d
, near_pt
, far_pt
= Point3(), Point3(), Point3()
143 margin
, ar
= .04, base
.get_aspect_ratio()
144 margin_x
= margin
/ ar
if ar
>= 1 else margin
145 margin_z
= margin
* ar
if ar
< 1 else margin
146 top
= self
._curr
_bottom
()
147 base
.camLens
.extrude((-1 + margin_x
, top
- margin_z
), near_pt
, far_pt
)
148 plane
.intersects_line(
149 pos3d
, render
.get_relative_point(base
.camera
, near_pt
),
150 render
.get_relative_point(base
.camera
, far_pt
))
151 delta
= Vec3(bounds
[1][0], bounds
[1][1], bounds
[0][2])
152 self
._np
.set_pos(pos3d
+ delta
)
153 if not hasattr(self
, '_txt'):
154 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
156 font
.set_pixels_per_unit(60)
157 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
158 font
.set_outline((0, 0, 0, 1), .8, .2)
159 self
._txt
= OnscreenText(
160 str(self
._count
), font
=font
, scale
=0.06, fg
=(.9, .9, .9, 1))
161 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[0][1], bounds
[0][2])
162 p2d
= P3dGfxMgr
.screen_coord(pos
)
163 self
._txt
['pos'] = p2d
164 self
.repos_done
= True
166 def repos_x(self
, x
):
168 bounds
= self
._np
.get_tight_bounds()
169 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
170 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[0][1], bounds
[0][2])
171 p2d
= P3dGfxMgr
.screen_coord(pos
)
172 self
._txt
['pos'] = p2d
174 def get_bottom(self
):
175 bounds
= self
._np
.get_tight_bounds()
176 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
177 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[1][1], bounds
[0][2])
178 p2d
= P3dGfxMgr
.screen_coord(pos
)
179 ar
= base
.get_aspect_ratio()
180 return p2d
[1] if ar
>= 1 else (p2d
[1] * ar
)
182 def get_corner(self
):
183 bounds
= self
._np
.get_tight_bounds()
184 return bounds
[1][0], bounds
[1][1], bounds
[0][2]
186 def _set_outline_model(self
):
187 self
._outline
_model
= loader
.load_model(self
._model
_path
)
188 #clockw = CullFaceAttrib.MCullClockwise
189 #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
190 self
._outline
_model
.set_attrib(CullFaceAttrib
.make_reverse())
191 self
._outline
_model
.reparent_to(self
._np
)
192 self
._outline
_model
.set_scale(1.08)
193 self
._outline
_model
.set_light_off()
194 self
._outline
_model
.set_color(.4, .4, .4, 1)
195 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
196 self
._outline
_model
.hide()
198 def on_frame(self
, task
):
203 self
._command
_idx
-= 1
204 self
._np
.set_pos(self
._commands
[self
._command
_idx
].pos
)
205 self
._np
.set_hpr(self
._commands
[self
._command
_idx
].rot
)
208 self
._command
_idx
+= 1
209 self
._np
.set_pos(self
._commands
[self
._command
_idx
].pos
)
210 self
._np
.set_hpr(self
._commands
[self
._command
_idx
].rot
)
213 if not self
._instantiated
:
215 self
._world
.remove_rigid_body(self
.node
)
216 self
.node
.set_mass(self
._mass
)
217 self
._world
.attach_rigid_body(self
.node
)
218 self
.node
.set_restitution(self
._restitution
)
219 self
.node
.set_friction(self
._friction
)
221 def on_click_l(self
, pos
):
222 if self
._paused
: return
224 messenger
.send('editor-item-click', [self
])
225 self
._start
_drag
_pos
= pos
, self
._np
.get_pos()
226 loader
.load_sfx('assets/audio/sfx/grab.ogg').play()
227 if not self
._instantiated
:
228 self
._world
.remove_ghost(self
.node
)
229 pos
= self
._np
.get_pos()
230 self
._np
.remove_node()
231 self
.node
= BulletRigidBodyNode('box')
233 self
._np
= render
.attach_new_node(self
.node
)
234 self
._world
.attach_rigid_body(self
.node
)
235 self
._model
.reparent_to(self
._np
)
236 self
._np
.set_pos(pos
)
237 self
._set
_outline
_model
()
238 self
._np
.set_scale(self
._model
_scale
)
239 self
._model
.show(BitMask32(0x01))
240 self
._outline
_model
.hide(BitMask32(0x01))
241 self
._instantiated
= True
245 item
= self
.__class
__(self
._world
, self
._plane
_node
, self
._cb
_inst
, self
._curr
_bottom
, self
._scene
_repos
, None, count
=self
._count
, mass
=self
._mass
, pos
=self
._pos
, r
=self
._r
) # pylint: disable=no-value-for-parameter
249 def on_click_r(self
, pos
):
250 if self
._paused
or not self
._instantiated
: return
252 messenger
.send('editor-item-click', [self
])
253 self
._prev
_rot
_info
= pos
, self
._np
.get_pos(), self
._np
.get_r()
254 loader
.load_sfx('assets/audio/sfx/grab.ogg').play()
256 def on_release(self
):
257 if self
._start
_drag
_pos
or self
._prev
_rot
_info
:
258 loader
.load_sfx('assets/audio/sfx/release.ogg').play()
259 self
._command
_idx
+= 1
260 self
._commands
= self
._commands
[:self
._command
_idx
]
261 self
._commands
+= [Command(self
._np
.get_pos(), self
._np
.get_hpr())]
262 self
._first
_command
= False
263 self
._start
_drag
_pos
= self
._prev
_rot
_info
= None
264 if self
._overlapping
:
265 self
._np
.set_pos(self
._last
_nonoverlapping
_pos
)
266 self
._np
.set_hpr(self
._last
_nonoverlapping
_rot
)
267 self
._outline
_model
.set_color(.4, .4, .4, 1)
268 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
269 self
._overlapping
= False
271 def on_mouse_on(self
):
272 if not self
._paused
and self
.interactable
:
273 self
._outline
_model
.show()
275 def on_mouse_off(self
):
276 if self
._start
_drag
_pos
or self
._prev
_rot
_info
: return
277 if self
.interactable
:
278 self
._outline
_model
.hide()
280 def on_mouse_move(self
, pos
):
281 if self
._start
_drag
_pos
:
282 d_pos
= pos
- self
._start
_drag
_pos
[0]
283 self
._np
.set_pos(self
._start
_drag
_pos
[1] + d_pos
)
284 if self
._prev
_rot
_info
:
285 start_vec
= self
._prev
_rot
_info
[0] - self
._prev
_rot
_info
[1]
286 curr_vec
= pos
- self
._prev
_rot
_info
[1]
287 d_angle
= curr_vec
.signed_angle_deg(start_vec
, (0, -1, 0))
288 self
._np
.set_r(self
._prev
_rot
_info
[2] + d_angle
)
289 self
._prev
_rot
_info
= pos
, self
._np
.get_pos(), self
._np
.get_r()
290 if self
._start
_drag
_pos
or self
._prev
_rot
_info
:
291 res
= self
._world
.contact_test(self
.node
)
292 nres
= res
.get_num_contacts()
293 if nres
<= self
._exp
_num
_contacts
:
294 self
._overlapping
= False
295 self
._outline
_model
.set_color(.4, .4, .4, 1)
296 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
297 if nres
> self
._exp
_num
_contacts
and not self
._overlapping
:
299 for contact
in res
.get_contacts():
300 for node
in [contact
.get_node0(), contact
.get_node1()]:
301 if isinstance(node
, BulletRigidBodyNode
) and \
305 self
._overlapping
= True
306 loader
.load_sfx('assets/audio/sfx/overlap.ogg').play()
307 self
._outline
_model
.set_color(.9, .1, .1, 1)
308 self
._outline
_model
.set_color_scale(.9, .1, .1, 1)
309 if not self
._overlapping
:
310 self
._last
_nonoverlapping
_pos
= self
._np
.get_pos()
311 self
._last
_nonoverlapping
_rot
= self
._np
.get_hpr()
312 messenger
.send('item-rototranslated', [self
._np
])
314 def on_aspect_ratio_changed(self
):
315 if not self
._instantiated
:
318 def store_state(self
):
320 self
._model
.set_transparency(True)
321 self
._model
.set_alpha_scale(.3)
322 if hasattr(self
, '_txt') and not self
._txt
.is_empty():
323 self
._txt
.set_alpha_scale(.3)
325 def restore_state(self
):
327 self
._model
.set_alpha_scale(1)
328 if hasattr(self
, '_txt') and not self
._txt
.is_empty():
329 self
._txt
.set_alpha_scale(1)
331 def fail_condition(self
):
332 if self
._np
.get_z() < -6:
334 self
._positions
+= [self
._np
.get_pos()]
335 self
._rotations
+= [self
._np
.get_hpr()]
336 if len(self
._positions
) > 30:
337 self
._positions
.pop(0)
338 if len(self
._rotations
) > 30:
339 self
._rotations
.pop(0)
340 if len(self
._positions
) < 28:
342 avg_x
= sum(pos
.x
for pos
in self
._positions
) / len(self
._positions
)
343 avg_y
= sum(pos
.y
for pos
in self
._positions
) / len(self
._positions
)
344 avg_z
= sum(pos
.z
for pos
in self
._positions
) / len(self
._positions
)
345 avg_h
= sum(rot
.x
for rot
in self
._rotations
) / len(self
._rotations
)
346 avg_p
= sum(rot
.y
for rot
in self
._rotations
) / len(self
._rotations
)
347 avg_r
= sum(rot
.z
for rot
in self
._rotations
) / len(self
._rotations
)
348 avg_pos
= Point3(avg_x
, avg_y
, avg_z
)
349 avg_rot
= Point3(avg_h
, avg_p
, avg_r
)
350 return all((pos
- avg_pos
).length() < .1 for pos
in self
._positions
) and \
351 all((rot
- avg_rot
).length() < 1 for rot
in self
._rotations
)
360 self
.json
['mass'] = val
363 def restitution(self
):
364 return self
._restitution
367 def restitution(self
, val
):
368 self
._restitution
= val
369 self
.json
['restitution'] = val
373 return self
._friction
376 def friction(self
, val
):
378 self
.json
['friction'] = val
382 return self
._np
.get_pos()
385 def position(self
, val
):
386 self
._np
.set_pos(*val
)
387 self
.json
['position'] = val
391 return self
._np
.get_r()
396 self
.json
['roll'] = val
400 return self
._np
.get_scale()[0]
403 def scale(self
, val
):
404 self
._np
.set_scale(val
)
405 self
.json
['model_scale'] = val
409 if 'id' in self
.json
:
410 return self
.json
['id']
415 self
.json
['id'] = val
418 def strategy_json(self
):
419 return self
.json
['strategy']
421 @strategy_json.setter
422 def strategy_json(self
, val
):
423 self
.json
['strategy'] = val
426 def strategy_args_json(self
):
427 return self
.json
['strategy_args']
429 @strategy_args_json.setter
430 def strategy_args_json(self
, val
):
431 self
.json
['strategy_args'] = val
.split(' ')
434 self
._np
.remove_node()
435 taskMgr
.remove(self
._box
_tsk
)
436 if hasattr(self
, '_txt'):
438 if not self
._instantiated
:
439 self
._world
.remove_ghost(self
.node
)
441 self
._world
.remove_rigid_body(self
.node
)
442 self
.ignore('editor-start')
443 self
.ignore('editor-stop')