ya2 · news · projects · code · about

pmachines/ -> game/
authorFlavio Calva <f.calva@gmail.com>
Wed, 16 Mar 2022 17:58:02 +0000 (18:58 +0100)
committerFlavio Calva <f.calva@gmail.com>
Wed, 16 Mar 2022 17:58:02 +0000 (18:58 +0100)
46 files changed:
game/__init__.py [new file with mode: 0644]
game/app.py [new file with mode: 0755]
game/items/__init__.py [new file with mode: 0644]
game/items/background.py [new file with mode: 0644]
game/items/basketball.py [new file with mode: 0644]
game/items/box.py [new file with mode: 0644]
game/items/domino.py [new file with mode: 0644]
game/items/item.py [new file with mode: 0644]
game/items/shelf.py [new file with mode: 0644]
game/items/teetertooter.py [new file with mode: 0644]
game/menu.py [new file with mode: 0644]
game/music.py [new file with mode: 0644]
game/scene.py [new file with mode: 0644]
game/scenes/__init__.py [new file with mode: 0644]
game/scenes/scene_basketball.py [new file with mode: 0644]
game/scenes/scene_box.py [new file with mode: 0644]
game/scenes/scene_domino.py [new file with mode: 0644]
game/scenes/scene_domino_box.py [new file with mode: 0644]
game/scenes/scene_domino_box_basketball.py [new file with mode: 0644]
game/scenes/scene_teeter_domino_box_basketball.py [new file with mode: 0644]
game/scenes/scene_teeter_tooter.py [new file with mode: 0644]
game/sidepanel.py [new file with mode: 0644]
lib/build/screenshots.py
main.py
pmachines/__init__.py [deleted file]
pmachines/app.py [deleted file]
pmachines/items/__init__.py [deleted file]
pmachines/items/background.py [deleted file]
pmachines/items/basketball.py [deleted file]
pmachines/items/box.py [deleted file]
pmachines/items/domino.py [deleted file]
pmachines/items/item.py [deleted file]
pmachines/items/shelf.py [deleted file]
pmachines/items/teetertooter.py [deleted file]
pmachines/menu.py [deleted file]
pmachines/music.py [deleted file]
pmachines/scene.py [deleted file]
pmachines/scenes/__init__.py [deleted file]
pmachines/scenes/scene_basketball.py [deleted file]
pmachines/scenes/scene_box.py [deleted file]
pmachines/scenes/scene_domino.py [deleted file]
pmachines/scenes/scene_domino_box.py [deleted file]
pmachines/scenes/scene_domino_box_basketball.py [deleted file]
pmachines/scenes/scene_teeter_domino_box_basketball.py [deleted file]
pmachines/scenes/scene_teeter_tooter.py [deleted file]
pmachines/sidepanel.py [deleted file]

