-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
+from pmachines.items.item import Item
-class Command:
+class Box(Item):
- def __init__(self, pos, rot):
- self.pos = pos
- self.rot = rot
+ def __init__(self, world, plane_node, count, cb_inst, curr_bottom):
+ super().__init__(world, plane_node, count, cb_inst, curr_bottom, 'assets/gltf/box/box.gltf')
-
-class Box:
-
- def __init__(self, world, plane_node, count, cb_inst):
- 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._commands = []
- self._command_idx = -1
+ def _set_phys(self):
+ super()._set_phys()
self._shape = BulletBoxShape((.5, .5, .5))
- self.node = BulletGhostNode('box')
- self.node.add_shape(self._shape)
- self._np = render.attach_new_node(self.node)
- world.attach_ghost(self.node)
- self._model = loader.load_model('assets/gltf/box/box.gltf')
- set_srgb(self._model)
- self._model.flatten_light()
- self._model.reparent_to(self._np)
- 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 = False
- self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
- self._repos()
-
- 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
- base.camLens.extrude((-1 + margin_x, 1 - 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
-
- 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('assets/gltf/box/box.gltf')
- clockw = CullFaceAttrib.MCullClockwise
- self._outline_model.set_attrib(CullFaceAttrib.make(clockw))
- self._outline_model.reparent_to(self._np)
- self._outline_model.set_scale(-1.08, -1.08, -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(1)
- self._world.attach_rigid_body(self.node)
-
- 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.node.add_shape(self._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._model.show(BitMask32(0x01))
- self._outline_model.hide(BitMask32(0x01))
- self._instantiated = True
- self._txt.destroy()
- self._count -= 1
- if self._count:
- box = Box(self._world, self._plane_node, self._count, self._cb_inst)
- self._cb_inst(box)
-
- def on_click_r(self, pos):
- if self._paused: 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)
-
- def on_mouse_on(self):
- if not self._paused:
- self._outline_model.show()
-
- def on_mouse_off(self):
- if self._start_drag_pos or self._prev_rot_info: return
- 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 <=1:
- self._overlapping = False
- self._outline_model.set_color(.4, .4, .4, 1)
- self._outline_model.set_color_scale(.4, .4, .4, 1)
- if nres > 1 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 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 not self._txt.is_empty():
- self._txt.set_alpha_scale(1)
-
- def destroy(self):
- self._np.remove_node()
- taskMgr.remove(self._box_tsk)
- self._txt.destroy()
- if not self._instantiated:
- self._world.remove_ghost(self.node)
- else:
- self._world.remove_rigid_body(self.node)
--- /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 Item:
+
+ def __init__(self, world, plane_node, count, cb_inst, curr_bottom, model_path):
+ 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._curr_bottom = curr_bottom
+ self._model_path = model_path
+ self._commands = []
+ self._command_idx = -1
+ self._set_phys()
+ self.node.add_shape(self._shape)
+ self._np = render.attach_new_node(self.node)
+ world.attach_ghost(self.node)
+ self._model = loader.load_model(model_path)
+ set_srgb(self._model)
+ self._model.flatten_light()
+ self._model.reparent_to(self._np)
+ 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 = False
+ self._box_tsk = taskMgr.add(self.on_frame, 'on_frame')
+ self._repos()
+
+ def _set_phys(self):
+ self._shape = None
+ self.node = BulletGhostNode(self.__class__.__name__)
+
+ 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 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.reparent_to(self._np)
+ self._outline_model.set_scale(-1.08, -1.08, -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(1)
+ self._world.attach_rigid_body(self.node)
+
+ 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.node.add_shape(self._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._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._count, self._cb_inst) # pylint: disable=no-value-for-parameter
+ self._cb_inst(item)
+
+ def on_click_r(self, pos):
+ if self._paused: 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)
+
+ def on_mouse_on(self):
+ if not self._paused:
+ self._outline_model.show()
+
+ def on_mouse_off(self):
+ if self._start_drag_pos or self._prev_rot_info: return
+ 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 <=1:
+ self._overlapping = False
+ self._outline_model.set_color(.4, .4, .4, 1)
+ self._outline_model.set_color_scale(.4, .4, .4, 1)
+ if nres > 1 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 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 not self._txt.is_empty():
+ self._txt.set_alpha_scale(1)
+
+ def destroy(self):
+ self._np.remove_node()
+ taskMgr.remove(self._box_tsk)
+ self._txt.destroy()
+ if not self._instantiated:
+ self._world.remove_ghost(self.node)
+ else:
+ self._world.remove_rigid_body(self.node)
--- /dev/null
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from pmachines.items.item import Item
+
+
+class Shelf(Item):
+
+ def __init__(self, world, plane_node, count, cb_inst, curr_bottom):
+ super().__init__(world, plane_node, count, cb_inst, curr_bottom, 'assets/gltf/shelf/shelf.gltf')
+
+ def _set_phys(self):
+ super()._set_phys()
+ self._shape = BulletBoxShape((1, .5, .05))
from direct.showbase.DirectObject import DirectObject
from pmachines.items.background import Background
from pmachines.items.box import Box
+from pmachines.items.shelf import Shelf
from pmachines.sidepanel import SidePanel
from lib.engine.gui.cursor import MouseCursor
from lib.lib.p3d.gfx import P3dGfxMgr
self._set_lights()
self._set_input()
self._set_mouse_plane()
- self.items = [Box(world, self._mouse_plane_node, 3, self.cb_inst)]
- self._commands = []
- self._command_idx = 0
+ self.items = []
+ self.reset()
self._paused = False
self._item_active = None
if auto_close_instr:
self._side_panel = SidePanel(world, self._mouse_plane_node, (-5, 4), (-3, 1), 1, self.items)
self._scene_tsk = taskMgr.add(self.on_frame, 'on_frame')
+ def current_bottom(self):
+ curr_bottom = 1
+ for item in self.items:
+ if item.repos_done:
+ curr_bottom = min(curr_bottom, item.get_bottom())
+ return curr_bottom
+
def reset(self):
+ [itm.destroy() for itm in self.items]
+ self.items = [Box(self._world, self._mouse_plane_node, 3, self.cb_inst, self.current_bottom)]
+ self.items += [Shelf(self._world, self._mouse_plane_node, 3, self.cb_inst, self.current_bottom)]
self._commands = []
self._command_idx = 0
- [itm.destroy() for itm in self.items]
- self.items = [Box(self._world, self._mouse_plane_node, 3, self.cb_inst)]
def destroy(self):
self._unset_gui()
self._cursor.set_image('assets/buttons/arrowUpLeft.png')
def on_aspect_ratio_changed(self):
+ for item in self.items:
+ item.repos_done = False
[item.on_aspect_ratio_changed() for item in self.items]
self._side_panel.update(self.items)
* issues
* todo
** implement other items
-*** shelf
*** domino
*** basketball
*** teeter_tooter