1 from panda3d
.core
import CullFaceAttrib
, Point3
, NodePath
, Point2
, Texture
, \
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
10 def __init__(self
, pos
, rot
):
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):
19 self
._plane
_node
= plane_node
21 self
._cb
_inst
= cb_inst
23 self
._overlapping
= False
24 self
._first
_command
= True
25 self
.repos_done
= False
29 self
._exp
_num
_contacts
= exp_num_contacts
30 self
._curr
_bottom
= curr_bottom
31 self
._scene
_repos
= scene_repos
32 self
._model
_scale
= model_scale
33 self
._model
_path
= model_path
35 self
._command
_idx
= -1
37 self
.node
= BulletGhostNode(self
.__class
__.__name
__)
39 self
.node
= BulletRigidBodyNode(self
.__class
__.__name
__)
41 self
._np
= render
.attach_new_node(self
.node
)
43 world
.attach_ghost(self
.node
)
45 world
.attach_rigid_body(self
.node
)
46 self
._model
= loader
.load_model(model_path
)
48 self
._model
.flatten_light()
49 self
._model
.reparent_to(self
._np
)
50 self
._np
.set_scale(model_scale
)
52 self
._set
_outline
_model
()
53 set_srgb(self
._outline
_model
)
54 self
._model
.hide(BitMask32(0x01))
55 self
._outline
_model
.hide(BitMask32(0x01))
56 self
._start
_drag
_pos
= None
57 self
._prev
_rot
_info
= None
58 self
._last
_nonoverlapping
_pos
= None
59 self
._last
_nonoverlapping
_rot
= None
60 self
._instantiated
= not count
61 self
.interactable
= count
62 self
._box
_tsk
= taskMgr
.add(self
.on_frame
, 'on_frame')
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()
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')
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
106 def repos_x(self
, 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
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
)
122 def get_corner(self
):
123 bounds
= self
._np
.get_tight_bounds()
124 return bounds
[1][0], bounds
[1][1], bounds
[0][2]
126 def _set_outline_model(self
):
127 self
._outline
_model
= loader
.load_model(self
._model
_path
)
128 #clockw = CullFaceAttrib.MCullClockwise
129 #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
130 self
._outline
_model
.set_attrib(CullFaceAttrib
.make_reverse())
131 self
._outline
_model
.reparent_to(self
._np
)
132 self
._outline
_model
.set_scale(1.08)
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()
138 def on_frame(self
, task
):
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
)
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
)
153 if not self
._instantiated
:
155 self
._world
.remove_rigid_body(self
.node
)
156 self
.node
.set_mass(self
._mass
)
157 self
._world
.attach_rigid_body(self
.node
)
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')
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
()
174 self
._np
.set_scale(self
._model
_scale
)
175 self
._model
.show(BitMask32(0x01))
176 self
._outline
_model
.hide(BitMask32(0x01))
177 self
._instantiated
= True
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
185 def on_click_r(self
, pos
):
186 if self
._paused
or not self
._instantiated
: return
187 self
._prev
_rot
_info
= pos
, self
._np
.get_pos(), self
._np
.get_r()
188 loader
.load_sfx('assets/audio/sfx/grab.ogg').play()
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
)
201 self
._outline
_model
.set_color(.4, .4, .4, 1)
202 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
203 self
._overlapping
= False
205 def on_mouse_on(self
):
206 if not self
._paused
and self
.interactable
:
207 self
._outline
_model
.show()
209 def on_mouse_off(self
):
210 if self
._start
_drag
_pos
or self
._prev
_rot
_info
: return
211 if self
.interactable
:
212 self
._outline
_model
.hide()
214 def on_mouse_move(self
, pos
):
215 if self
._start
_drag
_pos
:
216 d_pos
= pos
- self
._start
_drag
_pos
[0]
217 self
._np
.set_pos(self
._start
_drag
_pos
[1] + d_pos
)
218 if self
._prev
_rot
_info
:
219 start_vec
= self
._prev
_rot
_info
[0] - self
._prev
_rot
_info
[1]
220 curr_vec
= pos
- self
._prev
_rot
_info
[1]
221 d_angle
= curr_vec
.signed_angle_deg(start_vec
, (0, -1, 0))
222 self
._np
.set_r(self
._prev
_rot
_info
[2] + d_angle
)
223 self
._prev
_rot
_info
= pos
, self
._np
.get_pos(), self
._np
.get_r()
224 if self
._start
_drag
_pos
or self
._prev
_rot
_info
:
225 res
= self
._world
.contact_test(self
.node
)
226 nres
= res
.get_num_contacts()
227 if nres
<= self
._exp
_num
_contacts
:
228 self
._overlapping
= False
229 self
._outline
_model
.set_color(.4, .4, .4, 1)
230 self
._outline
_model
.set_color_scale(.4, .4, .4, 1)
231 if nres
> self
._exp
_num
_contacts
and not self
._overlapping
:
233 for contact
in res
.get_contacts():
234 for node
in [contact
.get_node0(), contact
.get_node1()]:
235 if isinstance(node
, BulletRigidBodyNode
) and \
239 self
._overlapping
= True
240 loader
.load_sfx('assets/audio/sfx/overlap.ogg').play()
241 self
._outline
_model
.set_color(.9, .1, .1, 1)
242 self
._outline
_model
.set_color_scale(.9, .1, .1, 1)
243 if not self
._overlapping
:
244 self
._last
_nonoverlapping
_pos
= self
._np
.get_pos()
245 self
._last
_nonoverlapping
_rot
= self
._np
.get_hpr()
247 def on_aspect_ratio_changed(self
):
248 if not self
._instantiated
:
251 def store_state(self
):
253 self
._model
.set_transparency(True)
254 self
._model
.set_alpha_scale(.3)
255 if hasattr(self
, '_txt') and not self
._txt
.is_empty():
256 self
._txt
.set_alpha_scale(.3)
258 def restore_state(self
):
260 self
._model
.set_alpha_scale(1)
261 if hasattr(self
, '_txt') and not self
._txt
.is_empty():
262 self
._txt
.set_alpha_scale(1)
264 def end_condition(self
):
268 self
._np
.remove_node()
269 taskMgr
.remove(self
._box
_tsk
)
270 if hasattr(self
, '_txt'):
272 if not self
._instantiated
:
273 self
._world
.remove_ghost(self
.node
)
275 self
._world
.remove_rigid_body(self
.node
)