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