ya2 · news · projects · code · about

shelf
authorFlavio Calva <f.calva@gmail.com>
Tue, 25 Jan 2022 17:49:04 +0000 (18:49 +0100)
committerFlavio Calva <f.calva@gmail.com>
Tue, 25 Jan 2022 17:49:04 +0000 (18:49 +0100)
pmachines/items/box.py
pmachines/items/item.py [new file with mode: 0644]
pmachines/items/shelf.py [new file with mode: 0644]
pmachines/scene.py
prj.org

index 2565a55f050f0acbec63ba2845da0cb97e090902..8cba5e70cfdbd938cfa10753d3ae897f4ea5d80f 100644 (file)
-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)
diff --git a/pmachines/items/item.py b/pmachines/items/item.py
new file mode 100644 (file)
index 0000000..9d06be6
--- /dev/null
@@ -0,0 +1,238 @@
+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)
diff --git a/pmachines/items/shelf.py b/pmachines/items/shelf.py
new file mode 100644 (file)
index 0000000..5ed9b69
--- /dev/null
@@ -0,0 +1,12 @@
+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))
index 80725d85e477bb439d3f4e0d3fbe70d290ea7efc..c1dcdfdf11c7edddda52becdc5fa6a093c228632 100644 (file)
@@ -8,6 +8,7 @@ from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
 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
@@ -27,9 +28,8 @@ class Scene(DirectObject):
         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:
@@ -41,11 +41,19 @@ class Scene(DirectObject):
         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()
@@ -212,6 +220,8 @@ class Scene(DirectObject):
         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)
 
diff --git a/prj.org b/prj.org
index 0585ba5aec72fe59cb7e852dcca84eaae0e5a051..272d1bd0dd6d20872aaa5eb9d525259b7e958bd1 100644 (file)
--- a/prj.org
+++ b/prj.org
@@ -1,7 +1,6 @@
 * issues
 * todo
 ** implement other items
-*** shelf
 *** domino
 *** basketball
 *** teeter_tooter