diff --git a/game/__init__.py b/game/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/app.py b/game/app.py
new file mode 100755 (executable)
index 0000000..8b3876f
--- /dev/null
@@ -0,0 +1,236 @@
+import argparse
+import simplepbr
+#import gltf
+from glob import glob
+from importlib import import_module
+from inspect import isclass
+from sys import platform, argv, exit
+from logging import info, debug
+from os.path import exists
+from os import makedirs
+from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
+    Texture, WindowProperties, LVector2i
+from panda3d.bullet import BulletWorld, BulletDebugNode
+from direct.showbase.ShowBase import ShowBase
+from direct.fsm.FSM import FSM
+from game.music import MusicMgr
+from game.items.background import Background
+from game.menu import Menu
+from game.scene import Scene
+from lib.dictfile import DctFile
+from lib.lib.p3d.p3d import LibP3d
+from lib.engine.lang import LangMgr
+
+
+class MainFsm(FSM):
+
+    def __init__(self, pmachines):
+        super().__init__('Main FSM')
+        self._pmachines = pmachines
+
+    def enterMenu(self):
+        self._pmachines.on_menu_enter()
+
+    def exitMenu(self):
+        self._pmachines.on_menu_exit()
+
+    def enterScene(self, cls):
+        self._pmachines.on_scene_enter(cls)
+
+    def exitScene(self):
+        self._pmachines.on_scene_exit()
+
+
+class PmachinesApp:
+
+    def __init__(self):
+        info('platform: %s' % platform)
+        info('exists main.py: %s' % exists('main.py'))
+        args = self._parse_args()
+        self._configure(args)
+        self.base = ShowBase()
+        self._prepare_window(args)
+        self.updating = args.update
+        self.version = args.version
+        if args.update:
+            return
+        self._music = MusicMgr(self._options['settings']['volume'])
+        self.lang_mgr = LangMgr(self._options['settings']['language'],
+                                'pmachines',
+                                'assets/locale/')
+        self._fsm = MainFsm(self)
+        if args.screenshot:
+            scene_classes = []
+            for _file in glob('game/scenes/*.py'):
+                _fn = _file.replace('.py', '').replace('/', '.')
+                for member in import_module(_fn).__dict__.values():
+                    if isclass(member) and issubclass(member, Scene) and \
+                            member != Scene:
+                        scene_classes += [member]
+            cls = [cls for cls in scene_classes if cls.__name__ == args.screenshot][0]
+            scene = cls(BulletWorld(), None, True, False, lambda: None)
+            scene.screenshot()
+            scene.destroy()
+            exit()
+        elif self._options['development']['auto_start']:
+            mod_name = 'game.scenes.scene_' + self._options['development']['auto_start']
+            for member in import_module(mod_name).__dict__.values():
+                if isclass(member) and issubclass(member, Scene) and \
+                        member != Scene:
+                    cls = member
+            self._fsm.demand('Scene', cls)
+        else:
+            self._fsm.demand('Menu')
+
+    def on_menu_enter(self):
+        self._menu_bg = Background()
+        self._menu = Menu(
+            self._fsm, self.lang_mgr, self._options, self._music,
+            self._pipeline)
+
+    def on_home(self):
+        self._fsm.demand('Menu')
+
+    def on_menu_exit(self):
+        self._menu_bg.destroy()
+        self._menu.destroy()
+
+    def on_scene_enter(self, cls):
+        self._set_physics()
+        self._scene = cls(
+            self.world, self.on_home,
+            self._options['development']['auto_close_instructions'],
+            self._options['development']['debug_items'],
+            self.reload)
+
+    def on_scene_exit(self):
+        self._unset_physics()
+        self._scene.destroy()
+
+    def reload(self, cls):
+        self._fsm.demand('Scene', cls)
+
+    def _configure(self, args):
+        load_prc_file_data('', 'window-title pmachines')
+        load_prc_file_data('', 'framebuffer-srgb true')
+        load_prc_file_data('', 'sync-video true')
+        # load_prc_file_data('', 'threading-model Cull/Draw')
+        # it freezes when you go to the next scene
+        if args.screenshot:
+            load_prc_file_data('', 'window-type offscreen')
+            load_prc_file_data('', 'audio-library-name null')
+
+    def _parse_args(self):
+        parser = argparse.ArgumentParser()
+        parser.add_argument('--update', action='store_true')
+        parser.add_argument('--version', action='store_true')
+        parser.add_argument('--optfile')
+        parser.add_argument('--screenshot')
+        cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
+        args = parser.parse_args(cmd_line)
+        return args
+
+    def _prepare_window(self, args):
+        data_path = ''
+        if (platform.startswith('win') or platform.startswith('linux')) and (
+                not exists('main.py') or __file__.startswith('/app/bin/')):
+            # it is the deployed version for windows
+            data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
+            home = '/home/flavio'  # we must force this for wine
+            if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
+                data_path = home + '/.wine/drive_' + data_path[1:]
+            info('creating dirs: %s' % data_path)
+            makedirs(data_path, exist_ok=True)
+        optfile = args.optfile if args.optfile else 'options.ini'
+        info('data path: %s' % data_path)
+        info('option file: %s' % optfile)
+        info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
+        default_opt = {
+            'settings': {
+                'volume': 1,
+                'language': 'en',
+                'fullscreen': 1,
+                'resolution': '',
+                'antialiasing': 1,
+                'shadows': 1},
+            'development': {
+                'simplepbr': 1,
+                'verbose_log': 0,
+                'physics_debug': 0,
+                'auto_start': 0,
+                'auto_close_instructions': 0,
+                'show_buffers': 0,
+                'debug_items': 0,
+                'fps': 0}}
+        opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
+        opt_exists = exists(opt_path)
+        self._options = DctFile(
+            LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
+            default_opt)
+        if not opt_exists:
+            self._options.store()
+        res = self._options['settings']['resolution']
+        if res:
+            res = LVector2i(*[int(_res) for _res in res.split('x')])
+        else:
+            d_i = base.pipe.get_display_information()
+            def _res(idx):
+                return d_i.get_display_mode_width(idx), \
+                    d_i.get_display_mode_height(idx)
+            resolutions = [
+                _res(idx) for idx in range(d_i.get_total_display_modes())]
+            res = sorted(resolutions)[-1]
+        props = WindowProperties()
+        props.set_size(res)
+        props.set_fullscreen(self._options['settings']['fullscreen'])
+        props.set_icon_filename('assets/images/icon/pmachines.ico')
+        if not args.screenshot:
+            base.win.request_properties(props)
+        #gltf.patch_loader(base.loader)
+        if self._options['development']['simplepbr']:
+            self._pipeline = simplepbr.init(
+                use_normal_maps=True,
+                use_emission_maps=False,
+                use_occlusion_maps=True,
+                msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
+                enable_shadows=int(self._options['settings']['shadows']))
+            debug(f'msaa: {self._pipeline.msaa_samples}')
+            debug(f'shadows: {self._pipeline.enable_shadows}')
+        render.setAntialias(AntialiasAttrib.MAuto)
+        self.base.set_background_color(0, 0, 0, 1)
+        self.base.disable_mouse()
+        if self._options['development']['show_buffers']:
+            base.bufferViewer.toggleEnable()
+        if self._options['development']['fps']:
+            base.set_frame_rate_meter(True)
+        #self.base.accept('window-event', self._on_win_evt)
+        self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
+
+    def _set_physics(self):
+        if self._options['development']['physics_debug']:
+            debug_node = BulletDebugNode('Debug')
+            debug_node.show_wireframe(True)
+            debug_node.show_constraints(True)
+            debug_node.show_bounding_boxes(True)
+            debug_node.show_normals(True)
+            self._debug_np = render.attach_new_node(debug_node)
+            self._debug_np.show()
+        self.world = BulletWorld()
+        self.world.set_gravity((0, 0, -9.81))
+        if self._options['development']['physics_debug']:
+            self.world.set_debug_node(self._debug_np.node())
+        def update(task):
+            dt = globalClock.get_dt()
+            self.world.do_physics(dt, 10, 1/180)
+            return task.cont
+        self._phys_tsk = taskMgr.add(update, 'update')
+
+    def _unset_physics(self):
+        if self._options['development']['physics_debug']:
+            self._debug_np.remove_node()
+        self.world = None
+        taskMgr.remove(self._phys_tsk)
+
+    def _on_aspect_ratio_changed(self):
+        if self._fsm.state == 'Scene':
+            self._scene.on_aspect_ratio_changed()
diff --git a/game/items/__init__.py b/game/items/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/items/background.py b/game/items/background.py
new file mode 100644 (file)
index 0000000..8702543
--- /dev/null
@@ -0,0 +1,26 @@
+from itertools import product
+from panda3d.core import NodePath
+from lib.lib.p3d.gfx import set_srgb
+
+
+class Background:
+
+    def __init__(self):
+        self._root = NodePath('background_root')
+        self._root.reparent_to(render)
+        ncols, nrows = 16, 8
+        start_size, end_size = 5, 2.5
+        offset = 5
+        for col, row in product(range(ncols), range(nrows)):
+            model = loader.load_model('assets/models/bam/background/background.bam')
+            model.set_scale(end_size / start_size)
+            model.reparent_to(self._root)
+            total_width, total_height = end_size * ncols, end_size * nrows
+            left, bottom = -total_width/2, -total_height/2
+            model.set_pos(left + end_size * col, offset, bottom + end_size * row)
+        self._root.clear_model_nodes()
+        self._root.flatten_strong()
+        set_srgb(self._root)
+
+    def destroy(self):
+        self._root.remove_node()
diff --git a/game/items/basketball.py b/game/items/basketball.py
new file mode 100644 (file)
index 0000000..b27130a
--- /dev/null
@@ -0,0 +1,11 @@
+from panda3d.bullet import BulletSphereShape, BulletRigidBodyNode, BulletGhostNode
+from game.items.item import Item
+
+
+class Basketball(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.92, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/basketball/basketball.bam', .4, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletSphereShape(1))
diff --git a/game/items/box.py b/game/items/box.py
new file mode 100644 (file)
index 0000000..0687b56
--- /dev/null
@@ -0,0 +1,25 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from game.items.item import Item
+
+
+class Box(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/box/box.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((.5, .5, .5)))
+
+
+class HitStrategy:
+
+    def __init__(self, hit_by, node, world):
+        self._hit_by = hit_by
+        self._node = node
+        self._world = world
+
+    def win_condition(self):
+        for contact in self._world.contact_test(self._node).get_contacts():
+            other = contact.get_node1() if contact.get_node0() == self._node else contact.get_node0()
+            if other == self._hit_by.node:
+                return True
diff --git a/game/items/domino.py b/game/items/domino.py
new file mode 100644 (file)
index 0000000..7586953
--- /dev/null
@@ -0,0 +1,33 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from game.items.item import Item, StillStrategy
+
+
+class Domino(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/domino/domino.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((.1, .25, .5)))
+
+
+class UpStrategy(StillStrategy):
+
+    def __init__(self, np, tgt_degrees):
+        super().__init__(np)
+        self._tgt_degrees = tgt_degrees
+        self._np = np
+
+    def win_condition(self):
+        return super().win_condition() and abs(self._np.get_r()) <= self._tgt_degrees
+
+
+class DownStrategy(StillStrategy):
+
+    def __init__(self, np, tgt_degrees):
+        super().__init__(np)
+        self._tgt_degrees = tgt_degrees
+        self._np = np
+
+    def win_condition(self):
+        return self._np.get_z() < -6 or super().win_condition() and abs(self._np.get_r()) >= self._tgt_degrees
diff --git a/game/items/item.py b/game/items/item.py
new file mode 100644 (file)
index 0000000..fabd391
--- /dev/null
@@ -0,0 +1,339 @@
+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)
diff --git a/game/items/shelf.py b/game/items/shelf.py
new file mode 100644 (file)
index 0000000..dd39e83
--- /dev/null
@@ -0,0 +1,11 @@
+from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
+from game.items.item import Item
+
+
+class Shelf(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/shelf/shelf.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
+
+    def _set_shape(self, apply_scale=True):
+        self.node.add_shape(BulletBoxShape((1, .5, .05)))
diff --git a/game/items/teetertooter.py b/game/items/teetertooter.py
new file mode 100644 (file)
index 0000000..6fd01f2
--- /dev/null
@@ -0,0 +1,21 @@
+from panda3d.core import TransformState
+from panda3d.bullet import BulletCylinderShape, BulletRigidBodyNode, BulletGhostNode, YUp, ZUp
+from game.items.item import Item
+
+
+class TeeterTooter(Item):
+
+    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
+        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/teeter_tooter/teeter_tooter.bam', exp_num_contacts=2, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=.5)
+
+    def _set_shape(self, apply_scale=True):
+        scale = self._model_scale if apply_scale else 1
+        self.node.add_shape(
+            BulletCylinderShape(.1, 1.6, YUp),
+            TransformState.makePos((0, 0, scale * .36)))
+        self.node.add_shape(
+            BulletCylinderShape(.1, .7, ZUp),
+            TransformState.makePos((0, scale * .8, scale * -.1)))
+        self.node.add_shape(
+            BulletCylinderShape(.1, .7, ZUp),
+            TransformState.makePos((0, scale * -.8, scale * -.1)))
diff --git a/game/menu.py b/game/menu.py
new file mode 100644 (file)
index 0000000..ad6619f
--- /dev/null
@@ -0,0 +1,291 @@
+from logging import info, debug
+from sys import platform, exit
+from os import environ, system
+from glob import glob
+from importlib import import_module
+from inspect import isclass
+from webbrowser import open_new_tab
+from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
+    TextProperties, TextPropertiesManager
+from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
+    DirectOptionMenu, DirectSlider, DirectCheckButton
+from direct.gui.DirectGuiGlobals import FLAT
+from direct.gui.OnscreenText import OnscreenText
+from lib.engine.gui.cursor import MouseCursor
+from game.scene import Scene
+from panda3d.bullet import BulletWorld
+
+
+class Menu:
+
+    def __init__(self, fsm, lang_mgr, opt_file, music, pipeline):
+        self._fsm = fsm
+        self._lang_mgr = lang_mgr
+        self._opt_file = opt_file
+        self._music = music
+        self._pipeline = pipeline
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
+        self._font.clear()
+        self._font.set_pixels_per_unit(60)
+        self._font.set_minfilter(Texture.FTLinearMipmapLinear)
+        self._font.set_outline((0, 0, 0, 1), .8, .2)
+        self._widgets = []
+        self._common = {
+            'scale': .12,
+            'text_font': self._font,
+            'text_fg': (.9, .9, .9, 1),
+            'relief': FLAT,
+            'frameColor': (.4, .4, .4, .14),
+            'rolloverSound': loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            'clickSound': loader.load_sfx('assets/audio/sfx/click.ogg')}
+        self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
+        hlc = self._common_btn['frameColor']
+        hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
+        self._common_opt = {
+            'item_frameColor': self._common_btn['frameColor'],
+            'popupMarker_frameColor': self._common_btn['frameColor'],
+            'item_relief': self._common_btn['relief'],
+            'item_text_font': self._common_btn['text_font'],
+            'item_text_fg': self._common_btn['text_fg'],
+            'textMayChange': 1,
+            'highlightColor': hlc,
+            'text_align': TextNode.A_center,
+        } | self._common_btn
+        f_s = self._common_opt['frameSize']
+        self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
+        self._common_slider = self._common | {
+            'range': (0, 1),
+            'thumb_frameColor': (.4, .4, .4, .4),
+            'thumb_scale': 1.6,
+            'scale': .4}
+        del self._common_slider['rolloverSound']
+        del self._common_slider['clickSound']
+        self._set_main()
+
+    def _set_main(self):
+        self._widgets = []
+        self._widgets += [DirectButton(
+            text=_('Play'), pos=(0, 1, .6), command=self.on_play,
+            **self._common_btn)]
+        self._widgets += [DirectButton(
+            text=_('Options'), pos=(0, 1, .2), command=self.on_options,
+            **self._common_btn)]
+        self._widgets += [DirectButton(
+            text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
+            **self._common_btn)]
+        self._widgets += [DirectButton(
+            text=_('Exit'), pos=(0, 1, -.6), command=lambda: exit(),
+            **self._common_btn)]
+        self._rearrange_width()
+
+    def _set_options(self):
+        self._widgets = []
+        self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
+        items = [fnc() for fnc in self._lang_funcs]
+        inititem = {
+            'en': _('English'),
+            'it': _('Italian')
+        }[self._opt_file['settings']['language']]
+        btn = DirectOptionMenu(
+            text=_('Language'), items=items, initialitem=inititem,
+            pos=(0, 1, .8), command=self.on_language, **self._common_opt)
+        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
+        btn.popupMenu['relief'] = self._common_btn['relief']
+        self._widgets += [btn]
+        self._widgets += [OnscreenText(
+            _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_right)]
+        self._widgets += [DirectSlider(
+            pos=(.5, 1, .57),
+            value=self._opt_file['settings']['volume'],
+            command=self.on_volume,
+            **self._common_slider)]
+        self._slider = self._widgets[-1]
+        self._widgets += [DirectCheckButton(
+            text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['fullscreen'],
+            **self._common_btn)]
+        res = self._opt_file['settings']['resolution']
+        d_i = base.pipe.get_display_information()
+        def _res(idx):
+            return d_i.get_display_mode_width(idx), \
+                d_i.get_display_mode_height(idx)
+        resolutions = [
+            _res(idx) for idx in range(d_i.get_total_display_modes())]
+        resolutions = list(set(resolutions))
+        resolutions = sorted(resolutions)
+        resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
+        resolutions = ['x'.join(_res) for _res in resolutions]
+        if not res:
+            res = resolutions[-1]
+        btn = DirectOptionMenu(
+            text=_('Resolution'), items=resolutions, initialitem=res,
+            pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
+        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
+        btn.popupMenu['relief'] = self._common_btn['relief']
+        self._widgets += [btn]
+        self._widgets += [DirectCheckButton(
+            text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['antialiasing'],
+            **self._common_btn)]
+        self._widgets += [DirectCheckButton(
+            text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
+            indicator_frameColor=self._common_opt['highlightColor'],
+            indicator_relief=self._common_btn['relief'],
+            indicatorValue=self._opt_file['settings']['shadows'],
+            **self._common_btn)]
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+
+    def _set_credits(self):
+        self._widgets = []
+        tp_scale = TextProperties()
+        tp_scale.set_text_scale(.64)
+        TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
+        self._widgets += [OnscreenText(
+            _('Code and gfx\n  \1scale\1Flavio Calva\2\n\n\nMusic\n  \1scale\1Stefan Grossmann\2'),
+            pos=(-.9, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_left)]
+        self._widgets += [DirectButton(
+            text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
+            **self._common_btn | {'scale': .08})]
+        self._widgets += [OnscreenText(
+            _('Special thanks to:\n  \1scale\1rdb\2\n  \1scale\1Luisa Tenuta\2\n  \1scale\1Damiana Ercolani\2'),
+            pos=(.1, .55), font=self._common['text_font'],
+            scale=self._common['scale'], fg=self._common['text_fg'],
+            align=TextNode.A_left)]
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+
+    def on_play(self):
+        scenes = []
+        for _file in glob('game/scenes/*.py'):
+            _fn = _file.replace('.py', '').replace('/', '.')
+            for member in import_module(_fn).__dict__.values():
+                if isclass(member) and issubclass(member, Scene) and \
+                        member != Scene:
+                    scenes += [member]
+        scenes = sorted(scenes, key=lambda elm: elm.sorting)
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._widgets = []
+        cmn = self._common_btn.copy() | {
+            'frameSize': (-2.4, 2.4, -2.4, 2.4),
+            'frameColor': (1, 1, 1, .8),
+            'text_scale': .64}
+        left = - (dx := .8) * (min(4, len(scenes)) - 1) / 2
+        for i, cls in enumerate(scenes):
+            top = .1 if len(scenes) < 5 else .6
+            row = 0 if i < 4 else 1
+            self._widgets += [DirectButton(
+                text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
+                command=self.start, extraArgs=[cls], text_wordwrap=6,
+                frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
+                **cmn)]
+            for j in range(4):
+                tnode = self._widgets[-1].component('text%s' % j).textNode
+                height = - tnode.getLineHeight() / 2
+                height += (tnode.get_height() - tnode.get_line_height()) / 2
+                self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
+        self._widgets += [DirectButton(
+            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
+            **self._common_btn)]
+
+    def start(self, cls):
+        self._fsm.demand('Scene', cls)
+
+    def on_options(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_options()
+
+    def on_credits(self):
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_credits()
+
+    def _rearrange_width(self):
+        max_width = 0
+        for wdg in self._widgets:
+            t_n = wdg.component('text0')
+            u_l = t_n.textNode.get_upper_left_3d()
+            l_r = t_n.textNode.get_lower_right_3d()
+            max_width = max(l_r[0] - u_l[0], max_width)
+        for wdg in self._widgets:
+            m_w = max_width / 2 + .8
+            wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
+
+    def on_language(self, arg):
+        lang_code = {
+            _('English'): 'en_EN',
+            _('Italian'): 'it_IT'}[arg]
+        self._lang_mgr.set_lang(lang_code)
+        self._opt_file['settings']['language'] = lang_code[:2]
+        self._opt_file.store()
+        self.on_options()
+
+    def on_volume(self):
+        self._opt_file['settings']['volume'] = self._slider['value']
+        self._music.set_volume(self._slider['value'])
+
+    def on_fullscreen(self, arg):
+        props = WindowProperties()
+        props.set_fullscreen(arg)
+        base.win.request_properties(props)
+        self._opt_file['settings']['fullscreen'] = int(arg)
+        self._opt_file.store()
+
+    def on_resolution(self, arg):
+        props = WindowProperties()
+        props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
+        base.win.request_properties(props)
+        self._opt_file['settings']['resolution'] = arg
+        self._opt_file.store()
+
+    def on_aa(self, arg):
+        self._pipeline.msaa_samples = 4 if arg else 1
+        debug(f'msaa: {self._pipeline.msaa_samples}')
+        self._opt_file['settings']['antialiasing'] = int(arg)
+        self._opt_file.store()
+
+    def on_shadows(self, arg):
+        self._pipeline.enable_shadows = int(arg)
+        debug(f'shadows: {self._pipeline.enable_shadows}')
+        self._opt_file['settings']['shadows'] = int(arg)
+        self._opt_file.store()
+
+    def on_website(self):
+        if platform.startswith('linux'):
+            environ['LD_LIBRARY_PATH'] = ''
+            system('xdg-open https://www.ya2.it')
+        else:
+            open_new_tab('https://www.ya2.it')
+
+    def on_back(self):
+        self._opt_file.store()
+        self.destroy()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_main()
+
+    def destroy(self):
+        [wdg.destroy() for wdg in self._widgets]
+        self._cursor.destroy()
diff --git a/game/music.py b/game/music.py
new file mode 100644 (file)
index 0000000..2dfb2cd
--- /dev/null
@@ -0,0 +1,29 @@
+from glob import glob
+from random import choice
+from logging import info
+from panda3d.core import AudioSound
+
+
+class MusicMgr:
+
+    def __init__(self, volume):
+        self._start_music(glob('assets/audio/music/*.ogg'))
+        base.musicManager.setVolume(.8 * volume)
+        base.sfxManagerList[0].setVolume(volume)
+        taskMgr.add(self._on_frame, 'on frame music')
+
+    def _start_music(self, files):
+        self._music = loader.load_music(choice(files))
+        info('playing music ' + self._music.get_name())
+        self._music.play()
+
+    def set_volume(self, volume):
+        base.musicManager.setVolume(.8 * volume)
+        base.sfxManagerList[0].setVolume(volume)
+
+    def _on_frame(self, task):
+        if self._music.status() == AudioSound.READY:
+            files = glob('assets/audio/music/*.ogg')
+            files.remove('assets/audio/music/' + self._music.get_name())
+            self._start_music(files)
+        return task.cont
diff --git a/game/scene.py b/game/scene.py
new file mode 100644 (file)
index 0000000..b44506a
--- /dev/null
@@ -0,0 +1,582 @@
+from os.path import exists
+from os import makedirs
+from glob import glob
+from importlib import import_module
+from inspect import isclass
+from panda3d.core import AmbientLight, DirectionalLight, Point3, Texture, \
+    TextPropertiesManager, TextNode, Spotlight, PerspectiveLens, BitMask32
+from panda3d.bullet import BulletPlaneShape, BulletGhostNode
+from direct.gui.OnscreenImage import OnscreenImage
+from direct.gui.OnscreenText import OnscreenText
+from direct.gui.DirectGui import DirectButton, DirectFrame
+from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
+from direct.showbase.DirectObject import DirectObject
+from game.items.background import Background
+from game.sidepanel import SidePanel
+from lib.engine.gui.cursor import MouseCursor
+from lib.lib.p3d.gfx import P3dGfxMgr
+
+
+class Scene(DirectObject):
+
+    def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb):
+        super().__init__()
+        self._world = world
+        self._exit_cb = exit_cb
+        self._dbg_items = dbg_items
+        self._reload_cb = reload_cb
+        self._set_camera()
+        self._cursor = MouseCursor(
+            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
+            (.01, .01))
+        self._set_gui()
+        self._set_lights()
+        self._set_input()
+        self._set_mouse_plane()
+        self.items = []
+        self.reset()
+        self._state = 'init'
+        self._paused = False
+        self._item_active = None
+        if auto_close_instr:
+            self.__store_state()
+            self.__restore_state()
+        else:
+            self._set_instructions()
+        self._bg = Background()
+        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')
+
+    @staticmethod
+    def name():
+        return ''
+
+    def _instr_txt(self):
+        return ''
+
+    def _set_items(self):
+        self.items = []
+
+    def screenshot(self, task=None):
+        tex = Texture('screenshot')
+        buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
+        cam = base.make_camera(buffer)
+        cam.reparent_to(render)
+        cam.node().get_lens().set_fov(base.camLens.get_fov())
+        cam.set_pos(0, -20, 0)
+        cam.look_at(0, 0, 0)
+        import simplepbr
+        simplepbr.init(
+            window=buffer,
+            camera_node=cam,
+            use_normal_maps=True,
+            use_emission_maps=False,
+            use_occlusion_maps=True,
+            msaa_samples=4,
+            enable_shadows=True)
+        base.graphicsEngine.renderFrame()
+        base.graphicsEngine.renderFrame()
+        fname = self.__class__.__name__
+        if not exists('assets/images/scenes'):
+            makedirs('assets/images/scenes')
+        buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
+        # img = DirectButton(
+        #     frameTexture=buffer.get_texture(), relief=FLAT,
+        #     frameSize=(-.2, .2, -.2, .2))
+        return buffer.get_texture()
+
+    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._set_items()
+        self._state = 'init'
+        self._commands = []
+        self._command_idx = 0
+        if hasattr(self, '_success_txt'):
+            self._success_txt.destroy()
+            del self._success_txt
+        self.__right_btn['state'] = NORMAL
+
+    def destroy(self):
+        self._unset_gui()
+        self._unset_lights()
+        self._unset_input()
+        self._unset_mouse_plane()
+        [itm.destroy() for itm in self.items]
+        self._bg.destroy()
+        self._side_panel.destroy()
+        self._cursor.destroy()
+        taskMgr.remove(self._scene_tsk)
+        if hasattr(self, '_success_txt'):
+            self._success_txt.destroy()
+
+    def _set_camera(self):
+        base.camera.set_pos(0, -20, 0)
+        base.camera.look_at(0, 0, 0)
+
+    def __load_img_btn(self, path, col):
+        img = OnscreenImage('assets/images/buttons/%s.dds' % path)
+        img.set_transparency(True)
+        img.set_color(col)
+        img.detach_node()
+        return img
+
+    def _set_gui(self):
+        def load_images_btn(path, col):
+            colors = {
+                'gray': [
+                    (.6, .6, .6, 1),  # ready
+                    (1, 1, 1, 1), # press
+                    (.8, .8, .8, 1), # rollover
+                    (.4, .4, .4, .4)],
+                'green': [
+                    (.1, .68, .1, 1),
+                    (.1, 1, .1, 1),
+                    (.1, .84, .1, 1),
+                    (.4, .1, .1, .4)]}[col]
+            return [self.__load_img_btn(path, col) for col in colors]
+        abl, abr = base.a2dBottomLeft, base.a2dBottomRight
+        btn_info = [
+            ('home', self.on_home, NORMAL, abl, 'gray'),
+            ('information', self._set_instructions, NORMAL, abl, 'gray'),
+            ('right', self.on_play, NORMAL, abr, 'green'),
+            ('next', self.on_next, DISABLED, abr, 'gray'),
+            ('previous', self.on_prev, DISABLED, abr, 'gray'),
+            ('rewind', self.reset, NORMAL, abr, 'gray')]
+        num_l = num_r = 0
+        btns = []
+        for binfo in btn_info:
+            imgs = load_images_btn(binfo[0], binfo[4])
+            if binfo[3] == base.a2dBottomLeft:
+                sign, num = 1, num_l
+                num_l += 1
+            else:
+                sign, num = -1, num_r
+                num_r += 1
+            fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+            btn = DirectButton(
+                image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
+                parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
+                frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
+                rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+                clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+            btn.set_transparency(True)
+            btns += [btn]
+        self.__home_btn, self.__info_btn, self.__right_btn, self.__next_btn, \
+            self.__prev_btn, self.__rewind_btn = btns
+        if self._dbg_items:
+            self._info_txt = OnscreenText(
+                '', parent=base.a2dTopRight, scale=0.04,
+                pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
+
+    def _unset_gui(self):
+        btns = [
+            self.__home_btn, self.__info_btn, self.__right_btn,
+            self.__next_btn, self.__prev_btn, self.__rewind_btn]
+        [btn.destroy() for btn in btns]
+        if self._dbg_items:
+            self._info_txt.destroy()
+
+    def _set_spotlight(self, name, pos, look_at, color, shadows=False):
+        light = Spotlight(name)
+        if shadows:
+            light.setLens(PerspectiveLens())
+        light_np = render.attach_new_node(light)
+        light_np.set_pos(pos)
+        light_np.look_at(look_at)
+        light.set_color(color)
+        render.set_light(light_np)
+        return light_np
+
+    def _set_lights(self):
+        alight = AmbientLight('alight')  # for ao
+        alight.set_color((.15, .15, .15, 1))
+        self._alnp = render.attach_new_node(alight)
+        render.set_light(self._alnp)
+        self._key_light = self._set_spotlight(
+            'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
+        self._shadow_light = self._set_spotlight(
+            'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
+        self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
+        self._shadow_light.node().get_lens().set_film_size(2048, 2048)
+        self._shadow_light.node().get_lens().set_near_far(1, 256)
+        self._shadow_light.node().set_camera_mask(BitMask32(0x01))
+
+    def _unset_lights(self):
+        for light in [self._alnp, self._key_light, self._shadow_light]:
+            render.clear_light(light)
+            light.remove_node()
+
+    def _set_input(self):
+        self.accept('mouse1', self.on_click_l)
+        self.accept('mouse1-up', self.on_release)
+        self.accept('mouse3', self.on_click_r)
+        self.accept('mouse3-up', self.on_release)
+
+    def _unset_input(self):
+        for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
+            self.ignore(evt)
+
+    def _set_mouse_plane(self):
+        shape = BulletPlaneShape((0, -1, 0), 0)
+        #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
+        self._mouse_plane_node = BulletGhostNode('mouse plane')
+        self._mouse_plane_node.addShape(shape)
+        #np = render.attachNewNode(self._mouse_plane_node)
+        #self._world.attachRigidBody(self._mouse_plane_node)
+        self._world.attach_ghost(self._mouse_plane_node)
+
+    def _unset_mouse_plane(self):
+        self._world.remove_ghost(self._mouse_plane_node)
+
+    def _get_hits(self):
+        if not base.mouseWatcherNode.has_mouse(): return []
+        p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
+        return self._world.ray_test_all(p_from, p_to).get_hits()
+
+    def _update_info(self, item):
+        txt = ''
+        if item:
+            txt = '%.3f %.3f\n%.3f°' % (
+                item._np.get_x(), item._np.get_z(), item._np.get_r())
+        self._info_txt['text'] = txt
+
+    def _on_click(self, method):
+        if self._paused:
+            return
+        for hit in self._get_hits():
+            if hit.get_node() == self._mouse_plane_node:
+                pos = hit.get_hit_pos()
+        for hit in self._get_hits():
+            for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
+                if not self._item_active:
+                    self._item_active = item
+                getattr(item, method)(pos)
+                img = 'move' if method == 'on_click_l' else 'rotate'
+                if not (img == 'rotate' and not item._instantiated):
+                    self._cursor.set_image('assets/images/buttons/%s.dds' % img)
+
+    def on_click_l(self):
+        self._on_click('on_click_l')
+
+    def on_click_r(self):
+        self._on_click('on_click_r')
+
+    def on_release(self):
+        if self._item_active and not self._item_active._first_command:
+            self._commands = self._commands[:self._command_idx]
+            self._commands += [self._item_active]
+            self._command_idx += 1
+            self.__prev_btn['state'] = NORMAL
+            fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+            self.__prev_btn['frameColor'] = fcols[0]
+            if self._item_active._command_idx == len(self._item_active._commands) - 1:
+                self.__next_btn['state'] = DISABLED
+                self.__next_btn['frameColor'] = fcols[1]
+        self._item_active = None
+        [item.on_release() for item in self.items]
+        self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
+
+    def repos(self):
+        for item in self.items:
+            item.repos_done = False
+        self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
+        [item.on_aspect_ratio_changed() for item in self.items]
+        self._side_panel.update(self.items)
+        max_x = -float('inf')
+        for item in self.items:
+            if not item._instantiated:
+                max_x = max(item._np.get_x(), max_x)
+        for item in self.items:
+            if not item._instantiated:
+                item.repos_x(max_x)
+
+    def on_aspect_ratio_changed(self):
+        self.repos()
+
+    def _win_condition(self):
+        pass
+
+    def _fail_condition(self):
+        return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
+
+    def on_frame(self, task):
+        hits = self._get_hits()
+        pos = None
+        for hit in self._get_hits():
+            if hit.get_node() == self._mouse_plane_node:
+                pos = hit.get_hit_pos()
+        hit_nodes = [hit.get_node() for hit in hits]
+        if self._item_active:
+            items_hit = [self._item_active]
+        else:
+            items_hit = [itm for itm in self.items if itm.node in hit_nodes]
+        items_no_hit = [itm for itm in self.items if itm not in items_hit]
+        [itm.on_mouse_on() for itm in items_hit]
+        [itm.on_mouse_off() for itm in items_no_hit]
+        if pos and self._item_active:
+            self._item_active.on_mouse_move(pos)
+        if self._dbg_items:
+            self._update_info(items_hit[0] if items_hit else None)
+        if self._win_condition():
+            self._set_win()
+        elif self._state == 'playing' and self._fail_condition():
+            self._set_fail()
+        if any(itm._overlapping for itm in self.items):
+            self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
+        else:
+            self._cursor.cursor_img.img.set_color(.9, .9, .9, 1)
+        return task.cont
+
+    def cb_inst(self, item):
+        self.items += [item]
+
+    def on_play(self):
+        self._state = 'playing'
+        self.__prev_btn['state'] = DISABLED
+        self.__next_btn['state'] = DISABLED
+        self.__right_btn['state'] = DISABLED
+        [itm.play() for itm in self.items]
+
+    def on_next(self):
+        self._commands[self._command_idx].redo()
+        self._command_idx += 1
+        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+        self.__prev_btn['state'] = NORMAL
+        self.__prev_btn['frameColor'] = fcols[0]
+        more_commands = self._command_idx < len(self._commands)
+        self.__next_btn['state'] = NORMAL if more_commands else DISABLED
+        self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
+
+    def on_prev(self):
+        self._command_idx -= 1
+        self._commands[self._command_idx].undo()
+        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
+        self.__next_btn['state'] = NORMAL
+        self.__next_btn['frameColor'] = fcols[0]
+        self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
+        self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
+
+    def on_home(self):
+        self._exit_cb()
+
+    def _set_instructions(self):
+        self._paused = True
+        self.__store_state()
+        mgr = TextPropertiesManager.get_global_ptr()
+        for name in ['mouse_l', 'mouse_r']:
+            graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
+            graphic.set_scale(.5)
+            graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
+            graphic.get_texture().set_anisotropic_degree(2)
+            mgr.set_graphic(name, graphic)
+            graphic.set_z(-.2)
+            graphic.set_transparency(True)
+            graphic.detach_node()
+        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+                          frameSize=(-.6, .6, -.3, .3))
+        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(
+            self._instr_txt(), parent=frm, font=font, scale=0.06,
+            fg=(.9, .9, .9, 1), align=TextNode.A_left)
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+        btn_scale = .05
+        mar = .06  # margin
+        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+        z += (btn_scale + 2 * mar) / 2
+        self._txt['pos'] = -w / 2, z
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+        frm['frameSize'] = fsz
+        colors = [
+            (.6, .6, .6, 1),  # ready
+            (1, 1, 1, 1), # press
+            (.8, .8, .8, 1), # rollover
+            (.4, .4, .4, .4)]
+        imgs = [self.__load_img_btn('exitRight', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn.set_transparency(True)
+
+    def _set_win(self):
+        loader.load_sfx('assets/audio/sfx/success.ogg').play()
+        self._paused = True
+        self.__store_state()
+        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+                          frameSize=(-.6, .6, -.3, .3))
+        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(
+            _('You win!'),
+            parent=frm,
+            font=font, scale=0.2,
+            fg=(.9, .9, .9, 1))
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+        btn_scale = .05
+        mar = .06  # margin
+        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+        z += (btn_scale + 2 * mar) / 2
+        self._txt['pos'] = 0, z
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+        frm['frameSize'] = fsz
+        colors = [
+            (.6, .6, .6, 1),  # ready
+            (1, 1, 1, 1), # press
+            (.8, .8, .8, 1), # rollover
+            (.4, .4, .4, .4)]
+        imgs = [self.__load_img_btn('home', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_end_home, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn.set_transparency(True)
+        imgs = [self.__load_img_btn('rewind', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(0, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_restart, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn.set_transparency(True)
+        scenes = []
+        for _file in glob('game/scenes/*.py'):
+            _fn = _file.replace('.py', '').replace('/', '.')
+            for member in import_module(_fn).__dict__.values():
+                if isclass(member) and issubclass(member, Scene) and \
+                        member != Scene:
+                    scenes += [member]
+        scenes = sorted(scenes, key=lambda elm: elm.sorting)
+        enabled = scenes.index(self.__class__) < len(scenes) - 1
+        if enabled:
+            next_scene = scenes[scenes.index(self.__class__) + 1]
+        else:
+            next_scene = None
+        imgs = [self.__load_img_btn('right', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_next_scene,
+            extraArgs=[frm, next_scene], relief=FLAT,
+            frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn['state'] = NORMAL if enabled else DISABLED
+        btn.set_transparency(True)
+
+    def _set_fail(self):
+        loader.load_sfx('assets/audio/sfx/success.ogg').play()
+        self._paused = True
+        self.__store_state()
+        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
+                          frameSize=(-.6, .6, -.3, .3))
+        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(
+            _('You have failed!'),
+            parent=frm,
+            font=font, scale=0.2,
+            fg=(.9, .9, .9, 1))
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
+        btn_scale = .05
+        mar = .06  # margin
+        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
+        z += (btn_scale + 2 * mar) / 2
+        self._txt['pos'] = 0, z
+        u_l = self._txt.textNode.get_upper_left_3d()
+        l_r = self._txt.textNode.get_lower_right_3d()
+        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
+        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
+        frm['frameSize'] = fsz
+        colors = [
+            (.6, .6, .6, 1),  # ready
+            (1, 1, 1, 1), # press
+            (.8, .8, .8, 1), # rollover
+            (.4, .4, .4, .4)]
+        imgs = [self.__load_img_btn('home', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_end_home, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn.set_transparency(True)
+        imgs = [self.__load_img_btn('rewind', col) for col in colors]
+        btn = DirectButton(
+            image=imgs, scale=btn_scale,
+            pos=(0, 1, l_r[2] - mar - btn_scale),
+            parent=frm, command=self._on_restart, extraArgs=[frm],
+            relief=FLAT, frameColor=(.6, .6, .6, .08),
+            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
+            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
+        btn.set_transparency(True)
+
+    def _on_restart(self, frm):
+        self.__on_close_instructions(frm)
+        self.reset()
+
+    def _on_end_home(self, frm):
+        self.__on_close_instructions(frm)
+        self.on_home()
+
+    def _on_next_scene(self, frm, scene):
+        self.__on_close_instructions(frm)
+        self._reload_cb(scene)
+
+    def __store_state(self):
+        btns = [
+            self.__home_btn, self.__info_btn, self.__right_btn,
+            self.__next_btn, self.__prev_btn, self.__rewind_btn]
+        self.__btn_state = [btn['state'] for btn in btns]
+        for btn in btns:
+            btn['state'] = DISABLED
+        [itm.store_state() for itm in self.items]
+
+    def __restore_state(self):
+        btns = [
+            self.__home_btn, self.__info_btn, self.__right_btn,
+            self.__next_btn, self.__prev_btn, self.__rewind_btn]
+        for btn, state in zip(btns, self.__btn_state):
+            btn['state'] = state
+        [itm.restore_state() for itm in self.items]
+        self._paused = False
+
+    def __on_close_instructions(self, frm):
+        frm.remove_node()
+        self.__restore_state()
diff --git a/game/scenes/__init__.py b/game/scenes/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/scenes/scene_basketball.py b/game/scenes/scene_basketball.py
new file mode 100644 (file)
index 0000000..1118c29
--- /dev/null
@@ -0,0 +1,61 @@
+from game.scene import Scene
+from game.items.box import Box
+from game.items.shelf import Shelf
+from game.items.domino import Domino, UpStrategy, DownStrategy
+from game.items.basketball import Basketball
+from game.items.teetertooter import TeeterTooter
+
+
+class SceneBasketBall(Scene):
+
+    sorting = 3
+
+    @staticmethod
+    def name():
+        return _('Basket ball')
+
+    def _set_items(self):
+        self.items = []
+        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-4.45, 0, -3.18), r=27, restitution=1)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-5.45, 0, -3.18), restitution=1)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 30))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
+        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
+        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
+        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: you must hit every domino piece\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_box.py b/game/scenes/scene_box.py
new file mode 100644 (file)
index 0000000..6bd92a1
--- /dev/null
@@ -0,0 +1,45 @@
+from game.scene import Scene
+from game.items.box import Box, HitStrategy
+from game.items.shelf import Shelf
+from game.items.domino import Domino
+from game.items.basketball import Basketball
+from game.items.teetertooter import TeeterTooter
+
+
+class SceneBox(Scene):
+
+    sorting = 1
+
+    @staticmethod
+    def name():
+        return _('Box')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.46, 0, -3.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.43, 0, -3.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.29, 0, .26), r=28.45)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.15, 0, -1.49), r=28.45)]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.55, 0, 1.23), friction=.4)]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.38, 0, -3.35))]
+        self.items[-1].set_strategy(HitStrategy(self.items[-2], self.items[-1].node, self.items[-1]._world))
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04), tgt_degrees=60)]
+        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04), tgt_degrees=88)]
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: the left box must hit the right box\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_domino.py b/game/scenes/scene_domino.py
new file mode 100644 (file)
index 0000000..3481881
--- /dev/null
@@ -0,0 +1,45 @@
+from game.scene import Scene
+from game.items.box import Box
+from game.items.shelf import Shelf
+from game.items.domino import Domino, DownStrategy
+from game.items.basketball import Basketball
+from game.items.teetertooter import TeeterTooter
+
+
+class SceneDomino(Scene):
+
+    sorting = 0
+
+    @staticmethod
+    def name():
+        return _('Domino')
+
+    def _set_items(self):
+        self.items = []
+        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.2, 0, -.6))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.2, 0, -.6))]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 88))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must fall\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_domino_box.py b/game/scenes/scene_domino_box.py
new file mode 100644 (file)
index 0000000..db8753d
--- /dev/null
@@ -0,0 +1,58 @@
+from game.scene import Scene
+from game.items.box import Box
+from game.items.shelf import Shelf
+from game.items.domino import Domino, UpStrategy, DownStrategy
+from game.items.basketball import Basketball
+from game.items.teetertooter import TeeterTooter
+
+
+class SceneDominoBox(Scene):
+
+    sorting = 2
+
+    @staticmethod
+    def name():
+        return _('Domino and box')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.94), r=37)]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
+        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: only the last piece of each row must be up\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_domino_box_basketball.py b/game/scenes/scene_domino_box_basketball.py
new file mode 100644 (file)
index 0000000..0807029
--- /dev/null
@@ -0,0 +1,47 @@
+from game.scene import Scene
+from game.items.box import Box
+from game.items.shelf import Shelf
+from game.items.domino import Domino, UpStrategy, DownStrategy
+from game.items.basketball import Basketball
+from game.items.teetertooter import TeeterTooter
+
+
+class SceneDominoBoxBasketball(Scene):
+
+    sorting = 4
+
+    @staticmethod
+    def name():
+        return _('Domino, box and basket ball')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1, mass=5)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.3, 1, 2.5))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.68, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.35, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.08, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.78, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.53, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must be hit\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_teeter_domino_box_basketball.py b/game/scenes/scene_teeter_domino_box_basketball.py
new file mode 100644 (file)
index 0000000..7a799bb
--- /dev/null
@@ -0,0 +1,57 @@
+from game.scene import Scene
+from game.items.box import Box
+from game.items.shelf import Shelf
+from game.items.domino import Domino, UpStrategy, DownStrategy
+from game.items.basketball import Basketball
+from game.items.teetertooter import TeeterTooter
+
+
+class SceneTeeterDominoBoxBasketball(Scene):
+
+    sorting = 6
+
+    @staticmethod
+    def name():
+        return _('Teeter tooter, domino, box and basket ball')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=2, friction=1)]
+        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(.98, 1, 1.02))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.45))]
+        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.20))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=24.60, friction=1, pos=(-6.15, 0, -.93))]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.3, friction=1, model_scale=.5, pos=(-5.38, 0, -.93), r=24.60)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(5.37, 0, -.78))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(7.48, 0, -.78))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.74, 0, -1.95))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(6.88, 0, -1.95))]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(4.83, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(5.67, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(6.59, 0, -1.39))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.53, 0, -1.95), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.63, 0, -1.95), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-3.65, 0, 1.05), r=28, friction=0)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.27, 0, 1.72), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.88, 0, 1.72), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.67, 0, .55), restitution=.95)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.52, 0, .55), restitution=.95)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-1.73, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.97, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.1, 0, 1.11))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: every domino piece must be hit\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/scenes/scene_teeter_tooter.py b/game/scenes/scene_teeter_tooter.py
new file mode 100644 (file)
index 0000000..b66cf8e
--- /dev/null
@@ -0,0 +1,48 @@
+from game.scene import Scene
+from game.items.box import Box
+from game.items.shelf import Shelf
+from game.items.domino import Domino, UpStrategy, DownStrategy
+from game.items.basketball import Basketball
+from game.items.teetertooter import TeeterTooter
+
+
+class SceneTeeterTooter(Scene):
+
+    sorting = 5
+
+    @staticmethod
+    def name():
+        return _('Teeter tooter')
+
+    def _set_items(self):
+        self.items = []
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=1, friction=1)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.76, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.27, 0, -.28))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.38, 0, -.28))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
+        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.74, 0, -1.20))]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=-25.30, friction=1, pos=(-2.78, 0, -.93))]
+        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.2, friction=1, model_scale=.5, pos=(-3.61, 0, -.99), r=-25.30)]
+        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.25, 0, -.57), r=52)]
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.50, 0, -.89))]
+        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
+        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
+
+    def _instr_txt(self):
+        txt = _('Scene: ') + self.name() + '\n\n'
+        txt += _('Goal: you must hit every domino piece\n\n')
+        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
+                'keep \5mouse_r\5 pressed to rotate an item')
+        return txt
+
+    def _win_condition(self):
+        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/game/sidepanel.py b/game/sidepanel.py
new file mode 100644 (file)
index 0000000..1523e21
--- /dev/null
@@ -0,0 +1,132 @@
+from textwrap import dedent
+from panda3d.core import GeomVertexData, GeomVertexFormat, Geom, \
+    GeomVertexWriter, GeomTriangles, GeomNode, Shader, Point3, Plane, Vec3
+from lib.lib.p3d.gfx import P3dGfxMgr
+
+
+class SidePanel:
+
+    def __init__(self, world, plane_node, top_l, bottom_r, y, items):
+        self._world = world
+        self._plane_node = plane_node
+        self._set((-1, 1), y)
+        self.update(items)
+
+    def update(self, items):
+        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()
+        y = 0
+        corner = -20, 20
+        for item in items:
+            if not item._instantiated:
+                bounds = item._np.get_tight_bounds()
+                if bounds[1][1] > y:
+                    y = bounds[1][1]
+                icorner = item.get_corner()
+                icorner = P3dGfxMgr.screen_coord(icorner)
+                if icorner[0] > corner[0]:
+                    corner = icorner[0], corner[1]
+                if icorner[1] < corner[1]:
+                    corner = corner[0], icorner[1]
+        self._set((pos[0], pos[2]), y)
+        bounds = self._np.get_tight_bounds()
+        corner3d = bounds[1][0], bounds[1][1], bounds[0][2]
+        corner2d = P3dGfxMgr.screen_coord(corner3d)
+        plane = Plane(Vec3(0, 1, 0), y)
+        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
+        ar, margin = base.get_aspect_ratio(), .04
+        x = corner[0] / ar if ar >= 1 else corner[0]
+        z = corner[1] * ar if ar < 1 else corner[1]
+        x += margin / ar if ar >= 1 else margin
+        z -= margin * ar if ar < 1 else margin
+        base.camLens.extrude((x, 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))
+        corner_pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
+        base.camLens.extrude((-1, 1), near_pt, far_pt)
+        plane.intersects_line(
+            corner_pos3d, render.get_relative_point(base.camera, near_pt),
+            render.get_relative_point(base.camera, far_pt))
+        self._np.set_pos((pos3d + corner_pos3d) / 2)
+        scale = Vec3(pos3d[0] - corner_pos3d[0], 1, corner_pos3d[2] - pos3d[2])
+        self._np.set_scale(scale)
+
+    def _set(self, pos, y):
+        if hasattr(self, '_np'):
+            self._np.remove_node()
+        vdata = GeomVertexData('quad', GeomVertexFormat.get_v3(), Geom.UHStatic)
+        vdata.setNumRows(2)
+        vertex = GeomVertexWriter(vdata, 'vertex')
+        vertex.add_data3(.5, 0, -.5)
+        vertex.add_data3(.5, 0, .5)
+        vertex.add_data3(-.5, 0, .5)
+        vertex.add_data3(-.5, 0, -.5)
+        prim = GeomTriangles(Geom.UHStatic)
+        prim.add_vertices(0, 1, 2)
+        prim.add_vertices(0, 2, 3)
+        prim.close_primitive()
+        geom = Geom(vdata)
+        geom.add_primitive(prim)
+        node = GeomNode('gnode')
+        node.add_geom(geom)
+        self._np = render.attach_new_node(node)
+        self._np.setTransparency(True)
+        self._np.set_pos(pos[0], y, pos[1])
+        vert = '''\
+            #version 150
+            uniform mat4 p3d_ModelViewProjectionMatrix;
+            in vec4 p3d_Vertex;
+            void main() {
+                gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; }'''
+        frag = '''\
+            #version 150
+            out vec4 p3d_FragColor;
+            void main() {
+                p3d_FragColor = vec4(.04, .04, .04, .08); }'''
+        self._np.set_shader(Shader.make(Shader.SL_GLSL, dedent(vert), dedent(frag)))
+        # mat = Material()
+        # mat.set_base_color((1, 1, 1, 1))
+        # mat.set_emission((1, 1, 1, 1))
+        # mat.set_metallic(.5)
+        # mat.set_roughness(.5)
+        # np.set_material(mat)
+        # texture_sz = 64
+        # base_color_pnm = PNMImage(texture_sz, texture_sz)
+        # base_color_pnm.fill(.1, .1, .1)
+        # base_color_pnm.add_alpha()
+        # base_color_pnm.alpha_fill(.04)
+        # base_color_tex = Texture('base color')
+        # base_color_tex.load(base_color_pnm)
+        # ts = TextureStage('base color')
+        # ts.set_mode(TextureStage.M_modulate)
+        # np.set_texture(ts, base_color_tex)
+        # emission_pnm = PNMImage(texture_sz, texture_sz)
+        # emission_pnm.fill(0, 0, 0)
+        # emission_tex = Texture('emission')
+        # emission_tex.load(emission_pnm)
+        # ts = TextureStage('emission')
+        # ts.set_mode(TextureStage.M_emission)
+        # np.set_texture(ts, emission_tex)
+        # metal_rough_pnm = PNMImage(texture_sz, texture_sz)
+        # ambient_occlusion = 1
+        # roughness = .5
+        # metallicity = .5
+        # metal_rough_pnm.fill(ambient_occlusion, roughness, metallicity)
+        # metal_rough_tex = Texture('ao metal roughness')
+        # metal_rough_tex.load(metal_rough_pnm)
+        # ts = TextureStage('ao metal roughness')
+        # ts.set_mode(TextureStage.M_selector)
+        # np.set_texture(ts, metal_rough_tex)
+        # normal_pnm = PNMImage(texture_sz, texture_sz)
+        # normal_pnm.fill(.5, .5, .1)
+        # normal_tex = Texture('normals')
+        # normal_tex.load(normal_pnm)
+        # ts = TextureStage('normals')
+        # ts.set_mode(TextureStage.M_normal)
+        # np.set_texture(ts, normal_tex)
+
+    def destroy(self):
+        self._np.remove_node()
index 71c845a0614b9754a6208a6266f82fc9f50e9598..bb9921e819959c3f1a779a3a66ca90adc3af5b95 100644 (file)
@@ -3,7 +3,7 @@ from glob import glob
 from importlib import import_module
 from inspect import isclass
 from multiprocessing import Pool
