ya2 · news · projects · code · about

fixes
[pmachines.git] / pmachines / items / item.py
CommitLineData
d5932612
FC
1from panda3d.core import CullFaceAttrib, Point3, NodePath, Point2, Texture, \
2 Plane, Vec3, BitMask32
3from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
4from direct.gui.OnscreenText import OnscreenText
5from lib.lib.p3d.gfx import P3dGfxMgr, set_srgb
6
7
8class Command:
9
10 def __init__(self, pos, rot):
11 self.pos = pos
12 self.rot = rot
13
14
15class Item:
16
ea38777c 17 def __init__(self, world, plane_node, count, cb_inst, curr_bottom, scene_repos, model_path, model_scale=1, exp_num_contacts=1):
d5932612
FC
18 self._world = world
19 self._plane_node = plane_node
20 self._count = count
21 self._cb_inst = cb_inst
22 self._paused = False
23 self._overlapping = False
24 self._first_command = True
25 self.repos_done = False
ea38777c 26 self._exp_num_contacts = exp_num_contacts
d5932612 27 self._curr_bottom = curr_bottom
e1438449 28 self._scene_repos = scene_repos
8254721d 29 self._model_scale = model_scale
d5932612
FC
30 self._model_path = model_path
31 self._commands = []
32 self._command_idx = -1
ea38777c
FC
33 self.node = BulletGhostNode(self.__class__.__name__)
34 self._set_shape()
d5932612
FC
35 self._np = render.attach_new_node(self.node)
36 world.attach_ghost(self.node)
37 self._model = loader.load_model(model_path)
38 set_srgb(self._model)
39 self._model.flatten_light()
40 self._model.reparent_to(self._np)
41 self._set_outline_model()
8254721d 42 self._np.set_scale(model_scale)
d5932612
FC
43 set_srgb(self._outline_model)
44 self._model.hide(BitMask32(0x01))
45 self._outline_model.hide(BitMask32(0x01))
46 self._start_drag_pos = None
47 self._prev_rot_info = None
48 self._last_nonoverlapping_pos = None
49 self._last_nonoverlapping_rot = None
50 self._instantiated = False
51 self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
52 self._repos()
53
ea38777c
FC
54 def _set_shape(self):
55 pass
d5932612
FC
56
57 def _repos(self):
58 p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
59 for hit in self._world.ray_test_all(p_from, p_to).get_hits():
60 if hit.get_node() == self._plane_node:
61 pos = hit.get_hit_pos()
62 corner = P3dGfxMgr.screen_coord(pos)
63 bounds = self._np.get_tight_bounds()
64 bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
65 self._np.set_pos(pos)
66 plane = Plane(Vec3(0, 1, 0), bounds[0][1])
67 pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
68 margin, ar = .04, base.get_aspect_ratio()
69 margin_x = margin / ar if ar >= 1 else margin
70 margin_z = margin * ar if ar < 1 else margin
71 top = self._curr_bottom()
72 base.camLens.extrude((-1 + margin_x, top - margin_z), near_pt, far_pt)
73 plane.intersects_line(
74 pos3d, render.get_relative_point(base.camera, near_pt),
75 render.get_relative_point(base.camera, far_pt))
76 delta = Vec3(bounds[1][0], bounds[1][1], bounds[0][2])
77 self._np.set_pos(pos3d + delta)
78 if not hasattr(self, '_txt'):
79 font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
80 font.clear()
81 font.set_pixels_per_unit(60)
82 font.set_minfilter(Texture.FTLinearMipmapLinear)
83 font.set_outline((0, 0, 0, 1), .8, .2)
84 self._txt = OnscreenText(
85 str(self._count), font=font, scale=0.06, fg=(.9, .9, .9, 1))
86 pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
87 p2d = P3dGfxMgr.screen_coord(pos)
88 self._txt['pos'] = p2d
89 self.repos_done = True
90
e1438449
FC
91 def repos_x(self, x):
92 self._np.set_x(x)
93 bounds = self._np.get_tight_bounds()
94 bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
95 pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
96 p2d = P3dGfxMgr.screen_coord(pos)
97 self._txt['pos'] = p2d
98
d5932612
FC
99 def get_bottom(self):
100 bounds = self._np.get_tight_bounds()
101 bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
102 pos = self._np.get_pos() + (bounds[1][0], bounds[1][1], bounds[0][2])
103 p2d = P3dGfxMgr.screen_coord(pos)
104 ar = base.get_aspect_ratio()
105 return p2d[1] if ar >= 1 else (p2d[1] * ar)
106
107 def get_corner(self):
108 bounds = self._np.get_tight_bounds()
109 return bounds[1][0], bounds[1][1], bounds[0][2]
110
111 def _set_outline_model(self):
112 self._outline_model = loader.load_model(self._model_path)
ea38777c
FC
113 #clockw = CullFaceAttrib.MCullClockwise
114 #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
115 self._outline_model.set_attrib(CullFaceAttrib.make_reverse())
d5932612 116 self._outline_model.reparent_to(self._np)
ea38777c 117 self._outline_model.set_scale(1.08)
d5932612
FC
118 self._outline_model.set_light_off()
119 self._outline_model.set_color(.4, .4, .4, 1)
120 self._outline_model.set_color_scale(.4, .4, .4, 1)
121 self._outline_model.hide()
122
123 def on_frame(self, task):
124 self._np.set_y(0)
125 return task.cont
126
127 def undo(self):
128 self._command_idx -= 1
129 self._np.set_pos(self._commands[self._command_idx].pos)
130 self._np.set_hpr(self._commands[self._command_idx].rot)
131
132 def redo(self):
133 self._command_idx += 1
134 self._np.set_pos(self._commands[self._command_idx].pos)
135 self._np.set_hpr(self._commands[self._command_idx].rot)
136
137 def play(self):
138 if not self._instantiated:
139 return
140 self._world.remove_rigid_body(self.node)
141 self.node.set_mass(1)
142 self._world.attach_rigid_body(self.node)
143
144 def on_click_l(self, pos):
145 if self._paused: return
146 self._start_drag_pos = pos, self._np.get_pos()
147 loader.load_sfx('assets/audio/sfx/grab.ogg').play()
148 if not self._instantiated:
149 self._world.remove_ghost(self.node)
150 pos = self._np.get_pos()
151 self._np.remove_node()
152 self.node = BulletRigidBodyNode('box')
ea38777c 153 self._set_shape()
d5932612
FC
154 self._np = render.attach_new_node(self.node)
155 self._world.attach_rigid_body(self.node)
156 self._model.reparent_to(self._np)
157 self._np.set_pos(pos)
158 self._set_outline_model()
8254721d 159 self._np.set_scale(self._model_scale)
d5932612
FC
160 self._model.show(BitMask32(0x01))
161 self._outline_model.hide(BitMask32(0x01))
162 self._instantiated = True
163 self._txt.destroy()
164 self._count -= 1
165 if self._count:
e1438449 166 item = self.__class__(self._world, self._plane_node, self._count, self._cb_inst, self._curr_bottom, self._scene_repos) # pylint: disable=no-value-for-parameter
d5932612 167 self._cb_inst(item)
e1438449 168 self._scene_repos()
d5932612
FC
169
170 def on_click_r(self, pos):
a6843832 171 if self._paused or not self._instantiated: return
d5932612
FC
172 self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
173 loader.load_sfx('assets/audio/sfx/grab.ogg').play()
174
175 def on_release(self):
176 if self._start_drag_pos or self._prev_rot_info:
177 loader.load_sfx('assets/audio/sfx/release.ogg').play()
178 self._command_idx += 1
179 self._commands = self._commands[:self._command_idx]
180 self._commands += [Command(self._np.get_pos(), self._np.get_hpr())]
181 self._first_command = False
182 self._start_drag_pos = self._prev_rot_info = None
183 if self._overlapping:
184 self._np.set_pos(self._last_nonoverlapping_pos)
185 self._np.set_hpr(self._last_nonoverlapping_rot)
a6843832
FC
186 self._outline_model.set_color(.4, .4, .4, 1)
187 self._outline_model.set_color_scale(.4, .4, .4, 1)
d5932612
FC
188
189 def on_mouse_on(self):
190 if not self._paused:
191 self._outline_model.show()
192
193 def on_mouse_off(self):
194 if self._start_drag_pos or self._prev_rot_info: return
195 self._outline_model.hide()
196
197 def on_mouse_move(self, pos):
198 if self._start_drag_pos:
199 d_pos = pos - self._start_drag_pos[0]
200 self._np.set_pos(self._start_drag_pos[1] + d_pos)
201 if self._prev_rot_info:
202 start_vec = self._prev_rot_info[0] - self._prev_rot_info[1]
203 curr_vec = pos - self._prev_rot_info[1]
204 d_angle = curr_vec.signed_angle_deg(start_vec, (0, -1, 0))
205 self._np.set_r(self._prev_rot_info[2] + d_angle)
206 self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
207 if self._start_drag_pos or self._prev_rot_info:
208 res = self._world.contact_test(self.node)
209 nres = res.get_num_contacts()
ea38777c 210 if nres <= self._exp_num_contacts:
d5932612
FC
211 self._overlapping = False
212 self._outline_model.set_color(.4, .4, .4, 1)
213 self._outline_model.set_color_scale(.4, .4, .4, 1)
ea38777c 214 if nres > self._exp_num_contacts and not self._overlapping:
d5932612
FC
215 actual_nres = 0
216 for contact in res.get_contacts():
217 for node in [contact.get_node0(), contact.get_node1()]:
218 if isinstance(node, BulletRigidBodyNode) and \
219 node != self.node:
220 actual_nres += 1
221 if actual_nres >= 1:
222 self._overlapping = True
223 loader.load_sfx('assets/audio/sfx/overlap.ogg').play()
224 self._outline_model.set_color(.9, .1, .1, 1)
225 self._outline_model.set_color_scale(.9, .1, .1, 1)
226 if not self._overlapping:
227 self._last_nonoverlapping_pos = self._np.get_pos()
228 self._last_nonoverlapping_rot = self._np.get_hpr()
229
230 def on_aspect_ratio_changed(self):
231 if not self._instantiated:
232 self._repos()
233
234 def store_state(self):
235 self._paused = True
236 self._model.set_transparency(True)
237 self._model.set_alpha_scale(.3)
238 if not self._txt.is_empty():
239 self._txt.set_alpha_scale(.3)
240
241 def restore_state(self):
242 self._paused = False
243 self._model.set_alpha_scale(1)
244 if not self._txt.is_empty():
245 self._txt.set_alpha_scale(1)
246
247 def destroy(self):
248 self._np.remove_node()
249 taskMgr.remove(self._box_tsk)
250 self._txt.destroy()
251 if not self._instantiated:
252 self._world.remove_ghost(self.node)
253 else:
254 self._world.remove_rigid_body(self.node)