37e653268c9674703a809dbe542447ae1b16bfb6
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
):
18 def win_condition(self
):
24 def __init__(self
, np
):
29 def win_condition(self
):
30 self
._positions
+= [self
._np
.get_pos()]
31 self
._rotations
+= [self
._np
.get_hpr()]
32 if len(self
._positions
) > 30:
33 self
._positions
.pop(0)
34 if len(self
._rotations
) > 30:
35 self
._rotations
.pop(0)
36 if len(self
._positions
) < 28:
38 avg_x
= sum(pos
.x
for pos
in self
._positions
) / len(self
._positions
)
39 avg_y
= sum(pos
.y
for pos
in self
._positions
) / len(self
._positions
)
40 avg_z
= sum(pos
.z
for pos
in self
._positions
) / len(self
._positions
)
41 avg_h
= sum(rot
.x
for rot
in self
._rotations
) / len(self
._rotations
)
42 avg_p
= sum(rot
.y
for rot
in self
._rotations
) / len(self
._rotations
)
43 avg_r
= sum(rot
.z
for rot
in self
._rotations
) / len(self
._rotations
)
44 avg_pos
= Point3(avg_x
, avg_y
, avg_z
)
45 avg_rot
= Point3(avg_h
, avg_p
, avg_r
)
46 return all((pos
- avg_pos
).length() < .1 for pos
in self
._positions
) and \
47 all((rot
- avg_rot
).length() < 1 for rot
in self
._rotations
)
50 class Item(DirectObject
):
52 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):
55 self
._plane
_node
= plane_node
57 self
._cb
_inst
= cb_inst
59 self
._overlapping
= False
60 self
._first
_command
= True
61 self
.repos_done
= False
66 self
.strategy
= FixedStrategy()
67 self
._exp
_num
_contacts
= exp_num_contacts
68 self
._curr
_bottom
= curr_bottom
69 self
._scene
_repos
= scene_repos
70 self
._model
_scale
= model_scale
71 self
._model
_path
= model_path
73 self
._command
_idx
= -1
74 self
._restitution
= restitution
75 self
._friction
= friction
79 self
.node
= BulletGhostNode(self
.__class
__.__name
__)
81 self
.node
= BulletRigidBodyNode(self
.__class
__.__name
__)
82 self
._set
_shape
(count
)
83 self
._np
= render
.attach_new_node(self
.node
)
85 world
.attach_ghost(self
.node
)
87 world
.attach_rigid_body(self
.node
)
88 self
._model
= loader
.load_model(model_path
)
90 self
._model
.flatten_light()
91 self
._model
.reparent_to(self
._np
)
92 self
._np
.set_scale(model_scale
)
93 self
._np
.flatten_strong()
94 self
._set
_outline
_model
()
95 set_srgb(self
._outline
_model
)
96 self
._model
.hide(BitMask32(0x01))
97 self
._outline
_model
.hide(BitMask32(0x01))
98 self
._start
_drag
_pos
= None
99 self
._prev
_rot
_info
= None
100 self
._last
_nonoverlapping
_pos
= None
101 self
._last
_nonoverlapping
_rot
= None
102 self
._instantiated
= not count
103 self
._box
_tsk
= taskMgr
.add(self
.on_frame
, 'on_frame')
107 self
._np
.set_pos(pos
)
109 self
.__editing
= False
110 self
.accept('editor-start', self
.__editor
_start
)
111 self
.accept('editor-stop', self
.__editor
_stop
)
113 def _set_shape(self
, apply_scale
=True):
116 def __editor_start(self
):
117 self
.__editing
= True
119 def __editor_stop(self
):
120 self
.__editing
= False
123 def interactable(self
):
124 return self
.__editing
or self
.count
126 def set_strategy(self
, strategy
):
127 self
.strategy
= strategy
130 p_from
, p_to
= P3dGfxMgr
.world_from_to((-1, 1))
131 for hit
in self
._world
.ray_test_all(p_from
, p_to
).get_hits():
132 if hit
.get_node() == self
._plane
_node
:
133 pos
= hit
.get_hit_pos()
134 #corner = P3dGfxMgr.screen_coord(pos)
135 bounds
= self
._np
.get_tight_bounds()
136 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
137 self
._np
.set_pos(pos
)
138 plane
= Plane(Vec3(0, 1, 0), bounds
[0][1])
139 pos3d
, near_pt
, far_pt
= Point3(), Point3(), Point3()
140 margin
, ar
= .04, base
.get_aspect_ratio()
141 margin_x
= margin
/ ar
if ar
>= 1 else margin
142 margin_z
= margin
* ar
if ar
< 1 else margin
143 top
= self
._curr
_bottom
()
144 base
.camLens
.extrude((-1 + margin_x
, top
- margin_z
), near_pt
, far_pt
)
145 plane
.intersects_line(
146 pos3d
, render
.get_relative_point(base
.camera
, near_pt
),
147 render
.get_relative_point(base
.camera
, far_pt
))
148 delta
= Vec3(bounds
[1][0], bounds
[1][1], bounds
[0][2])
149 self
._np
.set_pos(pos3d
+ delta
)
150 if not hasattr(self
, '_txt'):
151 font
= base
.loader
.load_font('assets/fonts/Hanken-Book.ttf')
153 font
.set_pixels_per_unit(60)
154 font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
155 font
.set_outline((0, 0, 0, 1), .8, .2)
156 self
._txt
= OnscreenText(
157 str(self
._count
), font
=font
, scale
=0.06, fg
=(.9, .9, .9, 1))
158 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[0][1], bounds
[0][2])
159 p2d
= P3dGfxMgr
.screen_coord(pos
)
160 self
._txt
['pos'] = p2d
161 self
.repos_done
= True
163 def repos_x(self
, x
):
165 bounds
= self
._np
.get_tight_bounds()
166 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
167 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[0][1], bounds
[0][2])
168 p2d
= P3dGfxMgr
.screen_coord(pos
)
169 self
._txt
['pos'] = p2d
171 def get_bottom(self
):
172 bounds
= self
._np
.get_tight_bounds()
173 bounds
= bounds
[0] - self
._np
.get_pos(), bounds
[1] - self
._np
.get_pos()
174 pos
= self
._np
.get_pos() + (bounds
[1][0], bounds
[1][1], bounds
[0][2])
175 p2d
= P3dGfxMgr
.screen_coord(pos
)
176 ar
= base
.get_aspect_ratio()
177 return p2d
[1] if ar
>= 1 else (p2d
[1] * ar
)
179 def get_corner(self
):
180 bounds
= self
._np
.get_tight_bounds()
181 return bounds
[1][0], bounds
[1][1], bounds
[0][2]
183 def _set_outline_model(self
):
184 self
._outline
_model
= loader
.load_model(self
._model
_path
)
185 #clockw = CullFaceAttrib.MCullClockwise
186 #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
187 self
._outline
_model
.set_attrib(CullFaceAttrib
.make_reverse())
188 self
._outline
_model
.reparent_to(self
._np
)
189 self
._outline
_model
.set_scale(1.08)
190 self
._outline
_model
.set_light_off()
191 self
._outline
_model
.set_color(.4, .4, .4, 1)
192 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
193 self
._outline
_model
.hide()
195 def on_frame(self
, task
):
200 self
._command
_idx
-= 1
201 self
._np
.set_pos(self
._commands
[self
._command
_idx
].pos
)
202 self
._np
.set_hpr(self
._commands
[self
._command
_idx
].rot
)
205 self
._command
_idx
+= 1
206 self
._np
.set_pos(self
._commands
[self
._command
_idx
].pos
)
207 self
._np
.set_hpr(self
._commands
[self
._command
_idx
].rot
)
210 if not self
._instantiated
:
212 self
._world
.remove_rigid_body(self
.node
)
213 self
.node
.set_mass(self
._mass
)
214 self
._world
.attach_rigid_body(self
.node
)
215 self
.node
.set_restitution(self
._restitution
)
216 self
.node
.set_friction(self
._friction
)
218 def on_click_l(self
, pos
):
219 if self
._paused
: return
221 messenger
.send('editor-item-click-l', [self
])
222 self
._start
_drag
_pos
= pos
, self
._np
.get_pos()
223 loader
.load_sfx('assets/audio/sfx/grab.ogg').play()
224 if not self
._instantiated
:
225 self
._world
.remove_ghost(self
.node
)
226 pos
= self
._np
.get_pos()
227 self
._np
.remove_node()
228 self
.node
= BulletRigidBodyNode('box')
230 self
._np
= render
.attach_new_node(self
.node
)
231 self
._world
.attach_rigid_body(self
.node
)
232 self
._model
.reparent_to(self
._np
)
233 self
._np
.set_pos(pos
)
234 self
._set
_outline
_model
()
235 self
._np
.set_scale(self
._model
_scale
)
236 self
._model
.show(BitMask32(0x01))
237 self
._outline
_model
.hide(BitMask32(0x01))
238 self
._instantiated
= True
242 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
246 def on_click_r(self
, pos
):
247 if self
._paused
or not self
._instantiated
: return
248 self
._prev
_rot
_info
= pos
, self
._np
.get_pos(), self
._np
.get_r()
249 loader
.load_sfx('assets/audio/sfx/grab.ogg').play()
251 def on_release(self
):
252 if self
._start
_drag
_pos
or self
._prev
_rot
_info
:
253 loader
.load_sfx('assets/audio/sfx/release.ogg').play()
254 self
._command
_idx
+= 1
255 self
._commands
= self
._commands
[:self
._command
_idx
]
256 self
._commands
+= [Command(self
._np
.get_pos(), self
._np
.get_hpr())]
257 self
._first
_command
= False
258 self
._start
_drag
_pos
= self
._prev
_rot
_info
= None
259 if self
._overlapping
:
260 self
._np
.set_pos(self
._last
_nonoverlapping
_pos
)
261 self
._np
.set_hpr(self
._last
_nonoverlapping
_rot
)
262 self
._outline
_model
.set_color(.4, .4, .4, 1)
263 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
264 self
._overlapping
= False
266 def on_mouse_on(self
):
267 if not self
._paused
and self
.interactable
:
268 self
._outline
_model
.show()
270 def on_mouse_off(self
):
271 if self
._start
_drag
_pos
or self
._prev
_rot
_info
: return
272 if self
.interactable
:
273 self
._outline
_model
.hide()
275 def on_mouse_move(self
, pos
):
276 if self
._start
_drag
_pos
:
277 d_pos
= pos
- self
._start
_drag
_pos
[0]
278 self
._np
.set_pos(self
._start
_drag
_pos
[1] + d_pos
)
279 if self
._prev
_rot
_info
:
280 start_vec
= self
._prev
_rot
_info
[0] - self
._prev
_rot
_info
[1]
281 curr_vec
= pos
- self
._prev
_rot
_info
[1]
282 d_angle
= curr_vec
.signed_angle_deg(start_vec
, (0, -1, 0))
283 self
._np
.set_r(self
._prev
_rot
_info
[2] + d_angle
)
284 self
._prev
_rot
_info
= pos
, self
._np
.get_pos(), self
._np
.get_r()
285 if self
._start
_drag
_pos
or self
._prev
_rot
_info
:
286 res
= self
._world
.contact_test(self
.node
)
287 nres
= res
.get_num_contacts()
288 if nres
<= self
._exp
_num
_contacts
:
289 self
._overlapping
= False
290 self
._outline
_model
.set_color(.4, .4, .4, 1)
291 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
292 if nres
> self
._exp
_num
_contacts
and not self
._overlapping
:
294 for contact
in res
.get_contacts():
295 for node
in [contact
.get_node0(), contact
.get_node1()]:
296 if isinstance(node
, BulletRigidBodyNode
) and \
300 self
._overlapping
= True
301 loader
.load_sfx('assets/audio/sfx/overlap.ogg').play()
302 self
._outline
_model
.set_color(.9, .1, .1, 1)
303 self
._outline
_model
.set_color_scale(.9, .1, .1, 1)
304 if not self
._overlapping
:
305 self
._last
_nonoverlapping
_pos
= self
._np
.get_pos()
306 self
._last
_nonoverlapping
_rot
= self
._np
.get_hpr()
307 messenger
.send('item-rototranslated', [self
._np
])
309 def on_aspect_ratio_changed(self
):
310 if not self
._instantiated
:
313 def store_state(self
):
315 self
._model
.set_transparency(True)
316 self
._model
.set_alpha_scale(.3)
317 if hasattr(self
, '_txt') and not self
._txt
.is_empty():
318 self
._txt
.set_alpha_scale(.3)
320 def restore_state(self
):
322 self
._model
.set_alpha_scale(1)
323 if hasattr(self
, '_txt') and not self
._txt
.is_empty():
324 self
._txt
.set_alpha_scale(1)
326 def fail_condition(self
):
327 if self
._np
.get_z() < -6:
329 self
._positions
+= [self
._np
.get_pos()]
330 self
._rotations
+= [self
._np
.get_hpr()]
331 if len(self
._positions
) > 30:
332 self
._positions
.pop(0)
333 if len(self
._rotations
) > 30:
334 self
._rotations
.pop(0)
335 if len(self
._positions
) < 28:
337 avg_x
= sum(pos
.x
for pos
in self
._positions
) / len(self
._positions
)
338 avg_y
= sum(pos
.y
for pos
in self
._positions
) / len(self
._positions
)
339 avg_z
= sum(pos
.z
for pos
in self
._positions
) / len(self
._positions
)
340 avg_h
= sum(rot
.x
for rot
in self
._rotations
) / len(self
._rotations
)
341 avg_p
= sum(rot
.y
for rot
in self
._rotations
) / len(self
._rotations
)
342 avg_r
= sum(rot
.z
for rot
in self
._rotations
) / len(self
._rotations
)
343 avg_pos
= Point3(avg_x
, avg_y
, avg_z
)
344 avg_rot
= Point3(avg_h
, avg_p
, avg_r
)
345 return all((pos
- avg_pos
).length() < .1 for pos
in self
._positions
) and \
346 all((rot
- avg_rot
).length() < 1 for rot
in self
._rotations
)
355 self
.json
['mass'] = val
358 def restitution(self
):
359 return self
._restitution
362 def restitution(self
, val
):
363 self
._restitution
= val
364 self
.json
['restitution'] = val
368 return self
._friction
371 def friction(self
, val
):
373 self
.json
['friction'] = val
380 def position(self
, val
):
381 self
._np
.set_pos(*val
)
382 self
.json
['position'] = val
386 return self
._np
.get_r()
391 self
.json
['roll'] = val
395 return self
._np
.get_scale()[0]
398 def scale(self
, val
):
399 self
._np
.set_scale(val
)
400 self
.json
['model_scale'] = val
403 self
._np
.remove_node()
404 taskMgr
.remove(self
._box
_tsk
)
405 if hasattr(self
, '_txt'):
407 if not self
._instantiated
:
408 self
._world
.remove_ghost(self
.node
)
410 self
._world
.remove_rigid_body(self
.node
)