ya2 · news · projects · code · about

pmachines/ -> game/
[pmachines.git] / game / items / item.py
1 from panda3d.core import CullFaceAttrib, Point3, NodePath, Point2, Texture, \
2 Plane, Vec3, BitMask32
3 from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
4 from direct.gui.OnscreenText import OnscreenText
5 from lib.lib.p3d.gfx import P3dGfxMgr, set_srgb
6
7
8 class Command:
9
10 def __init__(self, pos, rot):
11 self.pos = pos
12 self.rot = rot
13
14
15 class FixedStrategy:
16
17 def win_condition(self):
18 return True
19
20
21 class StillStrategy:
22
23 def __init__(self, np):
24 self._np = np
25 self._positions = []
26 self._rotations = []
27
28 def win_condition(self):
29 self._positions += [self._np.get_pos()]
30 self._rotations += [self._np.get_hpr()]
31 if len(self._positions) > 30:
32 self._positions.pop(0)
33 if len(self._rotations) > 30:
34 self._rotations.pop(0)
35 if len(self._positions) < 28:
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
49 class Item:
50
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):
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
60 self._mass = mass
61 self._pos = pos
62 self._r = r
63 self.strategy = FixedStrategy()
64 self._exp_num_contacts = exp_num_contacts
65 self._curr_bottom = curr_bottom
66 self._scene_repos = scene_repos
67 self._model_scale = model_scale
68 self._model_path = model_path
69 self._commands = []
70 self._command_idx = -1
71 self._restitution = restitution
72 self._friction = friction
73 self._positions = []
74 self._rotations = []
75 if count:
76 self.node = BulletGhostNode(self.__class__.__name__)
77 else:
78 self.node = BulletRigidBodyNode(self.__class__.__name__)
79 self._set_shape(count)
80 self._np = render.attach_new_node(self.node)
81 if count:
82 world.attach_ghost(self.node)
83 else:
84 world.attach_rigid_body(self.node)
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)
89 self._np.set_scale(model_scale)
90 self._np.flatten_strong()
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))
96 self._start_drag_pos = None
97 self._prev_rot_info = None
98 self._last_nonoverlapping_pos = None
99 self._last_nonoverlapping_rot = None
100 self._instantiated = not count
101 self.interactable = count
102 self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
103 if count:
104 self._repos()
105 else:
106 self._np.set_pos(pos)
107 self._np.set_r(r)
108
109 def _set_shape(self, apply_scale=True):
110 pass
111
112 def set_strategy(self, strategy):
113 self.strategy = strategy
114
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()
120 corner = P3dGfxMgr.screen_coord(pos)
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
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
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)
171 #clockw = CullFaceAttrib.MCullClockwise
172 #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
173 self._outline_model.set_attrib(CullFaceAttrib.make_reverse())
174 self._outline_model.reparent_to(self._np)
175 self._outline_model.set_scale(1.08)
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)
199 self.node.set_mass(self._mass)
200 self._world.attach_rigid_body(self.node)
201 self.node.set_restitution(self._restitution)
202 self.node.set_friction(self._friction)
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')
213 self._set_shape()
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()
219 self._np.set_scale(self._model_scale)
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:
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
227 self._cb_inst(item)
228 self._scene_repos()
229
230 def on_click_r(self, pos):
231 if self._paused or not self._instantiated: return
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)
246 self._outline_model.set_color(.4, .4, .4, 1)
247 self._outline_model.set_color_scale(.4, .4, .4, 1)
248 self._overlapping = False
249
250 def on_mouse_on(self):
251 if not self._paused and self.interactable:
252 self._outline_model.show()
253
254 def on_mouse_off(self):
255 if self._start_drag_pos or self._prev_rot_info: return
256 if self.interactable:
257 self._outline_model.hide()
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()
272 if nres <= self._exp_num_contacts:
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)
276 if nres > self._exp_num_contacts and not self._overlapping:
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)
300 if hasattr(self, '_txt') and not self._txt.is_empty():
301 self._txt.set_alpha_scale(.3)
302
303 def restore_state(self):
304 self._paused = False
305 self._model.set_alpha_scale(1)
306 if hasattr(self, '_txt') and not self._txt.is_empty():
307 self._txt.set_alpha_scale(1)
308
309 def fail_condition(self):
310 if self._np.get_z() < -6:
311 return True
312 self._positions += [self._np.get_pos()]
313 self._rotations += [self._np.get_hpr()]
314 if len(self._positions) > 30:
315 self._positions.pop(0)
316 if len(self._rotations) > 30:
317 self._rotations.pop(0)
318 if len(self._positions) < 28:
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
331 def destroy(self):
332 self._np.remove_node()
333 taskMgr.remove(self._box_tsk)
334 if hasattr(self, '_txt'):
335 self._txt.destroy()
336 if not self._instantiated:
337 self._world.remove_ghost(self.node)
338 else:
339 self._world.remove_rigid_body(self.node)