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
.utils
.gfx
import GfxTools
, Point
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
= GfxTools
.build_node_from_physics(self
.node
)
88 world
.attach_ghost(self
.node
)
90 world
.attach_rigid_body(self
.node
)
91 self
._model
= GfxTools
.build_model(model_path
)
92 self
._model
.set_srgb_textures()
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 self
._outline
_model
.set_srgb_textures()
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
= (0, 0, 0)
104 self
._last
_nonoverlapping
_rot
= 0
105 self
._instantiated
= not count
106 self
._box
_tsk
= taskMgr
.add(self
.on_frame
, 'item_on_frame')
110 self
._np
.set_pos(pos
)
112 self
.__editing
= False
113 self
.__interactable
= count
114 self
.accept('editor-start', self
.__editor
_start
)
115 self
.accept('editor-stop', self
.__editor
_stop
)
117 def _set_shape(self
, apply_scale
=True):
120 def __editor_start(self
):
121 self
.__editing
= True
123 def __editor_stop(self
):
124 self
.__editing
= False
127 def interactable(self
):
128 return self
.__editing
or self
.__interactable
130 def set_strategy(self
, strategy
):
131 self
.strategy
= strategy
134 p_from
, p_to
= Point((-1, 1)).from_to_points()
135 for hit
in self
._world
.ray_test_all(p_from
, p_to
).get_hits():
136 if hit
.get_node() == self
._plane
_node
:
137 pos
= hit
.get_hit_pos()
138 #corner = P3dGfxMgr.screen_coord(pos)
139 bounds
= self
._np
.get_tight_bounds()
140 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
141 self
._np
.set_pos(pos
)
142 plane
= Plane(Vec3(0, 1, 0), bounds
[0][1])
143 pos3d
, near_pt
, far_pt
= Point3(), Point3(), Point3()
144 margin
, ar
= .04, base
.get_aspect_ratio()
145 margin_x
= margin
/ ar
if ar
>= 1 else margin
146 margin_z
= margin
* ar
if ar
< 1 else margin
147 top
= self
._curr
_bottom
()
148 base
.camLens
.extrude((-1 + margin_x
, top
- margin_z
), near_pt
, far_pt
)
149 plane
.intersects_line(
150 pos3d
, render
.get_relative_point(base
.camera
, near_pt
),
151 render
.get_relative_point(base
.camera
, far_pt
))
152 delta
= Vec3(bounds
[1][0], bounds
[1][1], bounds
[0][2])
153 self
._np
.set_pos(pos3d
+ delta
)
154 if not hasattr(self
, '_txt'):
155 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
157 font
.set_pixels_per_unit(60)
158 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
159 font
.set_outline((0, 0, 0, 1), .8, .2)
160 self
._txt
= OnscreenText(
161 str(self
._count
), font
=font
, scale
=0.06, fg
=(.9, .9, .9, 1))
162 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[0][1], bounds
[0][2])
163 p2d
= Point(pos
).screen_coord()
164 self
._txt
['pos'] = p2d
165 self
.repos_done
= True
167 def repos_x(self
, x
):
169 bounds
= self
._np
.get_tight_bounds()
170 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
171 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[0][1], bounds
[0][2])
172 p2d
= Point(pos
).screen_coord()
173 self
._txt
['pos'] = p2d
175 def get_bottom(self
):
176 bounds
= self
._np
.get_tight_bounds()
177 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
178 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[1][1], bounds
[0][2])
179 p2d
= Point(pos
).screen_coord()
180 ar
= base
.get_aspect_ratio()
181 return p2d
[1] if ar
>= 1 else (p2d
[1] * ar
)
183 def get_corner(self
):
184 bounds
= self
._np
.get_tight_bounds()
185 return bounds
[1][0], bounds
[1][1], bounds
[0][2]
187 def _set_outline_model(self
):
188 self
._outline
_model
= GfxTools
.build_model(self
._model
_path
)
189 #clockw = CullFaceAttrib.MCullClockwise
190 #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
191 self
._outline
_model
.set_attrib(CullFaceAttrib
.make_reverse())
192 self
._outline
_model
.reparent_to(self
._np
)
193 self
._outline
_model
.set_scale(1.08)
194 self
._outline
_model
.set_light_off()
195 self
._outline
_model
.set_color(.4, .4, .4, 1)
196 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
197 self
._outline
_model
.hide()
199 def on_frame(self
, task
):
204 self
._command
_idx
-= 1
205 self
._np
.set_pos(self
._commands
[self
._command
_idx
].pos
)
206 self
._np
.set_hpr(self
._commands
[self
._command
_idx
].rot
)
209 self
._command
_idx
+= 1
210 self
._np
.set_pos(self
._commands
[self
._command
_idx
].pos
)
211 self
._np
.set_hpr(self
._commands
[self
._command
_idx
].rot
)
214 if not self
._instantiated
:
216 self
._world
.remove_rigid_body(self
.node
)
217 self
.node
.set_mass(self
._mass
)
218 self
._world
.attach_rigid_body(self
.node
)
219 self
.node
.set_restitution(self
._restitution
)
220 self
.node
.set_friction(self
._friction
)
222 def on_click_l(self
, pos
):
223 if self
._paused
: return
225 messenger
.send('editor-item-click', [self
])
226 self
._start
_drag
_pos
= pos
, self
._np
.get_pos()
227 loader
.load_sfx('assets/audio/sfx/grab.ogg').play()
228 if not self
._instantiated
:
229 self
._world
.remove_ghost(self
.node
)
230 pos
= self
._np
.get_pos()
231 self
._np
.remove_node()
232 self
.node
= BulletRigidBodyNode('box')
234 self
._np
= render
.attach_new_node(self
.node
)
235 self
._world
.attach_rigid_body(self
.node
)
236 self
._model
.reparent_to(self
._np
)
237 self
._np
.set_pos(pos
)
238 self
._set
_outline
_model
()
239 self
._np
.set_scale(self
._model
_scale
)
240 self
._model
.show(BitMask32(0x01))
241 self
._outline
_model
.hide(BitMask32(0x01))
242 self
._instantiated
= True
246 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
250 def on_click_r(self
, pos
):
251 if self
._paused
or not self
._instantiated
: return
253 messenger
.send('editor-item-click', [self
])
254 self
._prev
_rot
_info
= pos
, self
._np
.get_pos(), self
._np
.get_r()
255 loader
.load_sfx('assets/audio/sfx/grab.ogg').play()
257 def on_release(self
):
258 if self
._start
_drag
_pos
or self
._prev
_rot
_info
:
259 loader
.load_sfx('assets/audio/sfx/release.ogg').play()
260 self
._command
_idx
+= 1
261 self
._commands
= self
._commands
[:self
._command
_idx
]
262 self
._commands
+= [Command(self
._np
.get_pos(), self
._np
.get_hpr())]
263 self
._first
_command
= False
264 self
._start
_drag
_pos
= self
._prev
_rot
_info
= None
265 if self
._overlapping
:
266 self
._np
.set_pos(self
._last
_nonoverlapping
_pos
)
267 self
._np
.set_hpr(self
._last
_nonoverlapping
_rot
)
268 self
._outline
_model
.set_color(.4, .4, .4, 1)
269 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
270 self
._overlapping
= False
272 def on_mouse_on(self
):
273 if not self
._paused
and self
.interactable
:
274 self
._outline
_model
.show()
276 def on_mouse_off(self
):
277 if self
._start
_drag
_pos
or self
._prev
_rot
_info
: return
278 if self
.interactable
:
279 self
._outline
_model
.hide()
281 def on_mouse_move(self
, pos
):
282 if self
._start
_drag
_pos
:
283 d_pos
= pos
- self
._start
_drag
_pos
[0]
284 self
._np
.set_pos(self
._start
_drag
_pos
[1] + d_pos
)
285 if self
._prev
_rot
_info
:
286 start_vec
= self
._prev
_rot
_info
[0] - self
._prev
_rot
_info
[1]
287 curr_vec
= pos
- self
._prev
_rot
_info
[1]
288 d_angle
= curr_vec
.signed_angle_deg(start_vec
, (0, -1, 0))
289 self
._np
.set_r(self
._prev
_rot
_info
[2] + d_angle
)
290 self
._prev
_rot
_info
= pos
, self
._np
.get_pos(), self
._np
.get_r()
291 if self
._start
_drag
_pos
or self
._prev
_rot
_info
:
292 res
= self
._world
.contact_test(self
.node
)
293 nres
= res
.get_num_contacts()
294 if nres
<= self
._exp
_num
_contacts
:
295 self
._overlapping
= False
296 self
._outline
_model
.set_color(.4, .4, .4, 1)
297 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
298 if nres
> self
._exp
_num
_contacts
and not self
._overlapping
:
300 for contact
in res
.get_contacts():
301 for node
in [contact
.get_node0(), contact
.get_node1()]:
302 if isinstance(node
, BulletRigidBodyNode
) and \
306 self
._overlapping
= True
307 loader
.load_sfx('assets/audio/sfx/overlap.ogg').play()
308 self
._outline
_model
.set_color(.9, .1, .1, 1)
309 self
._outline
_model
.set_color_scale(.9, .1, .1, 1)
310 if not self
._overlapping
:
311 self
._last
_nonoverlapping
_pos
= self
._np
.get_pos()
312 self
._last
_nonoverlapping
_rot
= self
._np
.get_hpr()
313 messenger
.send('item-rototranslated', [self
._np
])
315 def on_aspect_ratio_changed(self
):
316 if not self
._instantiated
:
319 def store_state(self
):
321 self
._model
.set_transparency(True)
322 self
._model
.set_alpha_scale(.3)
323 if hasattr(self
, '_txt') and not self
._txt
.is_empty():
324 self
._txt
.set_alpha_scale(.3)
326 def restore_state(self
):
328 self
._model
.set_alpha_scale(1)
329 if hasattr(self
, '_txt') and not self
._txt
.is_empty():
330 self
._txt
.set_alpha_scale(1)
332 def fail_condition(self
):
333 if self
._np
.get_z() < -6:
335 self
._positions
+= [self
._np
.get_pos()]
336 self
._rotations
+= [self
._np
.get_hpr()]
337 if len(self
._positions
) > 30:
338 self
._positions
.pop(0)
339 if len(self
._rotations
) > 30:
340 self
._rotations
.pop(0)
341 if len(self
._positions
) < 28:
343 avg_x
= sum(pos
.x
for pos
in self
._positions
) / len(self
._positions
)
344 avg_y
= sum(pos
.y
for pos
in self
._positions
) / len(self
._positions
)
345 avg_z
= sum(pos
.z
for pos
in self
._positions
) / len(self
._positions
)
346 avg_h
= sum(rot
.x
for rot
in self
._rotations
) / len(self
._rotations
)
347 avg_p
= sum(rot
.y
for rot
in self
._rotations
) / len(self
._rotations
)
348 avg_r
= sum(rot
.z
for rot
in self
._rotations
) / len(self
._rotations
)
349 avg_pos
= Point3(avg_x
, avg_y
, avg_z
)
350 avg_rot
= Point3(avg_h
, avg_p
, avg_r
)
351 return all((pos
- avg_pos
).length() < .1 for pos
in self
._positions
) and \
352 all((rot
- avg_rot
).length() < 1 for rot
in self
._rotations
)
361 self
.json
['mass'] = val
364 def restitution(self
):
365 return self
._restitution
368 def restitution(self
, val
):
369 self
._restitution
= val
370 self
.json
['restitution'] = val
374 return self
._friction
377 def friction(self
, val
):
379 self
.json
['friction'] = val
383 return self
._np
.get_pos()
386 def position(self
, val
):
387 self
._np
.set_pos(*val
)
388 self
.json
['position'] = val
392 return self
._np
.get_r()
397 self
.json
['roll'] = val
401 return self
._np
.get_scale()[0]
404 def scale(self
, val
):
405 self
._np
.set_scale(val
)
406 self
.json
['model_scale'] = val
410 if 'id' in self
.json
:
411 return self
.json
['id']
416 self
.json
['id'] = val
419 def strategy_json(self
):
420 return self
.json
['strategy']
422 @strategy_json.setter
423 def strategy_json(self
, val
):
424 self
.json
['strategy'] = val
427 def strategy_args_json(self
):
428 return self
.json
['strategy_args']
430 @strategy_args_json.setter
431 def strategy_args_json(self
, val
):
432 self
.json
['strategy_args'] = val
.split(' ')
435 self
._np
.remove_node()
436 taskMgr
.remove(self
._box
_tsk
)
437 if hasattr(self
, '_txt'):
439 if not self
._instantiated
:
440 self
._world
.remove_ghost(self
.node
)
442 self
._world
.remove_rigid_body(self
.node
)
443 self
.ignore('editor-start')
444 self
.ignore('editor-stop')