+++ /dev/null
-from panda3d.core import CullFaceAttrib, Point3, NodePath, Point2, Texture, \
- Plane, Vec3, BitMask32
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from direct.gui.OnscreenText import OnscreenText
-from lib.lib.p3d.gfx import P3dGfxMgr, set_srgb
-
-
-class Command:
-
- def __init__(self, pos, rot):
- self.pos = pos
- self.rot = rot
-
-
-class FixedStrategy:
-
- def win_condition(self):
- return True
-
-
-class StillStrategy:
-
- def __init__(self, np):
- self._np = np
- self._positions = []
- self._rotations = []
-
- def win_condition(self):
- self._positions += [self._np.get_pos()]
- self._rotations += [self._np.get_hpr()]
- if len(self._positions) > 30:
- self._positions.pop(0)
- if len(self._rotations) > 30:
- self._rotations.pop(0)
- if len(self._positions) < 28:
- return
- avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
- avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
- avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
- avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
- avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
- avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
- avg_pos = Point3(avg_x, avg_y, avg_z)
- avg_rot = Point3(avg_h, avg_p, avg_r)
- return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
- all((rot - avg_rot).length() < 1 for rot in self._rotations)
-
-
-class Item:
-
- 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):
- self._world = world
- self._plane_node = plane_node
- self._count = count
- self._cb_inst = cb_inst
- self._paused = False
- self._overlapping = False
- self._first_command = True
- self.repos_done = False
- self._mass = mass
- self._pos = pos
- self._r = r
- self.strategy = FixedStrategy()
- self._exp_num_contacts = exp_num_contacts
- self._curr_bottom = curr_bottom
- self._scene_repos = scene_repos
- self._model_scale = model_scale
- self._model_path = model_path
- self._commands = []
- self._command_idx = -1
- self._restitution = restitution
- self._friction = friction
- self._positions = []
- self._rotations = []
- if count:
- self.node = BulletGhostNode(self.__class__.__name__)
- else:
- self.node = BulletRigidBodyNode(self.__class__.__name__)
- self._set_shape(count)
- self._np = render.attach_new_node(self.node)
- if count:
- world.attach_ghost(self.node)
- else:
- world.attach_rigid_body(self.node)
- self._model = loader.load_model(model_path)
- set_srgb(self._model)
- self._model.flatten_light()
- self._model.reparent_to(self._np)
- self._np.set_scale(model_scale)
- self._np.flatten_strong()
- if count:
- self._set_outline_model()
- set_srgb(self._outline_model)
- self._model.hide(BitMask32(0x01))
- self._outline_model.hide(BitMask32(0x01))
- self._start_drag_pos = None
- self._prev_rot_info = None
- self._last_nonoverlapping_pos = None
- self._last_nonoverlapping_rot = None
- self._instantiated = not count
- self.interactable = count
- self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
- if count:
- self._repos()
- else:
- self._np.set_pos(pos)
- self._np.set_r(r)
-
- def _set_shape(self, apply_scale=True):
- pass
-
- def set_strategy(self, strategy):
- self.strategy = strategy
-
- def _repos(self):
- p_from, p_to = P3dGfxMgr.world_from_to((-1, 1))
- for hit in self._world.ray_test_all(p_from, p_to).get_hits():
- if hit.get_node() == self._plane_node:
- pos = hit.get_hit_pos()
- corner = P3dGfxMgr.screen_coord(pos)
- bounds = self._np.get_tight_bounds()
- bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
- self._np.set_pos(pos)
- plane = Plane(Vec3(0, 1, 0), bounds[0][1])
- pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
- margin, ar = .04, base.get_aspect_ratio()
- margin_x = margin / ar if ar >= 1 else margin
- margin_z = margin * ar if ar < 1 else margin
- top = self._curr_bottom()
- base.camLens.extrude((-1 + margin_x, top - margin_z), near_pt, far_pt)
- plane.intersects_line(
- pos3d, render.get_relative_point(base.camera, near_pt),
- render.get_relative_point(base.camera, far_pt))
- delta = Vec3(bounds[1][0], bounds[1][1], bounds[0][2])
- self._np.set_pos(pos3d + delta)
- if not hasattr(self, '_txt'):
- font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
- font.clear()
- font.set_pixels_per_unit(60)
- font.set_minfilter(Texture.FTLinearMipmapLinear)
- font.set_outline((0, 0, 0, 1), .8, .2)
- self._txt = OnscreenText(
- str(self._count), font=font, scale=0.06, fg=(.9, .9, .9, 1))
- pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
- p2d = P3dGfxMgr.screen_coord(pos)
- self._txt['pos'] = p2d
- self.repos_done = True
-
- def repos_x(self, x):
- self._np.set_x(x)
- bounds = self._np.get_tight_bounds()
- bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
- pos = self._np.get_pos() + (bounds[1][0], bounds[0][1], bounds[0][2])
- p2d = P3dGfxMgr.screen_coord(pos)
- self._txt['pos'] = p2d
-
- def get_bottom(self):
- bounds = self._np.get_tight_bounds()
- bounds = bounds[0] - self._np.get_pos(), bounds[1] - self._np.get_pos()
- pos = self._np.get_pos() + (bounds[1][0], bounds[1][1], bounds[0][2])
- p2d = P3dGfxMgr.screen_coord(pos)
- ar = base.get_aspect_ratio()
- return p2d[1] if ar >= 1 else (p2d[1] * ar)
-
- def get_corner(self):
- bounds = self._np.get_tight_bounds()
- return bounds[1][0], bounds[1][1], bounds[0][2]
-
- def _set_outline_model(self):
- self._outline_model = loader.load_model(self._model_path)
- #clockw = CullFaceAttrib.MCullClockwise
- #self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
- self._outline_model.set_attrib(CullFaceAttrib.make_reverse())
- self._outline_model.reparent_to(self._np)
- self._outline_model.set_scale(1.08)
- self._outline_model.set_light_off()
- self._outline_model.set_color(.4, .4, .4, 1)
- self._outline_model.set_color_scale(.4, .4, .4, 1)
- self._outline_model.hide()
-
- def on_frame(self, task):
- self._np.set_y(0)
- return task.cont
-
- def undo(self):
- self._command_idx -= 1
- self._np.set_pos(self._commands[self._command_idx].pos)
- self._np.set_hpr(self._commands[self._command_idx].rot)
-
- def redo(self):
- self._command_idx += 1
- self._np.set_pos(self._commands[self._command_idx].pos)
- self._np.set_hpr(self._commands[self._command_idx].rot)
-
- def play(self):
- if not self._instantiated:
- return
- self._world.remove_rigid_body(self.node)
- self.node.set_mass(self._mass)
- self._world.attach_rigid_body(self.node)
- self.node.set_restitution(self._restitution)
- self.node.set_friction(self._friction)
-
- def on_click_l(self, pos):
- if self._paused: return
- self._start_drag_pos = pos, self._np.get_pos()
- loader.load_sfx('assets/audio/sfx/grab.ogg').play()
- if not self._instantiated:
- self._world.remove_ghost(self.node)
- pos = self._np.get_pos()
- self._np.remove_node()
- self.node = BulletRigidBodyNode('box')
- self._set_shape()
- self._np = render.attach_new_node(self.node)
- self._world.attach_rigid_body(self.node)
- self._model.reparent_to(self._np)
- self._np.set_pos(pos)
- self._set_outline_model()
- self._np.set_scale(self._model_scale)
- self._model.show(BitMask32(0x01))
- self._outline_model.hide(BitMask32(0x01))
- self._instantiated = True
- self._txt.destroy()
- self._count -= 1
- if self._count:
- 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
- self._cb_inst(item)
- self._scene_repos()
-
- def on_click_r(self, pos):
- if self._paused or not self._instantiated: return
- self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
- loader.load_sfx('assets/audio/sfx/grab.ogg').play()
-
- def on_release(self):
- if self._start_drag_pos or self._prev_rot_info:
- loader.load_sfx('assets/audio/sfx/release.ogg').play()
- self._command_idx += 1
- self._commands = self._commands[:self._command_idx]
- self._commands += [Command(self._np.get_pos(), self._np.get_hpr())]
- self._first_command = False
- self._start_drag_pos = self._prev_rot_info = None
- if self._overlapping:
- self._np.set_pos(self._last_nonoverlapping_pos)
- self._np.set_hpr(self._last_nonoverlapping_rot)
- self._outline_model.set_color(.4, .4, .4, 1)
- self._outline_model.set_color_scale(.4, .4, .4, 1)
- self._overlapping = False
-
- def on_mouse_on(self):
- if not self._paused and self.interactable:
- self._outline_model.show()
-
- def on_mouse_off(self):
- if self._start_drag_pos or self._prev_rot_info: return
- if self.interactable:
- self._outline_model.hide()
-
- def on_mouse_move(self, pos):
- if self._start_drag_pos:
- d_pos = pos - self._start_drag_pos[0]
- self._np.set_pos(self._start_drag_pos[1] + d_pos)
- if self._prev_rot_info:
- start_vec = self._prev_rot_info[0] - self._prev_rot_info[1]
- curr_vec = pos - self._prev_rot_info[1]
- d_angle = curr_vec.signed_angle_deg(start_vec, (0, -1, 0))
- self._np.set_r(self._prev_rot_info[2] + d_angle)
- self._prev_rot_info = pos, self._np.get_pos(), self._np.get_r()
- if self._start_drag_pos or self._prev_rot_info:
- res = self._world.contact_test(self.node)
- nres = res.get_num_contacts()
- if nres <= self._exp_num_contacts:
- self._overlapping = False
- self._outline_model.set_color(.4, .4, .4, 1)
- self._outline_model.set_color_scale(.4, .4, .4, 1)
- if nres > self._exp_num_contacts and not self._overlapping:
- actual_nres = 0
- for contact in res.get_contacts():
- for node in [contact.get_node0(), contact.get_node1()]:
- if isinstance(node, BulletRigidBodyNode) and \
- node != self.node:
- actual_nres += 1
- if actual_nres >= 1:
- self._overlapping = True
- loader.load_sfx('assets/audio/sfx/overlap.ogg').play()
- self._outline_model.set_color(.9, .1, .1, 1)
- self._outline_model.set_color_scale(.9, .1, .1, 1)
- if not self._overlapping:
- self._last_nonoverlapping_pos = self._np.get_pos()
- self._last_nonoverlapping_rot = self._np.get_hpr()
-
- def on_aspect_ratio_changed(self):
- if not self._instantiated:
- self._repos()
-
- def store_state(self):
- self._paused = True
- self._model.set_transparency(True)
- self._model.set_alpha_scale(.3)
- if hasattr(self, '_txt') and not self._txt.is_empty():
- self._txt.set_alpha_scale(.3)
-
- def restore_state(self):
- self._paused = False
- self._model.set_alpha_scale(1)
- if hasattr(self, '_txt') and not self._txt.is_empty():
- self._txt.set_alpha_scale(1)
-
- def fail_condition(self):
- if self._np.get_z() < -6:
- return True
- self._positions += [self._np.get_pos()]
- self._rotations += [self._np.get_hpr()]
- if len(self._positions) > 30:
- self._positions.pop(0)
- if len(self._rotations) > 30:
- self._rotations.pop(0)
- if len(self._positions) < 28:
- return
- avg_x = sum(pos.x for pos in self._positions) / len(self._positions)
- avg_y = sum(pos.y for pos in self._positions) / len(self._positions)
- avg_z = sum(pos.z for pos in self._positions) / len(self._positions)
- avg_h = sum(rot.x for rot in self._rotations) / len(self._rotations)
- avg_p = sum(rot.y for rot in self._rotations) / len(self._rotations)
- avg_r = sum(rot.z for rot in self._rotations) / len(self._rotations)
- avg_pos = Point3(avg_x, avg_y, avg_z)
- avg_rot = Point3(avg_h, avg_p, avg_r)
- return all((pos - avg_pos).length() < .1 for pos in self._positions) and \
- all((rot - avg_rot).length() < 1 for rot in self._rotations)
-
- def destroy(self):
- self._np.remove_node()
- taskMgr.remove(self._box_tsk)
- if hasattr(self, '_txt'):
- self._txt.destroy()
- if not self._instantiated:
- self._world.remove_ghost(self.node)
- else:
- self._world.remove_rigid_body(self.node)