-from pmachines.scene import Scene
+from game.scene import Scene
 
 
 def do_screenshot(cls):
@@ -12,7 +12,7 @@ def do_screenshot(cls):
 
 def bld_screenshots():
     scene_classes = []
-    for _file in glob('pmachines/scenes/*.py'):
+    for _file in glob('game/scenes/*.py'):
         _fn = _file.replace('.py', '').replace('/', '.')
         for member in import_module(_fn).__dict__.values():
             if isclass(member) and issubclass(member, Scene) and \
diff --git a/main.py b/main.py
index 74999f5da5ba444ace564812c0c59b48986b2588..aad78e0f3a441e426f17c22b69039ee79d5b5adf 100644 (file)
--- a/main.py
+++ b/main.py
@@ -6,7 +6,7 @@ if '--version' in argv:
     load_prc_file_data('', 'window-type none')
 from os.path import exists
 from traceback import print_exc
-from pmachines.app import PmachinesApp
+from game.app import PmachinesApp
 from p3d_appimage import AppImageBuilder
 
 if __name__ == '__main__' or exists('main.pyo'):
diff --git a/pmachines/__init__.py b/pmachines/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/pmachines/app.py b/pmachines/app.py
deleted file mode 100755 (executable)
index d125293..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-import argparse
-import simplepbr
-#import gltf
-from glob import glob
-from importlib import import_module
-from inspect import isclass
-from sys import platform, argv, exit
-from logging import info, debug
-from os.path import exists
-from os import makedirs
-from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
-    Texture, WindowProperties, LVector2i
-from panda3d.bullet import BulletWorld, BulletDebugNode
-from direct.showbase.ShowBase import ShowBase
-from direct.fsm.FSM import FSM
-from pmachines.music import MusicMgr
-from pmachines.items.background import Background
-from pmachines.menu import Menu
-from pmachines.scene import Scene
-from lib.dictfile import DctFile
-from lib.lib.p3d.p3d import LibP3d
-from lib.engine.lang import LangMgr
-
-
-class MainFsm(FSM):
-
-    def __init__(self, pmachines):
-        super().__init__('Main FSM')
-        self._pmachines = pmachines
-
-    def enterMenu(self):
-        self._pmachines.on_menu_enter()
-
-    def exitMenu(self):
-        self._pmachines.on_menu_exit()
-
-    def enterScene(self, cls):
-        self._pmachines.on_scene_enter(cls)
-
-    def exitScene(self):
-        self._pmachines.on_scene_exit()
-
-
-class PmachinesApp:
-
-    def __init__(self):
-        info('platform: %s' % platform)
-        info('exists main.py: %s' % exists('main.py'))
-        args = self._parse_args()
-        self._configure(args)
-        self.base = ShowBase()
-        self._prepare_window(args)
-        self.updating = args.update
-        self.version = args.version
-        if args.update:
-            return
-        self._music = MusicMgr(self._options['settings']['volume'])
-        self.lang_mgr = LangMgr(self._options['settings']['language'],
-                                'pmachines',
-                                'assets/locale/')
-        self._fsm = MainFsm(self)
-        if args.screenshot:
-            scene_classes = []
-            for _file in glob('pmachines/scenes/*.py'):
-                _fn = _file.replace('.py', '').replace('/', '.')
-                for member in import_module(_fn).__dict__.values():
-                    if isclass(member) and issubclass(member, Scene) and \
-                            member != Scene:
-                        scene_classes += [member]
-            cls = [cls for cls in scene_classes if cls.__name__ == args.screenshot][0]
-            scene = cls(BulletWorld(), None, True, False, lambda: None)
-            scene.screenshot()
-            scene.destroy()
-            exit()
-        elif self._options['development']['auto_start']:
-            mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
-            for member in import_module(mod_name).__dict__.values():
-                if isclass(member) and issubclass(member, Scene) and \
-                        member != Scene:
-                    cls = member
-            self._fsm.demand('Scene', cls)
-        else:
-            self._fsm.demand('Menu')
-
-    def on_menu_enter(self):
-        self._menu_bg = Background()
-        self._menu = Menu(
-            self._fsm, self.lang_mgr, self._options, self._music,
-            self._pipeline)
-
-    def on_home(self):
-        self._fsm.demand('Menu')
-
-    def on_menu_exit(self):
-        self._menu_bg.destroy()
-        self._menu.destroy()
-
-    def on_scene_enter(self, cls):
-        self._set_physics()
-        self._scene = cls(
-            self.world, self.on_home,
-            self._options['development']['auto_close_instructions'],
-            self._options['development']['debug_items'],
-            self.reload)
-
-    def on_scene_exit(self):
-        self._unset_physics()
-        self._scene.destroy()
-
-    def reload(self, cls):
-        self._fsm.demand('Scene', cls)
-
-    def _configure(self, args):
-        load_prc_file_data('', 'window-title pmachines')
-        load_prc_file_data('', 'framebuffer-srgb true')
-        load_prc_file_data('', 'sync-video true')
-        # load_prc_file_data('', 'threading-model Cull/Draw')
-        # it freezes when you go to the next scene
-        if args.screenshot:
-            load_prc_file_data('', 'window-type offscreen')
-            load_prc_file_data('', 'audio-library-name null')
-
-    def _parse_args(self):
-        parser = argparse.ArgumentParser()
-        parser.add_argument('--update', action='store_true')
-        parser.add_argument('--version', action='store_true')
-        parser.add_argument('--optfile')
-        parser.add_argument('--screenshot')
-        cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
-        args = parser.parse_args(cmd_line)
-        return args
-
-    def _prepare_window(self, args):
-        data_path = ''
-        if (platform.startswith('win') or platform.startswith('linux')) and (
-                not exists('main.py') or __file__.startswith('/app/bin/')):
-            # it is the deployed version for windows
-            data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
-            home = '/home/flavio'  # we must force this for wine
-            if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
-                data_path = home + '/.wine/drive_' + data_path[1:]
-            info('creating dirs: %s' % data_path)
-            makedirs(data_path, exist_ok=True)
-        optfile = args.optfile if args.optfile else 'options.ini'
-        info('data path: %s' % data_path)
-        info('option file: %s' % optfile)
-        info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
-        default_opt = {
-            'settings': {
-                'volume': 1,
-                'language': 'en',
-                'fullscreen': 1,
-                'resolution': '',
-                'antialiasing': 1,
-                'shadows': 1},
-            'development': {
-                'simplepbr': 1,
-                'verbose_log': 0,
-                'physics_debug': 0,
-                'auto_start': 0,
-                'auto_close_instructions': 0,
-                'show_buffers': 0,
-                'debug_items': 0,
-                'fps': 0}}
-        opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
-        opt_exists = exists(opt_path)
-        self._options = DctFile(
-            LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
-            default_opt)
-        if not opt_exists:
-            self._options.store()
-        res = self._options['settings']['resolution']
-        if res:
-            res = LVector2i(*[int(_res) for _res in res.split('x')])
-        else:
-            d_i = base.pipe.get_display_information()
-            def _res(idx):
-                return d_i.get_display_mode_width(idx), \
-                    d_i.get_display_mode_height(idx)
-            resolutions = [
-                _res(idx) for idx in range(d_i.get_total_display_modes())]
-            res = sorted(resolutions)[-1]
-        props = WindowProperties()
-        props.set_size(res)
-        props.set_fullscreen(self._options['settings']['fullscreen'])
-        props.set_icon_filename('assets/images/icon/pmachines.ico')
-        if not args.screenshot:
-            base.win.request_properties(props)
-        #gltf.patch_loader(base.loader)
-        if self._options['development']['simplepbr']:
-            self._pipeline = simplepbr.init(
-                use_normal_maps=True,
-                use_emission_maps=False,
-                use_occlusion_maps=True,
-                msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
-                enable_shadows=int(self._options['settings']['shadows']))
-            debug(f'msaa: {self._pipeline.msaa_samples}')
-            debug(f'shadows: {self._pipeline.enable_shadows}')
-        render.setAntialias(AntialiasAttrib.MAuto)
-        self.base.set_background_color(0, 0, 0, 1)
-        self.base.disable_mouse()
-        if self._options['development']['show_buffers']:
-            base.bufferViewer.toggleEnable()
-        if self._options['development']['fps']:
-            base.set_frame_rate_meter(True)
-        #self.base.accept('window-event', self._on_win_evt)
-        self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
-
-    def _set_physics(self):
-        if self._options['development']['physics_debug']:
-            debug_node = BulletDebugNode('Debug')
-            debug_node.show_wireframe(True)
-            debug_node.show_constraints(True)
-            debug_node.show_bounding_boxes(True)
-            debug_node.show_normals(True)
-            self._debug_np = render.attach_new_node(debug_node)
-            self._debug_np.show()
-        self.world = BulletWorld()
-        self.world.set_gravity((0, 0, -9.81))
-        if self._options['development']['physics_debug']:
-            self.world.set_debug_node(self._debug_np.node())
-        def update(task):
-            dt = globalClock.get_dt()
-            self.world.do_physics(dt, 10, 1/180)
-            return task.cont
-        self._phys_tsk = taskMgr.add(update, 'update')
-
-    def _unset_physics(self):
-        if self._options['development']['physics_debug']:
-            self._debug_np.remove_node()
-        self.world = None
-        taskMgr.remove(self._phys_tsk)
-
-    def _on_aspect_ratio_changed(self):
-        if self._fsm.state == 'Scene':
-            self._scene.on_aspect_ratio_changed()
diff --git a/pmachines/items/__init__.py b/pmachines/items/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/pmachines/items/background.py b/pmachines/items/background.py
deleted file mode 100644 (file)
index 8702543..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-from itertools import product
-from panda3d.core import NodePath
-from lib.lib.p3d.gfx import set_srgb
-
-
-class Background:
-
-    def __init__(self):
-        self._root = NodePath('background_root')
-        self._root.reparent_to(render)
-        ncols, nrows = 16, 8
-        start_size, end_size = 5, 2.5
-        offset = 5
-        for col, row in product(range(ncols), range(nrows)):
-            model = loader.load_model('assets/models/bam/background/background.bam')
-            model.set_scale(end_size / start_size)
-            model.reparent_to(self._root)
-            total_width, total_height = end_size * ncols, end_size * nrows
-            left, bottom = -total_width/2, -total_height/2
-            model.set_pos(left + end_size * col, offset, bottom + end_size * row)
-        self._root.clear_model_nodes()
-        self._root.flatten_strong()
-        set_srgb(self._root)
-
-    def destroy(self):
-        self._root.remove_node()
diff --git a/pmachines/items/basketball.py b/pmachines/items/basketball.py
deleted file mode 100644 (file)
index 1063379..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from panda3d.bullet import BulletSphereShape, BulletRigidBodyNode, BulletGhostNode
-from pmachines.items.item import Item
-
-
-class Basketball(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.92, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/basketball/basketball.bam', .4, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletSphereShape(1))
diff --git a/pmachines/items/box.py b/pmachines/items/box.py
deleted file mode 100644 (file)
index dd25992..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from pmachines.items.item import Item
-
-
-class Box(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.8, model_scale=1):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/box/box.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=model_scale)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((.5, .5, .5)))
-
-
-class HitStrategy:
-
-    def __init__(self, hit_by, node, world):
-        self._hit_by = hit_by
-        self._node = node
-        self._world = world
-
-    def win_condition(self):
-        for contact in self._world.contact_test(self._node).get_contacts():
-            other = contact.get_node1() if contact.get_node0() == self._node else contact.get_node0()
-            if other == self._hit_by.node:
-                return True
diff --git a/pmachines/items/domino.py b/pmachines/items/domino.py
deleted file mode 100644 (file)
index 333305d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from pmachines.items.item import Item, StillStrategy
-
-
-class Domino(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/domino/domino.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((.1, .25, .5)))
-
-
-class UpStrategy(StillStrategy):
-
-    def __init__(self, np, tgt_degrees):
-        super().__init__(np)
-        self._tgt_degrees = tgt_degrees
-        self._np = np
-
-    def win_condition(self):
-        return super().win_condition() and abs(self._np.get_r()) <= self._tgt_degrees
-
-
-class DownStrategy(StillStrategy):
-
-    def __init__(self, np, tgt_degrees):
-        super().__init__(np)
-        self._tgt_degrees = tgt_degrees
-        self._np = np
-
-    def win_condition(self):
-        return self._np.get_z() < -6 or super().win_condition() and abs(self._np.get_r()) >= self._tgt_degrees
diff --git a/pmachines/items/item.py b/pmachines/items/item.py
deleted file mode 100644 (file)
index fabd391..0000000
+++ /dev/null
@@ -1,339 +0,0 @@
-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)
diff --git a/pmachines/items/shelf.py b/pmachines/items/shelf.py
deleted file mode 100644 (file)
index 2710dd6..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-from panda3d.bullet import BulletBoxShape, BulletRigidBodyNode, BulletGhostNode
-from pmachines.items.item import Item
-
-
-class Shelf(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.6):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/shelf/shelf.bam', mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction)
-
-    def _set_shape(self, apply_scale=True):
-        self.node.add_shape(BulletBoxShape((1, .5, .05)))
diff --git a/pmachines/items/teetertooter.py b/pmachines/items/teetertooter.py
deleted file mode 100644 (file)
index 20adfb9..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-from panda3d.core import TransformState
-from panda3d.bullet import BulletCylinderShape, BulletRigidBodyNode, BulletGhostNode, YUp, ZUp
-from pmachines.items.item import Item
-
-
-class TeeterTooter(Item):
-
-    def __init__(self, world, plane_node, cb_inst, curr_bottom, repos, mass=1, pos=(0, 0, 0), r=0, count=0, restitution=.5, friction=.5):
-        super().__init__(world, plane_node, cb_inst, curr_bottom, repos, 'assets/models/bam/teeter_tooter/teeter_tooter.bam', exp_num_contacts=2, mass=mass, pos=pos, r=r, count=count, restitution=restitution, friction=friction, model_scale=.5)
-
-    def _set_shape(self, apply_scale=True):
-        scale = self._model_scale if apply_scale else 1
-        self.node.add_shape(
-            BulletCylinderShape(.1, 1.6, YUp),
-            TransformState.makePos((0, 0, scale * .36)))
-        self.node.add_shape(
-            BulletCylinderShape(.1, .7, ZUp),
-            TransformState.makePos((0, scale * .8, scale * -.1)))
-        self.node.add_shape(
-            BulletCylinderShape(.1, .7, ZUp),
-            TransformState.makePos((0, scale * -.8, scale * -.1)))
diff --git a/pmachines/menu.py b/pmachines/menu.py
deleted file mode 100644 (file)
index 0df49b2..0000000
+++ /dev/null
@@ -1,291 +0,0 @@
-from logging import info, debug
-from sys import platform, exit
-from os import environ, system
-from glob import glob
-from importlib import import_module
-from inspect import isclass
-from webbrowser import open_new_tab
-from panda3d.core import Texture, TextNode, WindowProperties, LVector2i, \
-    TextProperties, TextPropertiesManager
-from direct.gui.DirectGui import DirectButton, DirectCheckButton, \
-    DirectOptionMenu, DirectSlider, DirectCheckButton
-from direct.gui.DirectGuiGlobals import FLAT
-from direct.gui.OnscreenText import OnscreenText
-from lib.engine.gui.cursor import MouseCursor
-from pmachines.scene import Scene
-from panda3d.bullet import BulletWorld
-
-
-class Menu:
-
-    def __init__(self, fsm, lang_mgr, opt_file, music, pipeline):
-        self._fsm = fsm
-        self._lang_mgr = lang_mgr
-        self._opt_file = opt_file
-        self._music = music
-        self._pipeline = pipeline
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._font = base.loader.load_font('assets/fonts/Hanken-Book.ttf')
-        self._font.clear()
-        self._font.set_pixels_per_unit(60)
-        self._font.set_minfilter(Texture.FTLinearMipmapLinear)
-        self._font.set_outline((0, 0, 0, 1), .8, .2)
-        self._widgets = []
-        self._common = {
-            'scale': .12,
-            'text_font': self._font,
-            'text_fg': (.9, .9, .9, 1),
-            'relief': FLAT,
-            'frameColor': (.4, .4, .4, .14),
-            'rolloverSound': loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            'clickSound': loader.load_sfx('assets/audio/sfx/click.ogg')}
-        self._common_btn = {'frameSize': (-4.8, 4.8, -.6, 1.2)} | self._common
-        hlc = self._common_btn['frameColor']
-        hlc = (hlc[0] + .2, hlc[1] + .2, hlc[2] + .2, hlc[3] + .2)
-        self._common_opt = {
-            'item_frameColor': self._common_btn['frameColor'],
-            'popupMarker_frameColor': self._common_btn['frameColor'],
-            'item_relief': self._common_btn['relief'],
-            'item_text_font': self._common_btn['text_font'],
-            'item_text_fg': self._common_btn['text_fg'],
-            'textMayChange': 1,
-            'highlightColor': hlc,
-            'text_align': TextNode.A_center,
-        } | self._common_btn
-        f_s = self._common_opt['frameSize']
-        self._common_opt['frameSize'] = f_s[0], f_s[1] - .56, f_s[2], f_s[3]
-        self._common_slider = self._common | {
-            'range': (0, 1),
-            'thumb_frameColor': (.4, .4, .4, .4),
-            'thumb_scale': 1.6,
-            'scale': .4}
-        del self._common_slider['rolloverSound']
-        del self._common_slider['clickSound']
-        self._set_main()
-
-    def _set_main(self):
-        self._widgets = []
-        self._widgets += [DirectButton(
-            text=_('Play'), pos=(0, 1, .6), command=self.on_play,
-            **self._common_btn)]
-        self._widgets += [DirectButton(
-            text=_('Options'), pos=(0, 1, .2), command=self.on_options,
-            **self._common_btn)]
-        self._widgets += [DirectButton(
-            text=_('Credits'), pos=(0, 1, -.2), command=self.on_credits,
-            **self._common_btn)]
-        self._widgets += [DirectButton(
-            text=_('Exit'), pos=(0, 1, -.6), command=lambda: exit(),
-            **self._common_btn)]
-        self._rearrange_width()
-
-    def _set_options(self):
-        self._widgets = []
-        self._lang_funcs = [lambda: _('English'), lambda: _('Italian')]
-        items = [fnc() for fnc in self._lang_funcs]
-        inititem = {
-            'en': _('English'),
-            'it': _('Italian')
-        }[self._opt_file['settings']['language']]
-        btn = DirectOptionMenu(
-            text=_('Language'), items=items, initialitem=inititem,
-            pos=(0, 1, .8), command=self.on_language, **self._common_opt)
-        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
-        btn.popupMenu['relief'] = self._common_btn['relief']
-        self._widgets += [btn]
-        self._widgets += [OnscreenText(
-            _('Volume'), pos=(-.1, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_right)]
-        self._widgets += [DirectSlider(
-            pos=(.5, 1, .57),
-            value=self._opt_file['settings']['volume'],
-            command=self.on_volume,
-            **self._common_slider)]
-        self._slider = self._widgets[-1]
-        self._widgets += [DirectCheckButton(
-            text=_('Fullscreen'), pos=(0, 1, .3), command=self.on_fullscreen,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['fullscreen'],
-            **self._common_btn)]
-        res = self._opt_file['settings']['resolution']
-        d_i = base.pipe.get_display_information()
-        def _res(idx):
-            return d_i.get_display_mode_width(idx), \
-                d_i.get_display_mode_height(idx)
-        resolutions = [
-            _res(idx) for idx in range(d_i.get_total_display_modes())]
-        resolutions = list(set(resolutions))
-        resolutions = sorted(resolutions)
-        resolutions = [(str(_res[0]), str(_res[1])) for _res in resolutions]
-        resolutions = ['x'.join(_res) for _res in resolutions]
-        if not res:
-            res = resolutions[-1]
-        btn = DirectOptionMenu(
-            text=_('Resolution'), items=resolutions, initialitem=res,
-            pos=(0, 1, .05), command=self.on_resolution, **self._common_opt)
-        btn.popupMenu['frameColor'] = self._common_btn['frameColor']
-        btn.popupMenu['relief'] = self._common_btn['relief']
-        self._widgets += [btn]
-        self._widgets += [DirectCheckButton(
-            text=_('Antialiasing'), pos=(0, 1, -.2), command=self.on_aa,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['antialiasing'],
-            **self._common_btn)]
-        self._widgets += [DirectCheckButton(
-            text=_('Shadows'), pos=(0, 1, -.45), command=self.on_shadows,
-            indicator_frameColor=self._common_opt['highlightColor'],
-            indicator_relief=self._common_btn['relief'],
-            indicatorValue=self._opt_file['settings']['shadows'],
-            **self._common_btn)]
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-
-    def _set_credits(self):
-        self._widgets = []
-        tp_scale = TextProperties()
-        tp_scale.set_text_scale(.64)
-        TextPropertiesManager.getGlobalPtr().setProperties('scale', tp_scale)
-        self._widgets += [OnscreenText(
-            _('Code and gfx\n  \1scale\1Flavio Calva\2\n\n\nMusic\n  \1scale\1Stefan Grossmann\2'),
-            pos=(-.9, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_left)]
-        self._widgets += [DirectButton(
-            text=_('Website'), pos=(-.6, 1, .29), command=self.on_website,
-            **self._common_btn | {'scale': .08})]
-        self._widgets += [OnscreenText(
-            _('Special thanks to:\n  \1scale\1rdb\2\n  \1scale\1Luisa Tenuta\2\n  \1scale\1Damiana Ercolani\2'),
-            pos=(.1, .55), font=self._common['text_font'],
-            scale=self._common['scale'], fg=self._common['text_fg'],
-            align=TextNode.A_left)]
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-
-    def on_play(self):
-        scenes = []
-        for _file in glob('pmachines/scenes/*.py'):
-            _fn = _file.replace('.py', '').replace('/', '.')
-            for member in import_module(_fn).__dict__.values():
-                if isclass(member) and issubclass(member, Scene) and \
-                        member != Scene:
-                    scenes += [member]
-        scenes = sorted(scenes, key=lambda elm: elm.sorting)
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._widgets = []
-        cmn = self._common_btn.copy() | {
-            'frameSize': (-2.4, 2.4, -2.4, 2.4),
-            'frameColor': (1, 1, 1, .8),
-            'text_scale': .64}
-        left = - (dx := .8) * (min(4, len(scenes)) - 1) / 2
-        for i, cls in enumerate(scenes):
-            top = .1 if len(scenes) < 5 else .6
-            row = 0 if i < 4 else 1
-            self._widgets += [DirectButton(
-                text=cls.name(), pos=(left + dx * (i % 4), 1, top - dx * row),
-                command=self.start, extraArgs=[cls], text_wordwrap=6,
-                frameTexture='assets/images/scenes/%s.dds' % cls.__name__,
-                **cmn)]
-            for j in range(4):
-                tnode = self._widgets[-1].component('text%s' % j).textNode
-                height = - tnode.getLineHeight() / 2
-                height += (tnode.get_height() - tnode.get_line_height()) / 2
-                self._widgets[-1].component('text%s' % j).set_pos(0, 0, height)
-        self._widgets += [DirectButton(
-            text=_('Back'), pos=(0, 1, -.8), command=self.on_back,
-            **self._common_btn)]
-
-    def start(self, cls):
-        self._fsm.demand('Scene', cls)
-
-    def on_options(self):
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_options()
-
-    def on_credits(self):
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_credits()
-
-    def _rearrange_width(self):
-        max_width = 0
-        for wdg in self._widgets:
-            t_n = wdg.component('text0')
-            u_l = t_n.textNode.get_upper_left_3d()
-            l_r = t_n.textNode.get_lower_right_3d()
-            max_width = max(l_r[0] - u_l[0], max_width)
-        for wdg in self._widgets:
-            m_w = max_width / 2 + .8
-            wdg['frameSize'] = -m_w, m_w, wdg['frameSize'][2], wdg['frameSize'][3]
-
-    def on_language(self, arg):
-        lang_code = {
-            _('English'): 'en_EN',
-            _('Italian'): 'it_IT'}[arg]
-        self._lang_mgr.set_lang(lang_code)
-        self._opt_file['settings']['language'] = lang_code[:2]
-        self._opt_file.store()
-        self.on_options()
-
-    def on_volume(self):
-        self._opt_file['settings']['volume'] = self._slider['value']
-        self._music.set_volume(self._slider['value'])
-
-    def on_fullscreen(self, arg):
-        props = WindowProperties()
-        props.set_fullscreen(arg)
-        base.win.request_properties(props)
-        self._opt_file['settings']['fullscreen'] = int(arg)
-        self._opt_file.store()
-
-    def on_resolution(self, arg):
-        props = WindowProperties()
-        props.set_size(LVector2i(*[int(_res) for _res in arg.split('x')]))
-        base.win.request_properties(props)
-        self._opt_file['settings']['resolution'] = arg
-        self._opt_file.store()
-
-    def on_aa(self, arg):
-        self._pipeline.msaa_samples = 4 if arg else 1
-        debug(f'msaa: {self._pipeline.msaa_samples}')
-        self._opt_file['settings']['antialiasing'] = int(arg)
-        self._opt_file.store()
-
-    def on_shadows(self, arg):
-        self._pipeline.enable_shadows = int(arg)
-        debug(f'shadows: {self._pipeline.enable_shadows}')
-        self._opt_file['settings']['shadows'] = int(arg)
-        self._opt_file.store()
-
-    def on_website(self):
-        if platform.startswith('linux'):
-            environ['LD_LIBRARY_PATH'] = ''
-            system('xdg-open https://www.ya2.it')
-        else:
-            open_new_tab('https://www.ya2.it')
-
-    def on_back(self):
-        self._opt_file.store()
-        self.destroy()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_main()
-
-    def destroy(self):
-        [wdg.destroy() for wdg in self._widgets]
-        self._cursor.destroy()
diff --git a/pmachines/music.py b/pmachines/music.py
deleted file mode 100644 (file)
index 2dfb2cd..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-from glob import glob
-from random import choice
-from logging import info
-from panda3d.core import AudioSound
-
-
-class MusicMgr:
-
-    def __init__(self, volume):
-        self._start_music(glob('assets/audio/music/*.ogg'))
-        base.musicManager.setVolume(.8 * volume)
-        base.sfxManagerList[0].setVolume(volume)
-        taskMgr.add(self._on_frame, 'on frame music')
-
-    def _start_music(self, files):
-        self._music = loader.load_music(choice(files))
-        info('playing music ' + self._music.get_name())
-        self._music.play()
-
-    def set_volume(self, volume):
-        base.musicManager.setVolume(.8 * volume)
-        base.sfxManagerList[0].setVolume(volume)
-
-    def _on_frame(self, task):
-        if self._music.status() == AudioSound.READY:
-            files = glob('assets/audio/music/*.ogg')
-            files.remove('assets/audio/music/' + self._music.get_name())
-            self._start_music(files)
-        return task.cont
diff --git a/pmachines/scene.py b/pmachines/scene.py
deleted file mode 100644 (file)
index b2cc3d5..0000000
+++ /dev/null
@@ -1,581 +0,0 @@
-from os.path import exists
-from glob import glob
-from importlib import import_module
-from inspect import isclass
-from panda3d.core import AmbientLight, DirectionalLight, Point3, Texture, \
-    TextPropertiesManager, TextNode, Spotlight, PerspectiveLens, BitMask32
-from panda3d.bullet import BulletPlaneShape, BulletGhostNode
-from direct.gui.OnscreenImage import OnscreenImage
-from direct.gui.OnscreenText import OnscreenText
-from direct.gui.DirectGui import DirectButton, DirectFrame
-from direct.gui.DirectGuiGlobals import FLAT, DISABLED, NORMAL
-from direct.showbase.DirectObject import DirectObject
-from pmachines.items.background import Background
-from pmachines.sidepanel import SidePanel
-from lib.engine.gui.cursor import MouseCursor
-from lib.lib.p3d.gfx import P3dGfxMgr
-
-
-class Scene(DirectObject):
-
-    def __init__(self, world, exit_cb, auto_close_instr, dbg_items, reload_cb):
-        super().__init__()
-        self._world = world
-        self._exit_cb = exit_cb
-        self._dbg_items = dbg_items
-        self._reload_cb = reload_cb
-        self._set_camera()
-        self._cursor = MouseCursor(
-            'assets/images/buttons/arrowUpLeft.dds', (.04, 1, .04), (.5, .5, .5, 1),
-            (.01, .01))
-        self._set_gui()
-        self._set_lights()
-        self._set_input()
-        self._set_mouse_plane()
-        self.items = []
-        self.reset()
-        self._state = 'init'
-        self._paused = False
-        self._item_active = None
-        if auto_close_instr:
-            self.__store_state()
-            self.__restore_state()
-        else:
-            self._set_instructions()
-        self._bg = Background()
-        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')
-
-    @staticmethod
-    def name():
-        return ''
-
-    def _instr_txt(self):
-        return ''
-
-    def _set_items(self):
-        self.items = []
-
-    def screenshot(self, task=None):
-        tex = Texture('screenshot')
-        buffer = base.win.make_texture_buffer('screenshot', 512, 512, tex, True )
-        cam = base.make_camera(buffer)
-        cam.reparent_to(render)
-        cam.node().get_lens().set_fov(base.camLens.get_fov())
-        cam.set_pos(0, -20, 0)
-        cam.look_at(0, 0, 0)
-        import simplepbr
-        simplepbr.init(
-            window=buffer,
-            camera_node=cam,
-            use_normal_maps=True,
-            use_emission_maps=False,
-            use_occlusion_maps=True,
-            msaa_samples=4,
-            enable_shadows=True)
-        base.graphicsEngine.renderFrame()
-        base.graphicsEngine.renderFrame()
-        fname = self.__class__.__name__
-        if not exists('assets/images/scenes'):
-            makedirs('assets/images/scenes')
-        buffer.save_screenshot('assets/images/scenes/%s.png' % fname)
-        # img = DirectButton(
-        #     frameTexture=buffer.get_texture(), relief=FLAT,
-        #     frameSize=(-.2, .2, -.2, .2))
-        return buffer.get_texture()
-
-    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._set_items()
-        self._state = 'init'
-        self._commands = []
-        self._command_idx = 0
-        if hasattr(self, '_success_txt'):
-            self._success_txt.destroy()
-            del self._success_txt
-        self.__right_btn['state'] = NORMAL
-
-    def destroy(self):
-        self._unset_gui()
-        self._unset_lights()
-        self._unset_input()
-        self._unset_mouse_plane()
-        [itm.destroy() for itm in self.items]
-        self._bg.destroy()
-        self._side_panel.destroy()
-        self._cursor.destroy()
-        taskMgr.remove(self._scene_tsk)
-        if hasattr(self, '_success_txt'):
-            self._success_txt.destroy()
-
-    def _set_camera(self):
-        base.camera.set_pos(0, -20, 0)
-        base.camera.look_at(0, 0, 0)
-
-    def __load_img_btn(self, path, col):
-        img = OnscreenImage('assets/images/buttons/%s.dds' % path)
-        img.set_transparency(True)
-        img.set_color(col)
-        img.detach_node()
-        return img
-
-    def _set_gui(self):
-        def load_images_btn(path, col):
-            colors = {
-                'gray': [
-                    (.6, .6, .6, 1),  # ready
-                    (1, 1, 1, 1), # press
-                    (.8, .8, .8, 1), # rollover
-                    (.4, .4, .4, .4)],
-                'green': [
-                    (.1, .68, .1, 1),
-                    (.1, 1, .1, 1),
-                    (.1, .84, .1, 1),
-                    (.4, .1, .1, .4)]}[col]
-            return [self.__load_img_btn(path, col) for col in colors]
-        abl, abr = base.a2dBottomLeft, base.a2dBottomRight
-        btn_info = [
-            ('home', self.on_home, NORMAL, abl, 'gray'),
-            ('information', self._set_instructions, NORMAL, abl, 'gray'),
-            ('right', self.on_play, NORMAL, abr, 'green'),
-            ('next', self.on_next, DISABLED, abr, 'gray'),
-            ('previous', self.on_prev, DISABLED, abr, 'gray'),
-            ('rewind', self.reset, NORMAL, abr, 'gray')]
-        num_l = num_r = 0
-        btns = []
-        for binfo in btn_info:
-            imgs = load_images_btn(binfo[0], binfo[4])
-            if binfo[3] == base.a2dBottomLeft:
-                sign, num = 1, num_l
-                num_l += 1
-            else:
-                sign, num = -1, num_r
-                num_r += 1
-            fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
-            btn = DirectButton(
-                image=imgs, scale=.05, pos=(sign * (.06 + .11 * num), 1, .06),
-                parent=binfo[3], command=binfo[1], state=binfo[2], relief=FLAT,
-                frameColor=fcols[0] if binfo[2] == NORMAL else fcols[1],
-                rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-                clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-            btn.set_transparency(True)
-            btns += [btn]
-        self.__home_btn, self.__info_btn, self.__right_btn, self.__next_btn, \
-            self.__prev_btn, self.__rewind_btn = btns
-        if self._dbg_items:
-            self._info_txt = OnscreenText(
-                '', parent=base.a2dTopRight, scale=0.04,
-                pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
-
-    def _unset_gui(self):
-        btns = [
-            self.__home_btn, self.__info_btn, self.__right_btn,
-            self.__next_btn, self.__prev_btn, self.__rewind_btn]
-        [btn.destroy() for btn in btns]
-        if self._dbg_items:
-            self._info_txt.destroy()
-
-    def _set_spotlight(self, name, pos, look_at, color, shadows=False):
-        light = Spotlight(name)
-        if shadows:
-            light.setLens(PerspectiveLens())
-        light_np = render.attach_new_node(light)
-        light_np.set_pos(pos)
-        light_np.look_at(look_at)
-        light.set_color(color)
-        render.set_light(light_np)
-        return light_np
-
-    def _set_lights(self):
-        alight = AmbientLight('alight')  # for ao
-        alight.set_color((.15, .15, .15, 1))
-        self._alnp = render.attach_new_node(alight)
-        render.set_light(self._alnp)
-        self._key_light = self._set_spotlight(
-            'key light', (-5, -80, 5), (0, 0, 0), (2.8, 2.8, 2.8, 1))
-        self._shadow_light = self._set_spotlight(
-            'key light', (-5, -80, 5), (0, 0, 0), (.58, .58, .58, 1), True)
-        self._shadow_light.node().set_shadow_caster(True, 2048, 2048)
-        self._shadow_light.node().get_lens().set_film_size(2048, 2048)
-        self._shadow_light.node().get_lens().set_near_far(1, 256)
-        self._shadow_light.node().set_camera_mask(BitMask32(0x01))
-
-    def _unset_lights(self):
-        for light in [self._alnp, self._key_light, self._shadow_light]:
-            render.clear_light(light)
-            light.remove_node()
-
-    def _set_input(self):
-        self.accept('mouse1', self.on_click_l)
-        self.accept('mouse1-up', self.on_release)
-        self.accept('mouse3', self.on_click_r)
-        self.accept('mouse3-up', self.on_release)
-
-    def _unset_input(self):
-        for evt in ['mouse1', 'mouse1-up', 'mouse3', 'mouse3-up']:
-            self.ignore(evt)
-
-    def _set_mouse_plane(self):
-        shape = BulletPlaneShape((0, -1, 0), 0)
-        #self._mouse_plane_node = BulletRigidBodyNode('mouse plane')
-        self._mouse_plane_node = BulletGhostNode('mouse plane')
-        self._mouse_plane_node.addShape(shape)
-        #np = render.attachNewNode(self._mouse_plane_node)
-        #self._world.attachRigidBody(self._mouse_plane_node)
-        self._world.attach_ghost(self._mouse_plane_node)
-
-    def _unset_mouse_plane(self):
-        self._world.remove_ghost(self._mouse_plane_node)
-
-    def _get_hits(self):
-        if not base.mouseWatcherNode.has_mouse(): return []
-        p_from, p_to = P3dGfxMgr.world_from_to(base.mouseWatcherNode.get_mouse())
-        return self._world.ray_test_all(p_from, p_to).get_hits()
-
-    def _update_info(self, item):
-        txt = ''
-        if item:
-            txt = '%.3f %.3f\n%.3f°' % (
-                item._np.get_x(), item._np.get_z(), item._np.get_r())
-        self._info_txt['text'] = txt
-
-    def _on_click(self, method):
-        if self._paused:
-            return
-        for hit in self._get_hits():
-            if hit.get_node() == self._mouse_plane_node:
-                pos = hit.get_hit_pos()
-        for hit in self._get_hits():
-            for item in [i for i in self.items if hit.get_node() == i.node and i.interactable]:
-                if not self._item_active:
-                    self._item_active = item
-                getattr(item, method)(pos)
-                img = 'move' if method == 'on_click_l' else 'rotate'
-                if not (img == 'rotate' and not item._instantiated):
-                    self._cursor.set_image('assets/images/buttons/%s.dds' % img)
-
-    def on_click_l(self):
-        self._on_click('on_click_l')
-
-    def on_click_r(self):
-        self._on_click('on_click_r')
-
-    def on_release(self):
-        if self._item_active and not self._item_active._first_command:
-            self._commands = self._commands[:self._command_idx]
-            self._commands += [self._item_active]
-            self._command_idx += 1
-            self.__prev_btn['state'] = NORMAL
-            fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
-            self.__prev_btn['frameColor'] = fcols[0]
-            if self._item_active._command_idx == len(self._item_active._commands) - 1:
-                self.__next_btn['state'] = DISABLED
-                self.__next_btn['frameColor'] = fcols[1]
-        self._item_active = None
-        [item.on_release() for item in self.items]
-        self._cursor.set_image('assets/images/buttons/arrowUpLeft.dds')
-
-    def repos(self):
-        for item in self.items:
-            item.repos_done = False
-        self.items = sorted(self.items, key=lambda itm: itm.__class__.__name__)
-        [item.on_aspect_ratio_changed() for item in self.items]
-        self._side_panel.update(self.items)
-        max_x = -float('inf')
-        for item in self.items:
-            if not item._instantiated:
-                max_x = max(item._np.get_x(), max_x)
-        for item in self.items:
-            if not item._instantiated:
-                item.repos_x(max_x)
-
-    def on_aspect_ratio_changed(self):
-        self.repos()
-
-    def _win_condition(self):
-        pass
-
-    def _fail_condition(self):
-        return all(itm.fail_condition() for itm in self.items) and not self._paused and self._state == 'playing'
-
-    def on_frame(self, task):
-        hits = self._get_hits()
-        pos = None
-        for hit in self._get_hits():
-            if hit.get_node() == self._mouse_plane_node:
-                pos = hit.get_hit_pos()
-        hit_nodes = [hit.get_node() for hit in hits]
-        if self._item_active:
-            items_hit = [self._item_active]
-        else:
-            items_hit = [itm for itm in self.items if itm.node in hit_nodes]
-        items_no_hit = [itm for itm in self.items if itm not in items_hit]
-        [itm.on_mouse_on() for itm in items_hit]
-        [itm.on_mouse_off() for itm in items_no_hit]
-        if pos and self._item_active:
-            self._item_active.on_mouse_move(pos)
-        if self._dbg_items:
-            self._update_info(items_hit[0] if items_hit else None)
-        if self._win_condition():
-            self._set_win()
-        elif self._state == 'playing' and self._fail_condition():
-            self._set_fail()
-        if any(itm._overlapping for itm in self.items):
-            self._cursor.cursor_img.img.set_color(.9, .1, .1, 1)
-        else:
-            self._cursor.cursor_img.img.set_color(.9, .9, .9, 1)
-        return task.cont
-
-    def cb_inst(self, item):
-        self.items += [item]
-
-    def on_play(self):
-        self._state = 'playing'
-        self.__prev_btn['state'] = DISABLED
-        self.__next_btn['state'] = DISABLED
-        self.__right_btn['state'] = DISABLED
-        [itm.play() for itm in self.items]
-
-    def on_next(self):
-        self._commands[self._command_idx].redo()
-        self._command_idx += 1
-        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
-        self.__prev_btn['state'] = NORMAL
-        self.__prev_btn['frameColor'] = fcols[0]
-        more_commands = self._command_idx < len(self._commands)
-        self.__next_btn['state'] = NORMAL if more_commands else DISABLED
-        self.__next_btn['frameColor'] = fcols[0] if more_commands else fcols[1]
-
-    def on_prev(self):
-        self._command_idx -= 1
-        self._commands[self._command_idx].undo()
-        fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
-        self.__next_btn['state'] = NORMAL
-        self.__next_btn['frameColor'] = fcols[0]
-        self.__prev_btn['state'] = NORMAL if self._command_idx else DISABLED
-        self.__prev_btn['frameColor'] = fcols[0] if self._command_idx else fcols[1]
-
-    def on_home(self):
-        self._exit_cb()
-
-    def _set_instructions(self):
-        self._paused = True
-        self.__store_state()
-        mgr = TextPropertiesManager.get_global_ptr()
-        for name in ['mouse_l', 'mouse_r']:
-            graphic = OnscreenImage('assets/images/buttons/%s.dds' % name)
-            graphic.set_scale(.5)
-            graphic.get_texture().set_minfilter(Texture.FTLinearMipmapLinear)
-            graphic.get_texture().set_anisotropic_degree(2)
-            mgr.set_graphic(name, graphic)
-            graphic.set_z(-.2)
-            graphic.set_transparency(True)
-            graphic.detach_node()
-        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
-                          frameSize=(-.6, .6, -.3, .3))
-        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(
-            self._instr_txt(), parent=frm, font=font, scale=0.06,
-            fg=(.9, .9, .9, 1), align=TextNode.A_left)
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
-        btn_scale = .05
-        mar = .06  # margin
-        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
-        z += (btn_scale + 2 * mar) / 2
-        self._txt['pos'] = -w / 2, z
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
-        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
-        frm['frameSize'] = fsz
-        colors = [
-            (.6, .6, .6, 1),  # ready
-            (1, 1, 1, 1), # press
-            (.8, .8, .8, 1), # rollover
-            (.4, .4, .4, .4)]
-        imgs = [self.__load_img_btn('exitRight', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(l_r[0] - btn_scale, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self.__on_close_instructions, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn.set_transparency(True)
-
-    def _set_win(self):
-        loader.load_sfx('assets/audio/sfx/success.ogg').play()
-        self._paused = True
-        self.__store_state()
-        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
-                          frameSize=(-.6, .6, -.3, .3))
-        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(
-            _('You win!'),
-            parent=frm,
-            font=font, scale=0.2,
-            fg=(.9, .9, .9, 1))
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
-        btn_scale = .05
-        mar = .06  # margin
-        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
-        z += (btn_scale + 2 * mar) / 2
-        self._txt['pos'] = 0, z
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
-        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
-        frm['frameSize'] = fsz
-        colors = [
-            (.6, .6, .6, 1),  # ready
-            (1, 1, 1, 1), # press
-            (.8, .8, .8, 1), # rollover
-            (.4, .4, .4, .4)]
-        imgs = [self.__load_img_btn('home', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_end_home, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn.set_transparency(True)
-        imgs = [self.__load_img_btn('rewind', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(0, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_restart, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn.set_transparency(True)
-        scenes = []
-        for _file in glob('pmachines/scenes/*.py'):
-            _fn = _file.replace('.py', '').replace('/', '.')
-            for member in import_module(_fn).__dict__.values():
-                if isclass(member) and issubclass(member, Scene) and \
-                        member != Scene:
-                    scenes += [member]
-        scenes = sorted(scenes, key=lambda elm: elm.sorting)
-        enabled = scenes.index(self.__class__) < len(scenes) - 1
-        if enabled:
-            next_scene = scenes[scenes.index(self.__class__) + 1]
-        else:
-            next_scene = None
-        imgs = [self.__load_img_btn('right', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_next_scene,
-            extraArgs=[frm, next_scene], relief=FLAT,
-            frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn['state'] = NORMAL if enabled else DISABLED
-        btn.set_transparency(True)
-
-    def _set_fail(self):
-        loader.load_sfx('assets/audio/sfx/success.ogg').play()
-        self._paused = True
-        self.__store_state()
-        frm = DirectFrame(frameColor=(.4, .4, .4, .06),
-                          frameSize=(-.6, .6, -.3, .3))
-        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(
-            _('You have failed!'),
-            parent=frm,
-            font=font, scale=0.2,
-            fg=(.9, .9, .9, 1))
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        w, h = l_r[0] - u_l[0], u_l[2] - l_r[2]
-        btn_scale = .05
-        mar = .06  # margin
-        z = h / 2 - font.get_line_height() * self._txt['scale'][1]
-        z += (btn_scale + 2 * mar) / 2
-        self._txt['pos'] = 0, z
-        u_l = self._txt.textNode.get_upper_left_3d()
-        l_r = self._txt.textNode.get_lower_right_3d()
-        c_l_r = l_r[0], l_r[1], l_r[2] - 2 * mar - btn_scale
-        fsz = u_l[0] - mar, l_r[0] + mar, c_l_r[2] - mar, u_l[2] + mar
-        frm['frameSize'] = fsz
-        colors = [
-            (.6, .6, .6, 1),  # ready
-            (1, 1, 1, 1), # press
-            (.8, .8, .8, 1), # rollover
-            (.4, .4, .4, .4)]
-        imgs = [self.__load_img_btn('home', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(-2.8 * btn_scale, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_end_home, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn.set_transparency(True)
-        imgs = [self.__load_img_btn('rewind', col) for col in colors]
-        btn = DirectButton(
-            image=imgs, scale=btn_scale,
-            pos=(0, 1, l_r[2] - mar - btn_scale),
-            parent=frm, command=self._on_restart, extraArgs=[frm],
-            relief=FLAT, frameColor=(.6, .6, .6, .08),
-            rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
-            clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
-        btn.set_transparency(True)
-
-    def _on_restart(self, frm):
-        self.__on_close_instructions(frm)
-        self.reset()
-
-    def _on_end_home(self, frm):
-        self.__on_close_instructions(frm)
-        self.on_home()
-
-    def _on_next_scene(self, frm, scene):
-        self.__on_close_instructions(frm)
-        self._reload_cb(scene)
-
-    def __store_state(self):
-        btns = [
-            self.__home_btn, self.__info_btn, self.__right_btn,
-            self.__next_btn, self.__prev_btn, self.__rewind_btn]
-        self.__btn_state = [btn['state'] for btn in btns]
-        for btn in btns:
-            btn['state'] = DISABLED
-        [itm.store_state() for itm in self.items]
-
-    def __restore_state(self):
-        btns = [
-            self.__home_btn, self.__info_btn, self.__right_btn,
-            self.__next_btn, self.__prev_btn, self.__rewind_btn]
-        for btn, state in zip(btns, self.__btn_state):
-            btn['state'] = state
-        [itm.restore_state() for itm in self.items]
-        self._paused = False
-
-    def __on_close_instructions(self, frm):
-        frm.remove_node()
-        self.__restore_state()
diff --git a/pmachines/scenes/__init__.py b/pmachines/scenes/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/pmachines/scenes/scene_basketball.py b/pmachines/scenes/scene_basketball.py
deleted file mode 100644 (file)
index 43cef26..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-from pmachines.scene import Scene
-from pmachines.items.box import Box
-from pmachines.items.shelf import Shelf
-from pmachines.items.domino import Domino, UpStrategy, DownStrategy
-from pmachines.items.basketball import Basketball
-from pmachines.items.teetertooter import TeeterTooter
-
-
-class SceneBasketBall(Scene):
-
-    sorting = 3
-
-    @staticmethod
-    def name():
-        return _('Basket ball')
-
-    def _set_items(self):
-        self.items = []
-        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-4.45, 0, -3.18), r=27, restitution=1)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-5.45, 0, -3.18), restitution=1)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 30))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
-        #self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
-        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
-        #self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: you must hit every domino piece\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_box.py b/pmachines/scenes/scene_box.py
deleted file mode 100644 (file)
index 2e8ac27..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-from pmachines.scene import Scene
-from pmachines.items.box import Box, HitStrategy
-from pmachines.items.shelf import Shelf
-from pmachines.items.domino import Domino
-from pmachines.items.basketball import Basketball
-from pmachines.items.teetertooter import TeeterTooter
-
-
-class SceneBox(Scene):
-
-    sorting = 1
-
-    @staticmethod
-    def name():
-        return _('Box')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.46, 0, -3.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.43, 0, -3.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.29, 0, .26), r=28.45)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.15, 0, -1.49), r=28.45)]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.55, 0, 1.23), friction=.4)]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.38, 0, -3.35))]
-        self.items[-1].set_strategy(HitStrategy(self.items[-2], self.items[-1].node, self.items[-1]._world))
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04), tgt_degrees=60)]
-        #self.items += [TargetDomino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04), tgt_degrees=88)]
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: the left box must hit the right box\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_domino.py b/pmachines/scenes/scene_domino.py
deleted file mode 100644 (file)
index b06b4b5..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-from pmachines.scene import Scene
-from pmachines.items.box import Box
-from pmachines.items.shelf import Shelf
-from pmachines.items.domino import Domino, DownStrategy
-from pmachines.items.basketball import Basketball
-from pmachines.items.teetertooter import TeeterTooter
-
-
-class SceneDomino(Scene):
-
-    sorting = 0
-
-    @staticmethod
-    def name():
-        return _('Domino')
-
-    def _set_items(self):
-        self.items = []
-        #self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.2, 0, -.6))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.2, 0, -.6))]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=2)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-1.14, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.49, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.94, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.55, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 60))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.09, 0, -.04))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 88))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must fall\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_domino_box.py b/pmachines/scenes/scene_domino_box.py
deleted file mode 100644 (file)
index 2d162eb..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-from pmachines.scene import Scene
-from pmachines.items.box import Box
-from pmachines.items.shelf import Shelf
-from pmachines.items.domino import Domino, UpStrategy, DownStrategy
-from pmachines.items.basketball import Basketball
-from pmachines.items.teetertooter import TeeterTooter
-
-
-class SceneDominoBox(Scene):
-
-    sorting = 2
-
-    @staticmethod
-    def name():
-        return _('Domino and box')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        #self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, .21))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, -.94), r=37)]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.61, 0, .73), r=37)]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.06, 0, .78))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(0.91, 0, .78))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, .78))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, .78))]
-        self.items[-1].set_strategy(UpStrategy(self.items[-1]._np, 30))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: only the last piece of each row must be up\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_domino_box_basketball.py b/pmachines/scenes/scene_domino_box_basketball.py
deleted file mode 100644 (file)
index 8776691..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-from pmachines.scene import Scene
-from pmachines.items.box import Box
-from pmachines.items.shelf import Shelf
-from pmachines.items.domino import Domino, UpStrategy, DownStrategy
-from pmachines.items.basketball import Basketball
-from pmachines.items.teetertooter import TeeterTooter
-
-
-class SceneDominoBoxBasketball(Scene):
-
-    sorting = 4
-
-    @staticmethod
-    def name():
-        return _('Domino, box and basket ball')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1, mass=5)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=1)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(-.3, 1, 2.5))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        #self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=9)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.68, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.35, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.08, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.78, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(4.53, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must be hit\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_teeter_domino_box_basketball.py b/pmachines/scenes/scene_teeter_domino_box_basketball.py
deleted file mode 100644 (file)
index 08dff9e..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-from pmachines.scene import Scene
-from pmachines.items.box import Box
-from pmachines.items.shelf import Shelf
-from pmachines.items.domino import Domino, UpStrategy, DownStrategy
-from pmachines.items.basketball import Basketball
-from pmachines.items.teetertooter import TeeterTooter
-
-
-class SceneTeeterDominoBoxBasketball(Scene):
-
-    sorting = 6
-
-    @staticmethod
-    def name():
-        return _('Teeter tooter, domino, box and basket ball')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=2, friction=1)]
-        self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(.98, 1, 1.02))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.45))]
-        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-6.24, 0, -1.20))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=24.60, friction=1, pos=(-6.15, 0, -.93))]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.3, friction=1, model_scale=.5, pos=(-5.38, 0, -.93), r=24.60)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(5.37, 0, -.78))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(7.48, 0, -.78))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.74, 0, -1.95))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(6.88, 0, -1.95))]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(4.83, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(5.67, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, pos=(6.59, 0, -1.39))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.53, 0, -1.95), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.63, 0, -1.95), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-3.65, 0, 1.05), r=28, friction=0)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.27, 0, 1.72), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.88, 0, 1.72), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-1.67, 0, .55), restitution=.95)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(.52, 0, .55), restitution=.95)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-1.73, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.97, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.5, pos=(-.1, 0, 1.11))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: every domino piece must be hit\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/scenes/scene_teeter_tooter.py b/pmachines/scenes/scene_teeter_tooter.py
deleted file mode 100644 (file)
index 2ed320b..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-from pmachines.scene import Scene
-from pmachines.items.box import Box
-from pmachines.items.shelf import Shelf
-from pmachines.items.domino import Domino, UpStrategy, DownStrategy
-from pmachines.items.basketball import Basketball
-from pmachines.items.teetertooter import TeeterTooter
-
-
-class SceneTeeterTooter(Scene):
-
-    sorting = 5
-
-    @staticmethod
-    def name():
-        return _('Teeter tooter')
-
-    def _set_items(self):
-        self.items = []
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=3, count=1, friction=1)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=5, count=2)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.76, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.56, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(2.27, 0, -.28))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(4.38, 0, -.28))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(1.67, 0, -1.45))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(3.78, 0, -1.45))]
-        self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-2.74, 0, -1.20))]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=1, r=-25.30, friction=1, pos=(-2.78, 0, -.93))]
-        self.items += [Box(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=.2, friction=1, model_scale=.5, pos=(-3.61, 0, -.99), r=-25.30)]
-        self.items += [Shelf(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, mass=0, pos=(-.25, 0, -.57), r=52)]
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(1.73, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(2.57, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        self.items += [Domino(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, pos=(3.50, 0, -.89))]
-        self.items[-1].set_strategy(DownStrategy(self.items[-1]._np, 35))
-        #self.items += [Basketball(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-        #self.items += [TeeterTooter(self._world, self._mouse_plane_node, self.cb_inst, self.current_bottom, self.repos, count=3)]
-
-    def _instr_txt(self):
-        txt = _('Scene: ') + self.name() + '\n\n'
-        txt += _('Goal: you must hit every domino piece\n\n')
-        txt += _('keep \5mouse_l\5 pressed to drag an item\n\n'
-                'keep \5mouse_r\5 pressed to rotate an item')
-        return txt
-
-    def _win_condition(self):
-        return all(itm.strategy.win_condition() for itm in self.items) and not self._paused
diff --git a/pmachines/sidepanel.py b/pmachines/sidepanel.py
deleted file mode 100644 (file)
index 1523e21..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-from textwrap import dedent
-from panda3d.core import GeomVertexData, GeomVertexFormat, Geom, \
-    GeomVertexWriter, GeomTriangles, GeomNode, Shader, Point3, Plane, Vec3
-from lib.lib.p3d.gfx import P3dGfxMgr
-
-
-class SidePanel:
-
-    def __init__(self, world, plane_node, top_l, bottom_r, y, items):
-        self._world = world
-        self._plane_node = plane_node
-        self._set((-1, 1), y)
-        self.update(items)
-
-    def update(self, items):
-        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()
-        y = 0
-        corner = -20, 20
-        for item in items:
-            if not item._instantiated:
-                bounds = item._np.get_tight_bounds()
-                if bounds[1][1] > y:
-                    y = bounds[1][1]
-                icorner = item.get_corner()
-                icorner = P3dGfxMgr.screen_coord(icorner)
-                if icorner[0] > corner[0]:
-                    corner = icorner[0], corner[1]
-                if icorner[1] < corner[1]:
-                    corner = corner[0], icorner[1]
-        self._set((pos[0], pos[2]), y)
-        bounds = self._np.get_tight_bounds()
-        corner3d = bounds[1][0], bounds[1][1], bounds[0][2]
-        corner2d = P3dGfxMgr.screen_coord(corner3d)
-        plane = Plane(Vec3(0, 1, 0), y)
-        pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
-        ar, margin = base.get_aspect_ratio(), .04
-        x = corner[0] / ar if ar >= 1 else corner[0]
-        z = corner[1] * ar if ar < 1 else corner[1]
-        x += margin / ar if ar >= 1 else margin
-        z -= margin * ar if ar < 1 else margin
-        base.camLens.extrude((x, 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))
-        corner_pos3d, near_pt, far_pt = Point3(), Point3(), Point3()
-        base.camLens.extrude((-1, 1), near_pt, far_pt)
-        plane.intersects_line(
-            corner_pos3d, render.get_relative_point(base.camera, near_pt),
-            render.get_relative_point(base.camera, far_pt))
-        self._np.set_pos((pos3d + corner_pos3d) / 2)
-        scale = Vec3(pos3d[0] - corner_pos3d[0], 1, corner_pos3d[2] - pos3d[2])
-        self._np.set_scale(scale)
-
-    def _set(self, pos, y):
-        if hasattr(self, '_np'):
-            self._np.remove_node()
-        vdata = GeomVertexData('quad', GeomVertexFormat.get_v3(), Geom.UHStatic)
-        vdata.setNumRows(2)
-        vertex = GeomVertexWriter(vdata, 'vertex')
-        vertex.add_data3(.5, 0, -.5)
-        vertex.add_data3(.5, 0, .5)
-        vertex.add_data3(-.5, 0, .5)
-        vertex.add_data3(-.5, 0, -.5)
-        prim = GeomTriangles(Geom.UHStatic)
-        prim.add_vertices(0, 1, 2)
-        prim.add_vertices(0, 2, 3)
-        prim.close_primitive()
-        geom = Geom(vdata)
-        geom.add_primitive(prim)
-        node = GeomNode('gnode')
-        node.add_geom(geom)
-        self._np = render.attach_new_node(node)
-        self._np.setTransparency(True)
-        self._np.set_pos(pos[0], y, pos[1])
-        vert = '''\
-            #version 150
-            uniform mat4 p3d_ModelViewProjectionMatrix;
-            in vec4 p3d_Vertex;
-            void main() {
-                gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex; }'''
-        frag = '''\
-            #version 150
-            out vec4 p3d_FragColor;
-            void main() {
-                p3d_FragColor = vec4(.04, .04, .04, .08); }'''
-        self._np.set_shader(Shader.make(Shader.SL_GLSL, dedent(vert), dedent(frag)))
-        # mat = Material()
-        # mat.set_base_color((1, 1, 1, 1))
-        # mat.set_emission((1, 1, 1, 1))
-        # mat.set_metallic(.5)
-        # mat.set_roughness(.5)
-        # np.set_material(mat)
-        # texture_sz = 64
-        # base_color_pnm = PNMImage(texture_sz, texture_sz)
-        # base_color_pnm.fill(.1, .1, .1)
-        # base_color_pnm.add_alpha()
-        # base_color_pnm.alpha_fill(.04)
-        # base_color_tex = Texture('base color')
-        # base_color_tex.load(base_color_pnm)
-        # ts = TextureStage('base color')
-        # ts.set_mode(TextureStage.M_modulate)
-        # np.set_texture(ts, base_color_tex)
-        # emission_pnm = PNMImage(texture_sz, texture_sz)
-        # emission_pnm.fill(0, 0, 0)
-        # emission_tex = Texture('emission')
-        # emission_tex.load(emission_pnm)
-        # ts = TextureStage('emission')
-        # ts.set_mode(TextureStage.M_emission)
-        # np.set_texture(ts, emission_tex)
-        # metal_rough_pnm = PNMImage(texture_sz, texture_sz)
-        # ambient_occlusion = 1
-        # roughness = .5
-        # metallicity = .5
-        # metal_rough_pnm.fill(ambient_occlusion, roughness, metallicity)
-        # metal_rough_tex = Texture('ao metal roughness')
-        # metal_rough_tex.load(metal_rough_pnm)
-        # ts = TextureStage('ao metal roughness')
-        # ts.set_mode(TextureStage.M_selector)
-        # np.set_texture(ts, metal_rough_tex)
-        # normal_pnm = PNMImage(texture_sz, texture_sz)
-        # normal_pnm.fill(.5, .5, .1)
-        # normal_tex = Texture('normals')
-        # normal_tex.load(normal_pnm)
-        # ts = TextureStage('normals')
-        # ts.set_mode(TextureStage.M_normal)
-        # np.set_texture(ts, normal_tex)
-
-    def destroy(self):
-        self._np.remove_node()