ya2 · news · projects · code · about

unit test: pyflakes
[pmachines.git] / pmachines / items / item.py
CommitLineData
c991401b 1from panda3d.core import CullFaceAttrib, Point3, Texture, \
d5932612 2 Plane, Vec3, BitMask32
c991401b 3from panda3d.bullet import BulletRigidBodyNode, BulletGhostNode
d5932612 4from direct.gui.OnscreenText import OnscreenText
b35b1f62 5from ya2.p3d.gfx import P3dGfxMgr, set_srgb
d5932612
FC
6
7
8class Command:
9
10 def __init__(self, pos, rot):
11 self.pos = pos
12 self.rot = rot
13
14
0e86689f
FC
15class FixedStrategy:
16
0a0994e4 17 def win_condition(self):
0e86689f
FC
18 return True
19
20
21class StillStrategy:
22
23 def __init__(self, np):
24 self._np = np
25 self._positions = []
26 self._rotations = []
27
0a0994e4 28 def win_condition(self):
0e86689f
FC
29 self._positions += [self._np.get_pos()]
30 self._rotations += [self._np.get_hpr()]
32cd89ca 31 if len(self._positions) > 30:
0e86689f 32 self._positions.pop(0)
32cd89ca 33 if len(self._rotations) > 30:
0e86689f 34 self._rotations.pop(0)
32cd89ca 35 if len(self._positions) < 28:
0e86689f
FC
36 return
37 avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
38 avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
39 avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
40 avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
41 avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
42 avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
43 avg_pos = Point3(avg_x, avg_y, avg_z)
44 avg_rot = Point3(avg_h, avg_p, avg_r)
45 return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
46 all((rot - avg_rot).length() < 1 for rot in self._rotations)
47
48
d5932612
FC
49class Item:
50
32cd89ca 51 def __init__(self, world, plane_node, cb_inst, curr_bottom, scene_repos, model_path, model_scale=1, exp_num_contacts=1, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
d5932612
FC
52 self._world = world
53 self._plane_node = plane_node
54 self._count = count
55 self._cb_inst = cb_inst
56 self._paused = False
57 self._overlapping = False
58 self._first_command = True
59 self.repos_done = False
ef3c36bf
FC
60 self._mass = mass
61 self._pos = pos
62 self._r = r
0e86689f 63 self.strategy = FixedStrategy()
ea38777c 64 self._exp_num_contacts = exp_num_contacts
d5932612 65 self._curr_bottom = curr_bottom
e1438449 66 self._scene_repos = scene_repos
8254721d 67 self._model_scale = model_scale
d5932612
FC
68 self._model_path = model_path
69 self._commands = []
70 self._command_idx = -1
e67dbe53 71 self._restitution = restitution
32cd89ca 72 self._friction = friction
0a0994e4
FC
73 self._positions = []
74 self._rotations = []
ef3c36bf
FC
75 if count:
76 self.node = BulletGhostNode(self.__class__.__name__)
77 else:
78 self.node = BulletRigidBodyNode(self.__class__.__name__)
80d579b1 79 self._set_shape(count)
d5932612 80 self._np = render.attach_new_node(self.node)
ef3c36bf
FC
81 if count:
82 world.attach_ghost(self.node)
83 else:
84 world.attach_rigid_body(self.node)
d5932612
FC
85 self._model = loader.load_model(model_path)
86 set_srgb(self._model)
87 self._model.flatten_light()
88 self._model.reparent_to(self._np)
8254721d 89 self._np.set_scale(model_scale)
80d579b1 90 self._np.flatten_strong()
ef3c36bf
FC
91 if count:
92 self._set_outline_model()
93 set_srgb(self._outline_model)
94 self._model.hide(BitMask32(0x01))
95 self._outline_model.hide(BitMask32(0x01))
d5932612
FC
96 self._start_drag_pos = None
97 self._prev_rot_info = None
98 self._last_nonoverlapping_pos = None
99 self._last_nonoverlapping_rot = None
ef3c36bf
FC
100 self._instantiated = not count
101 self.interactable = count
d5932612 102 self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
ef3c36bf
FC
103 if count:
104 self._repos()
105 else:
106 self._np.set_pos(pos)
107 self._np.set_r(r)
d5932612 108
80d579b1 109 def _set_shape(self, apply_scale=True):
ea38777c 110 pass
d5932612 111
0e86689f
FC
112 def set_strategy(self, strategy):
113 self.strategy = strategy
114
d5932612
FC
115 def _repos(self):
116 p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
117 for hit in self._world.ray_test_all(p_from, p_to).get_hits():
118 if hit.get_node() == self._plane_node:
119 pos = hit.get_hit_pos()
c991401b 120 #corner = P3dGfxMgr.screen_coord(pos)
d5932612
FC
121 bounds = self._np.get_tight_bounds()
122 bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
123 self._np.set_pos(pos)
124 plane = Plane(Vec3(0, 1, 0), bounds[0][1])
125 pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
126 margin, ar = .04, base.get_aspect_ratio()
127 margin_x = margin / ar if ar >= 1 else margin
128 margin_z = margin * ar if ar < 1 else margin
129 top = self._curr_bottom()
130 base.camLens.extrude((-1 + margin_x, top - margin_z), near_pt, far_pt)
131 plane.intersects_line(
132 pos3d, render.get_relative_point(base.camera, near_pt),
133 render.get_relative_point(base.camera, far_pt))
134 delta = Vec3(bounds[1][0], bounds[1][1], bounds[0][2])
135 self._np.set_pos(pos3d + delta)
136 if not hasattr(self, '_txt'):
137 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
138 font.clear()
139 font.set_pixels_per_unit(60)
140 font.set_minfilter(Texture.FTLinearMipmapLinear)
141 font.set_outline((0, 0, 0, 1), .8, .2)
142 self._txt = OnscreenText(
143 str(self._count), font=font, scale=0.06, fg=(.9, .9, .9, 1))
144 pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
145 p2d = P3dGfxMgr.screen_coord(pos)
146 self._txt['pos'] = p2d
147 self.repos_done = True
148
e1438449
FC
149 def repos_x(self, x):
150 self._np.set_x(x)
151 bounds = self._np.get_tight_bounds()
152 bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
153 pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
154 p2d = P3dGfxMgr.screen_coord(pos)
155 self._txt['pos'] = p2d
156
d5932612
FC
157 def get_bottom(self):
158 bounds = self._np.get_tight_bounds()
159 bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
160 pos = self._np.get_pos() + (bounds[1][0], bounds[1][1], bounds[0][2])
161 p2d = P3dGfxMgr.screen_coord(pos)
162 ar = base.get_aspect_ratio()
163 return p2d[1] if ar >= 1 else (p2d[1] * ar)
164
165 def get_corner(self):
166 bounds = self._np.get_tight_bounds()
167 return bounds[1][0], bounds[1][1], bounds[0][2]
168
169 def _set_outline_model(self):
170 self._outline_model = loader.load_model(self._model_path)
ea38777c
FC
171 #clockw = CullFaceAttrib.MCullClockwise
172 #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
173 self._outline_model.set_attrib(CullFaceAttrib.make_reverse())
d5932612 174 self._outline_model.reparent_to(self._np)
ea38777c 175 self._outline_model.set_scale(1.08)
d5932612
FC
176 self._outline_model.set_light_off()
177 self._outline_model.set_color(.4, .4, .4, 1)
178 self._outline_model.set_color_scale(.4, .4, .4, 1)
179 self._outline_model.hide()
180
181 def on_frame(self, task):
182 self._np.set_y(0)
183 return task.cont
184
185 def undo(self):
186 self._command_idx -= 1
187 self._np.set_pos(self._commands[self._command_idx].pos)
188 self._np.set_hpr(self._commands[self._command_idx].rot)
189
190 def redo(self):
191 self._command_idx += 1
192 self._np.set_pos(self._commands[self._command_idx].pos)
193 self._np.set_hpr(self._commands[self._command_idx].rot)
194
195 def play(self):
196 if not self._instantiated:
197 return
198 self._world.remove_rigid_body(self.node)
ef3c36bf 199 self.node.set_mass(self._mass)
d5932612 200 self._world.attach_rigid_body(self.node)
e67dbe53 201 self.node.set_restitution(self._restitution)
32cd89ca 202 self.node.set_friction(self._friction)
d5932612
FC
203
204 def on_click_l(self, pos):
205 if self._paused: return
206 self._start_drag_pos = pos, self._np.get_pos()
207 loader.load_sfx('assets/audio/sfx/grab.ogg').play()
208 if not self._instantiated:
209 self._world.remove_ghost(self.node)
210 pos = self._np.get_pos()
211 self._np.remove_node()
212 self.node = BulletRigidBodyNode('box')
ea38777c 213 self._set_shape()
d5932612
FC
214 self._np = render.attach_new_node(self.node)
215 self._world.attach_rigid_body(self.node)
216 self._model.reparent_to(self._np)
217 self._np.set_pos(pos)
218 self._set_outline_model()
8254721d 219 self._np.set_scale(self._model_scale)
d5932612
FC
220 self._model.show(BitMask32(0x01))
221 self._outline_model.hide(BitMask32(0x01))
222 self._instantiated = True
223 self._txt.destroy()
224 self._count -= 1
225 if self._count:
ef3c36bf 226 item = self.__class__(self._world, self._plane_node, self._cb_inst, self._curr_bottom, self._scene_repos, count=self._count, mass=self._mass, pos=self._pos, r=self._r) # pylint: disable=no-value-for-parameter
d5932612 227 self._cb_inst(item)
e1438449 228 self._scene_repos()
d5932612
FC
229
230 def on_click_r(self, pos):
a6843832 231 if self._paused or not self._instantiated: return
d5932612
FC
232 self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
233 loader.load_sfx('assets/audio/sfx/grab.ogg').play()
234
235 def on_release(self):
236 if self._start_drag_pos or self._prev_rot_info:
237 loader.load_sfx('assets/audio/sfx/release.ogg').play()
238 self._command_idx += 1
239 self._commands = self._commands[:self._command_idx]
240 self._commands += [Command(self._np.get_pos(), self._np.get_hpr())]
241 self._first_command = False
242 self._start_drag_pos = self._prev_rot_info = None
243 if self._overlapping:
244 self._np.set_pos(self._last_nonoverlapping_pos)
245 self._np.set_hpr(self._last_nonoverlapping_rot)
a6843832
FC
246 self._outline_model.set_color(.4, .4, .4, 1)
247 self._outline_model.set_color_scale(.4, .4, .4, 1)
93cf86b2 248 self._overlapping = False
d5932612
FC
249
250 def on_mouse_on(self):
ef3c36bf 251 if not self._paused and self.interactable:
d5932612
FC
252 self._outline_model.show()
253
254 def on_mouse_off(self):
255 if self._start_drag_pos or self._prev_rot_info: return
ef3c36bf
FC
256 if self.interactable:
257 self._outline_model.hide()
d5932612
FC
258
259 def on_mouse_move(self, pos):
260 if self._start_drag_pos:
261 d_pos = pos - self._start_drag_pos[0]
262 self._np.set_pos(self._start_drag_pos[1] + d_pos)
263 if self._prev_rot_info:
264 start_vec = self._prev_rot_info[0] - self._prev_rot_info[1]
265 curr_vec = pos - self._prev_rot_info[1]
266 d_angle = curr_vec.signed_angle_deg(start_vec, (0, -1, 0))
267 self._np.set_r(self._prev_rot_info[2] + d_angle)
268 self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
269 if self._start_drag_pos or self._prev_rot_info:
270 res = self._world.contact_test(self.node)
271 nres = res.get_num_contacts()
ea38777c 272 if nres <= self._exp_num_contacts:
d5932612
FC
273 self._overlapping = False
274 self._outline_model.set_color(.4, .4, .4, 1)
275 self._outline_model.set_color_scale(.4, .4, .4, 1)
ea38777c 276 if nres > self._exp_num_contacts and not self._overlapping:
d5932612
FC
277 actual_nres = 0
278 for contact in res.get_contacts():
279 for node in [contact.get_node0(), contact.get_node1()]:
280 if isinstance(node, BulletRigidBodyNode) and \
281 node != self.node:
282 actual_nres += 1
283 if actual_nres >= 1:
284 self._overlapping = True
285 loader.load_sfx('assets/audio/sfx/overlap.ogg').play()
286 self._outline_model.set_color(.9, .1, .1, 1)
287 self._outline_model.set_color_scale(.9, .1, .1, 1)
288 if not self._overlapping:
289 self._last_nonoverlapping_pos = self._np.get_pos()
290 self._last_nonoverlapping_rot = self._np.get_hpr()
291
292 def on_aspect_ratio_changed(self):
293 if not self._instantiated:
294 self._repos()
295
296 def store_state(self):
297 self._paused = True
298 self._model.set_transparency(True)
299 self._model.set_alpha_scale(.3)
ef3c36bf 300 if hasattr(self, '_txt') and not self._txt.is_empty():
d5932612
FC
301 self._txt.set_alpha_scale(.3)
302
303 def restore_state(self):
304 self._paused = False
305 self._model.set_alpha_scale(1)
ef3c36bf 306 if hasattr(self, '_txt') and not self._txt.is_empty():
d5932612
FC
307 self._txt.set_alpha_scale(1)
308
0a0994e4 309 def fail_condition(self):
32cd89ca 310 if self._np.get_z() < -6:
0a0994e4
FC
311 return True
312 self._positions += [self._np.get_pos()]
313 self._rotations += [self._np.get_hpr()]
32cd89ca 314 if len(self._positions) > 30:
0a0994e4 315 self._positions.pop(0)
32cd89ca 316 if len(self._rotations) > 30:
0a0994e4 317 self._rotations.pop(0)
32cd89ca 318 if len(self._positions) < 28:
0a0994e4
FC
319 return
320 avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
321 avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
322 avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
323 avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
324 avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
325 avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
326 avg_pos = Point3(avg_x, avg_y, avg_z)
327 avg_rot = Point3(avg_h, avg_p, avg_r)
328 return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
329 all((rot - avg_rot).length() < 1 for rot in self._rotations)
330
d5932612
FC
331 def destroy(self):
332 self._np.remove_node()
333 taskMgr.remove(self._box_tsk)
ef3c36bf
FC
334 if hasattr(self, '_txt'):
335 self._txt.destroy()
d5932612
FC
336 if not self._instantiated:
337 self._world.remove_ghost(self.node)
338 else:
339 self._world.remove_rigid_body(self.node)