ya2 · news · projects · code · about

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