Exemplo n.º 1
0
class Mouse():
    def __init__(self):
        self.enabled = False
        self.locked = False
        self.position = Vec3(0, 0, 0)
        self.delta = Vec3(0, 0, 0)
        self.prev_x = 0
        self.prev_y = 0
        self.start_x = 0
        self.start_y = 0
        self.velocity = Vec3(0, 0, 0)
        self.prev_click_time = time.time()
        self.double_click_distance = .5

        self.hovered_entity = None  # returns the closest hovered entity with a collider.
        self.left = False
        self.right = False
        self.middle = False
        self.delta_drag = Vec3(0, 0, 0)

        self.update_step = 1
        self.traverse_target = scene
        self._i = 0
        self._mouse_watcher = None
        self._picker = CollisionTraverser()  # Make a traverser
        self._pq = CollisionHandlerQueue()  # Make a handler
        self._pickerNode = CollisionNode('mouseRay')
        self._pickerNP = camera.attach_new_node(self._pickerNode)
        self._pickerRay = CollisionRay()  # Make our ray
        self._pickerNode.addSolid(self._pickerRay)
        self._picker.addCollider(self._pickerNP, self._pq)
        self._pickerNode.set_into_collide_mask(0)

        self.raycast = True
        self.collision = None
        self.collisions = list()
        self.enabled = True

    @property
    def x(self):
        if not self._mouse_watcher.has_mouse():
            return 0
        return self._mouse_watcher.getMouseX(
        ) / 2 * window.aspect_ratio  # same space as ui stuff

    @x.setter
    def x(self, value):
        self.position = (value, self.y)

    @property
    def y(self):
        if not self._mouse_watcher.has_mouse():
            return 0

        return self._mouse_watcher.getMouseY() / 2

    @y.setter
    def y(self, value):
        self.position = (self.x, value)

    @property
    def position(self):
        return Vec3(self.x, self.y, 0)

    @position.setter
    def position(self, value):
        base.win.move_pointer(
            0,
            round(value[0] + (window.size[0] / 2) +
                  (value[0] / 2 * window.size[0]) *
                  1.124),  # no idea why I have * with 1.124
            round(value[1] + (window.size[1] / 2) -
                  (value[1] * window.size[1])),
        )

    def __setattr__(self, name, value):

        if name == 'visible':
            window.set_cursor_hidden(not value)
            application.base.win.requestProperties(window)

        if name == 'locked':
            try:
                object.__setattr__(self, name, value)
                window.set_cursor_hidden(value)
                if value:
                    window.set_mouse_mode(window.M_relative)
                else:
                    window.set_mouse_mode(window.M_absolute)

                application.base.win.requestProperties(window)
            except:
                pass

        try:
            super().__setattr__(name, value)
            # return
        except:
            pass

    def input(self, key):
        if not self.enabled:
            return

        if key.endswith('mouse down'):
            self.start_x = self.x
            self.start_y = self.y

        elif key.endswith('mouse up'):
            self.delta_drag = Vec3(self.x - self.start_x,
                                   self.y - self.start_y, 0)

        if key == 'left mouse down':
            self.left = True
            if self.hovered_entity:
                if hasattr(self.hovered_entity, 'on_click'):
                    self.hovered_entity.on_click()
                for s in self.hovered_entity.scripts:
                    if hasattr(s, 'on_click'):
                        s.on_click()
            # double click
            if time.time(
            ) - self.prev_click_time <= self.double_click_distance:
                base.input('double click')

                if self.hovered_entity:
                    if hasattr(self.hovered_entity, 'on_double_click'):
                        self.hovered_entity.on_double_click()
                    for s in self.hovered_entity.scripts:
                        if hasattr(s, 'on_double_click'):
                            s.on_double_click()

            self.prev_click_time = time.time()

        if key == 'left mouse up':
            self.left = False
        if key == 'right mouse down':
            self.right = True
        if key == 'right mouse up':
            self.right = False
        if key == 'middle mouse down':
            self.middle = True
        if key == 'middle mouse up':
            self.middle = False

    def update(self):
        if not self.enabled or not self._mouse_watcher.has_mouse():
            self.velocity = Vec3(0, 0, 0)
            return

        self.moving = self.x + self.y != self.prev_x + self.prev_y

        if self.moving:
            if self.locked:
                self.velocity = self.position
                self.position = (0, 0)
            else:
                self.velocity = Vec3(self.x - self.prev_x,
                                     (self.y - self.prev_y) /
                                     window.aspect_ratio, 0)
        else:
            self.velocity = Vec3(0, 0, 0)

        if self.left or self.right or self.middle:
            self.delta = Vec3(self.x - self.start_x, self.y - self.start_y, 0)

        self.prev_x = self.x
        self.prev_y = self.y

        self._i += 1
        if self._i < self.update_step:
            return
        # collide with ui
        self._pickerNP.reparent_to(scene.ui_camera)
        self._pickerRay.set_from_lens(camera._ui_lens_node,
                                      self.x * 2 / window.aspect_ratio,
                                      self.y * 2)
        self._picker.traverse(camera.ui)
        if self._pq.get_num_entries() > 0:
            # print('collided with ui', self._pq.getNumEntries())
            self.find_collision()
            return

        # collide with world
        self._pickerNP.reparent_to(camera)
        self._pickerRay.set_from_lens(scene.camera.lens_node,
                                      self.x * 2 / window.aspect_ratio,
                                      self.y * 2)
        try:
            self._picker.traverse(self.traverse_target)
        except:
            # print('error: mouse._picker could not traverse', self.traverse_target)
            return

        if self._pq.get_num_entries() > 0:
            self.find_collision()
        else:
            # print('mouse miss', base.render)
            # unhover all if it didn't hit anything
            for entity in scene.entities:
                if hasattr(entity, 'hovered') and entity.hovered:
                    entity.hovered = False
                    self.hovered_entity = None
                    if hasattr(entity, 'on_mouse_exit'):
                        entity.on_mouse_exit()
                    for s in entity.scripts:
                        if hasattr(s, 'on_mouse_exit'):
                            s.on_mouse_exit()

    @property
    def normal(self):
        if not self.collision:
            return None
        return self.collision.normal

    @property
    def world_normal(self):
        if not self.collision:
            return None
        return self.collision.world_normal

    @property
    def point(self):  # returns the point hit in local space
        if self.collision:
            return self.collision.point
        return None

    @property
    def world_point(self):
        if self.collision:
            return self.collision.world_point
        return None

    def find_collision(self):
        self.collisions = list()
        self.collision = None
        if not self.raycast or self._pq.get_num_entries() == 0:
            self.unhover_everything_not_hit()
            return False

        self._pq.sortEntries()

        for entry in self._pq.getEntries():
            for entity in scene.entities:
                if entry.getIntoNodePath(
                ).parent == entity and entity.collision:
                    if entity.collision:
                        hit = HitInfo(
                            hit=entry.collided(),
                            entity=entity,
                            distance=distance(entry.getSurfacePoint(scene),
                                              camera.getPos()),
                            point=entry.getSurfacePoint(entity),
                            world_point=entry.getSurfacePoint(scene),
                            normal=entry.getSurfaceNormal(entity),
                            world_normal=entry.getSurfaceNormal(scene),
                        )
                        self.collisions.append(hit)
                        break

        if self.collisions:
            self.collision = self.collisions[0]
            self.hovered_entity = self.collision.entity
            if not self.hovered_entity.hovered:
                self.hovered_entity.hovered = True
                if hasattr(self.hovered_entity, 'on_mouse_enter'):
                    self.hovered_entity.on_mouse_enter()
                for s in self.hovered_entity.scripts:
                    if hasattr(s, 'on_mouse_enter'):
                        s.on_mouse_enter()

        self.unhover_everything_not_hit()

    def unhover_everything_not_hit(self):
        for e in scene.entities:
            if e == self.hovered_entity:
                continue

            if e.hovered:
                e.hovered = False
                if hasattr(e, 'on_mouse_exit'):
                    e.on_mouse_exit()
                for s in e.scripts:
                    if hasattr(s, 'on_mouse_exit'):
                        s.on_mouse_exit()
Exemplo n.º 2
0
class Cursor(DirectObject):
    """Cursor object for the UI."""

    parent: NodePath
    mouse_np: NodePath
    actor: Actor
    last_position: Point
    moved: bool
    pointed_at: Optional[NodePath]

    def __init__(self, parent: NodePath):
        super().__init__()
        self.parent = parent

        self.mouse_np = p3d.camera.attach_new_node(PandaNode('mouse'))
        self.mouse_np.set_y(p3d.lens.get_near())

        picker_node = CollisionNode('mouse_ray')
        picker_np = p3d.camera.attach_new_node(picker_node)
        self._picker_ray = CollisionRay()
        picker_node.add_solid(self._picker_ray)
        self._collision_handler = CollisionHandlerQueue()
        self._traverser = CollisionTraverser('mouse_traverser')
        self._traverser.add_collider(picker_np, self._collision_handler)

        self.actor = Actor(
            resource_filename('tsim', 'data/models/cursor'),
            {'spin': resource_filename('tsim', 'data/models/cursor-spin')})
        self.actor.loop('spin')
        self.actor.reparent_to(parent)
        self.actor.set_pos(0.0, 0.0, 0.0)
        self.actor.set_shader_off()

        self._position = Point(0.0, 0.0)
        self.last_position = self._position
        self.moved = False
        self.pointed_at = None

        self._tool: Optional[Tool] = None
        self._register_events()

    @property
    def position(self) -> Point:
        """Get the cursor position."""
        return self._position

    @position.setter
    def position(self, value: Union[Point, Iterable]):
        if not isinstance(value, Point):
            value = Point(*islice(value, 2))
        self.actor.set_x(value.x)
        self.actor.set_y(value.y)
        self._position = value

    @property
    def tool(self) -> Optional[Tool]:
        """Get current tool."""
        return self._tool

    @tool.setter
    def tool(self, value: Tool):
        if self._tool is not None:
            self._tool.cleanup()
        self.ignore_all()
        self._register_events()
        self._tool = value
        if value is not None:
            for key in INPUT.keys_for('tool_1'):
                self.accept(key, self._tool.on_button1_press)
                self.accept(f'{key}-up', self._tool.on_button1_release)
            for key in INPUT.keys_for('tool_2'):
                self.accept(key, self._tool.on_button2_press)
                self.accept(f'{key}-up', self._tool.on_button2_release)
            for key in INPUT.keys_for('tool_3'):
                self.accept(key, self._tool.on_button3_press)
                self.accept(f'{key}-up', self._tool.on_button3_release)
            self.accept('cursor_move', self._tool.on_cursor_move)

    def update(self):
        """Update callback."""
        self.actor.set_scale(p3d.camera.get_z()**0.6 / 10)
        self.moved = False

        if p3d.mouse_watcher.has_mouse():
            mouse_x, mouse_y = p3d.mouse_watcher.get_mouse()
            self._picker_ray.set_from_lens(p3d.cam_node, mouse_x, mouse_y)
            self._traverser.traverse(p3d.render)

            if self._collision_handler.get_num_entries():
                self._collision_handler.sort_entries()
                node_path = (
                    self._collision_handler.get_entry(0).get_into_node_path())
                self.position = node_path.get_pos(p3d.render)
                self.actor.set_z(2.0)
                self.pointed_at = node_path
            else:
                self.pointed_at = None
                film = p3d.lens.get_film_size() * 0.5
                self.mouse_np.set_x(mouse_x * film.x)
                self.mouse_np.set_y(p3d.lens.get_focal_length())
                self.mouse_np.set_z(mouse_y * film.y)
                self.last_position = self._position
                mouse_pos = self.mouse_np.get_pos(self.parent)
                cam_pos = p3d.camera.get_pos(self.parent)
                mouse_vec = mouse_pos - cam_pos
                if mouse_vec.z < 0.0:
                    scale = -mouse_pos.z / mouse_vec.z
                    self.actor.set_pos(mouse_pos + mouse_vec * scale)
                    self.position = self.actor.get_pos()
                    if self._position != self.last_position:
                        self.moved = True
                        p3d.messenger.send('cursor_move')

        if self._tool is not None:
            self._tool.on_update()

    def _on_simulation_step(self, dt: Duration):
        if self._tool is not None:
            self._tool.on_simulation_step(dt)

    def _register_events(self):
        def set_tool(tool: Type[Tool]):
            self.tool = tool(self)
            log.info('[%s] Changing tool to %s', __name__, tool.__name__)

        for tool in TOOLS:
            try:
                self.accept(tool.KEY, partial(set_tool, tool))
            except AttributeError:
                log.warning('[%s] No KEY set for tool %s', __name__,
                            tool.__name__)
        self.accept('simulation_step', self._on_simulation_step)
Exemplo n.º 3
0
class Mouse():
    def __init__(self):
        self.enabled = False
        self.locked = False
        self.position = Vec3(0, 0, 0)
        self.delta = Vec3(0, 0, 0)
        self.prev_x = 0
        self.prev_y = 0
        self.velocity = Vec3(0, 0, 0)
        self.prev_click_time = time.time()
        self.double_click_distance = .5

        self.hovered_entity = None
        self.left = False
        self.right = False
        self.middle = False
        self.delta_drag = Vec3(0, 0, 0)

        self.i = 0
        self.update_rate = 10
        self._mouse_watcher = None
        self._picker = CollisionTraverser()  # Make a traverser
        self._pq = CollisionHandlerQueue()  # Make a handler
        self._pickerNode = CollisionNode('mouseRay')
        self._pickerNP = camera.attach_new_node(self._pickerNode)
        self._pickerRay = CollisionRay()  # Make our ray
        self._pickerNode.addSolid(self._pickerRay)
        self._picker.addCollider(self._pickerNP, self._pq)
        self.raycast = True
        self.collision = None
        self.enabled = True

    @property
    def x(self):
        if not self._mouse_watcher.has_mouse():
            return 0
        return self._mouse_watcher.getMouseX(
        ) / 2 * window.aspect_ratio  # same space as ui stuff

    @property
    def y(self):
        if not self._mouse_watcher.has_mouse():
            return 0
        return self._mouse_watcher.getMouseY() / 2

    def __setattr__(self, name, value):

        if name == 'visible':
            window.set_cursor_hidden(not value)
            application.base.win.requestProperties(window)

        if name == 'locked':
            try:
                object.__setattr__(self, name, value)
                window.set_cursor_hidden(value)
                application.base.win.requestProperties(window)
            except:
                pass

        try:
            super().__setattr__(name, value)
            # return
        except:
            pass

    def input(self, key):
        if not self.enabled:
            return

        if key.endswith('mouse down'):
            self.start_x = self.x
            self.start_y = self.y

        elif key.endswith('mouse up'):
            self.delta_drag = Vec3(self.x - self.start_x,
                                   self.y - self.start_y, 0)

        if key == 'left mouse down':
            self.left = True
            if self.hovered_entity:
                if hasattr(self.hovered_entity, 'on_click'):
                    self.hovered_entity.on_click()
                for s in self.hovered_entity.scripts:
                    if hasattr(s, 'on_click'):
                        s.on_click()
            # double click
            if time.time(
            ) - self.prev_click_time <= self.double_click_distance:
                base.input('double click')

                if self.hovered_entity:
                    if hasattr(self.hovered_entity, 'on_double_click'):
                        self.hovered_entity.on_double_click()
                    for s in self.hovered_entity.scripts:
                        if hasattr(s, 'on_double_click'):
                            s.on_double_click()

            self.prev_click_time = time.time()

        if key == 'left mouse up':
            self.left = False
        if key == 'right mouse down':
            self.right = True
        if key == 'right mouse up':
            self.right = False
        if key == 'middle mouse down':
            self.middle = True
        if key == 'middle mouse up':
            self.middle = False

    def update(self):
        if not self.enabled or not self._mouse_watcher.has_mouse():
            self.velocity = Vec3(0, 0, 0)
            return

        self.position = Vec3(self.x, self.y, 0)
        self.moving = self.x + self.y != self.prev_x + self.prev_y

        if self.moving:
            if self.locked:
                self.velocity = self.position
                application.base.win.move_pointer(0, int(window.size[0] / 2),
                                                  int(window.size[1] / 2))
            else:
                self.velocity = Vec3(self.x - self.prev_x,
                                     (self.y - self.prev_y) /
                                     window.aspect_ratio, 0)
        else:
            self.velocity = Vec3(0, 0, 0)

        if self.left or self.right or self.middle:
            self.delta = Vec3(self.x - self.start_x, self.y - self.start_y, 0)

        self.prev_x = self.x
        self.prev_y = self.y

        self.i += 1
        if self.i < self.update_rate:
            return
        # collide with ui
        self._pickerNP.reparent_to(scene.ui_camera)
        self._pickerRay.set_from_lens(camera._ui_lens_node,
                                      self.x * 2 / window.aspect_ratio,
                                      self.y * 2)
        self._picker.traverse(camera.ui)
        if self._pq.get_num_entries() > 0:
            # print('collided with ui', self._pq.getNumEntries())
            self.find_collision()
            return

        # collide with world
        self._pickerNP.reparent_to(camera)
        self._pickerRay.set_from_lens(scene.camera.lens_node,
                                      self.x * 2 / window.aspect_ratio,
                                      self.y * 2)
        self._picker.traverse(base.render)
        if self._pq.get_num_entries() > 0:
            # print('collided with world', self._pq.getNumEntries())
            self.find_collision()
            return
        # else:
        #     print('mouse miss', base.render)

        # unhover all if it didn't hit anything
        for entity in scene.entities:
            if hasattr(entity, 'hovered') and entity.hovered:
                entity.hovered = False
                self.hovered_entity = None
                if hasattr(entity, 'on_mouse_exit'):
                    entity.on_mouse_exit()
                for s in entity.scripts:
                    if hasattr(s, 'on_mouse_exit'):
                        s.on_mouse_exit()

    @property
    def normal(self):
        if not self.collision:
            return None
        if not self.collision.has_surface_normal():
            print('no surface normal')
            return None
        n = self.collision.get_surface_normal(
            self.collision.get_into_node_path().parent)
        return (n[0], n[2], n[1])

    @property
    def world_normal(self):
        if not self.collision:
            return None
        if not self.collision.has_surface_normal():
            print('no surface normal')
            return None
        n = self.collision.get_surface_normal(render)
        return (n[0], n[2], n[1])

    @property
    def point(self):
        if self.hovered_entity:
            p = self.collision.getSurfacePoint(self.hovered_entity)
            return Point3(p[0], p[2], p[1])
        else:
            return None

    @property
    def world_point(self):
        if self.hovered_entity:
            p = self.collision.getSurfacePoint(render)
            return Point3(p[0], p[2], p[1])
        else:
            return None

    def find_collision(self):
        if not self.raycast:
            return
        self._pq.sortEntries()
        if len(self._pq.get_entries()) == 0:
            self.collision = None
            return

        self.collisions = list()
        for entry in self._pq.getEntries():
            # print(entry.getIntoNodePath().parent)
            for entity in scene.entities:
                if entry.getIntoNodePath().parent == entity:
                    if entity.collision:
                        self.collisions.append(
                            Hit(
                                hit=entry.collided(),
                                entity=entity,
                                distance=0,
                                point=entry.getSurfacePoint(entity),
                                world_point=entry.getSurfacePoint(scene),
                                normal=entry.getSurfaceNormal(entity),
                                world_normal=entry.getSurfaceNormal(scene),
                            ))
                        break

        self.collision = self._pq.getEntry(0)
        nP = self.collision.getIntoNodePath().parent

        for entity in scene.entities:
            if not hasattr(entity, 'collision'
                           ) or not entity.collision or not entity.collider:
                continue
            # if hit entity is not hovered, call on_mouse_enter()
            if entity == nP:
                if not entity.hovered:
                    entity.hovered = True
                    self.hovered_entity = entity
                    # print(entity.name)
                    if hasattr(entity, 'on_mouse_enter'):
                        entity.on_mouse_enter()
                    for s in entity.scripts:
                        if hasattr(s, 'on_mouse_enter'):
                            s.on_mouse_enter()
            # unhover the rest
            else:
                if entity.hovered:
                    entity.hovered = False
                    if hasattr(entity, 'on_mouse_exit'):
                        entity.on_mouse_exit()
                    for s in entity.scripts:
                        if hasattr(s, 'on_mouse_exit'):
                            s.on_mouse_exit()
Exemplo n.º 4
0
class MouseOverOnEntity(System):
    entity_filters = {
        'mouseoverable': [Proxy('model'), MouseOverable],
        'mouseoverable_geometry': [Proxy('geometry'), MouseOverableGeometry],
        'camera': [Camera, Input, MouseOveringCamera],
    }
    proxies = {
        'model': ProxyType(Model, 'node'),
        'geometry': ProxyType(Geometry, 'node'),
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.traverser = CollisionTraverser()
        self.queue = CollisionHandlerQueue()

        self.picker_ray = CollisionRay()
        self.picker_node = CollisionNode('mouse ray')
        self.picker_node.add_solid(self.picker_ray)
        self.picker_node.set_from_collide_mask(MOUSEOVER_MASK)
        self.picker_node.set_into_collide_mask(0x0)
        self.picker_node_path = NodePath(self.picker_node)

        self.traverser.add_collider(self.picker_node_path, self.queue)

    def enter_filter_mouseoverable(self, entity):
        model_proxy = self.proxies['model']
        model_node = model_proxy.field(entity)
        mouseoverable = entity[MouseOverable]

        into_node = CollisionNode('wecs_mouseoverable')
        into_node.add_solid(mouseoverable.solid)
        into_node.set_from_collide_mask(0x0)
        into_node.set_into_collide_mask(mouseoverable.mask)
        into_node_path = model_node.attach_new_node(into_node)
        into_node_path.set_python_tag('wecs_mouseoverable', entity._uid)

    def exit_filter_mouseoverable(self, entity):
        # FIXME: Undo all the other stuff that accumulated!
        entity[MouseOverable].solid.detach_node()

    def enter_filter_mouseoverable_geometry(self, entity):
        into_node = self.proxies['geometry'].field(entity)

        old_mask = into_node.get_collide_mask()
        new_mask = old_mask | entity[MouseOverableGeometry].mask
        into_node.set_collide_mask(new_mask)
        into_node.find('**/+GeomNode').set_python_tag('wecs_mouseoverable',
                                                      entity._uid)

    def update(self, entities_by_filter):
        for entity in entities_by_filter['camera']:
            mouse_overing = entity[MouseOveringCamera]
            camera = entity[Camera]
            input = entity[Input]

            # Reset overed entity to None
            mouse_overing.entity = None
            mouse_overing.collision_entry = None

            requested = 'mouse_over' in input.contexts
            has_mouse = base.mouseWatcherNode.has_mouse()
            if requested and has_mouse:
                # Attach and align testing ray, and run collisions
                self.picker_node_path.reparent_to(camera.camera)
                mpos = base.mouseWatcherNode.get_mouse()
                self.picker_ray.set_from_lens(
                    base.camNode,
                    mpos.getX(),
                    mpos.getY(),
                )
                self.traverser.traverse(camera.camera.get_top())

                # Remember reference to mouseovered entity, if any
                if self.queue.get_num_entries() > 0:
                    self.queue.sort_entries()
                    entry = self.queue.get_entry(0)
                    picked_node = entry.get_into_node_path()
                    picked_uid = picked_node.get_python_tag(
                        'wecs_mouseoverable')
                    mouse_overing.entity = picked_uid
                    mouse_overing.collision_entry = entry
Exemplo n.º 5
0
class MapPicker():
    __name: Final[str]
    __base: Final[ShowBase]
    __data: Final[NDArray[(Any, Any, Any), np.uint8]]

    # collision data
    __ctrav: Final[CollisionTraverser]
    __cqueue: Final[CollisionHandlerQueue]
    __cn: Final[CollisionNode]
    __cnp: Final[NodePath]

    # picker data
    __pn: Final[CollisionNode]
    __pnp: Final[NodePath]
    __pray: Final[CollisionRay]

    # constants
    COLLIDE_MASK: Final[BitMask32] = BitMask32.bit(1)

    def __init__(self, services: Services, base: ShowBase, map_data: MapData, name: Optional[str] = None):
        self.__services = services
        self.__services.ev_manager.register_listener(self)
        self.__base = base
        self.__name = name if name is not None else (map_data.name + "_picker")
        self.__map = map_data
        self.__data = map_data.data

        # collision traverser & queue
        self.__ctrav = CollisionTraverser(self.name + '_ctrav')
        self.__cqueue = CollisionHandlerQueue()

        # collision boxes
        self.__cn = CollisionNode(self.name + '_cn')
        self.__cn.set_collide_mask(MapPicker.COLLIDE_MASK)
        self.__cnp = self.__map.root.attach_new_node(self.__cn)
        self.__ctrav.add_collider(self.__cnp, self.__cqueue)
        self.__points = []

        z_offset = 1 if self.__map.dim == 3 else self.__map.depth
        for idx in np.ndindex(self.__data.shape):
            if bool(self.__data[idx] & MapData.TRAVERSABLE_MASK):
                p = Point(*idx)
                self.__points.append(p)
                idx = self.__cn.add_solid(CollisionBox(idx, Point3(p.x+1, p.y+1, p.z-z_offset)))
                assert idx == (len(self.__points) - 1)

        # mouse picker
        self.__pn = CollisionNode(self.name + '_pray')
        self.__pnp = self.__base.cam.attach_new_node(self.__pn)
        self.__pn.set_from_collide_mask(MapPicker.COLLIDE_MASK)

        self.__pray = CollisionRay()
        self.__pn.add_solid(self.__pray)
        self.__ctrav.add_collider(self.__pnp, self.__cqueue)

        # debug -> shows collision ray / impact point
        # self.__ctrav.show_collisions(self.__map.root)

    @property
    def name(self) -> str:
        return self.__name

    @property
    def pos(self):
        # check if we have access to the mouse
        if not self.__base.mouseWatcherNode.hasMouse():
            return None

        # get the mouse position
        mpos = self.__base.mouseWatcherNode.get_mouse()

        # set the position of the ray based on the mouse position
        self.__pray.set_from_lens(self.__base.camNode, mpos.getX(), mpos.getY())

        # find collisions
        self.__ctrav.traverse(self.__map.root)

        # if we have hit something sort the hits so that the closest is first
        if self.__cqueue.get_num_entries() == 0:
            return None
        self.__cqueue.sort_entries()

        # compute & return logical cube position
        x, y, z = self.__cqueue.get_entry(0).getSurfacePoint(self.__map.root)
        x, y, z = [max(math.floor(x), 0), max(math.floor(y), 0), max(math.ceil(z), 0)]
        if x == len(self.__data):
            x -= 1
        if y == len(self.__data[x]):
            y -= 1
        if z == len(self.__data[x][y]):
            z -= 1

        return Point(x, y, z)

    def notify(self, event: Event) -> None:
        if isinstance(event, MapUpdateEvent):
            z_offset = 1 if self.__map.dim == 3 else self.__map.depth
            for p in event.updated_cells:
                if p.n_dim == 2:
                    p = Point(*p, 0)

                if bool(self.__data[p.values] & MapData.TRAVERSABLE_MASK):
                    self.__points.append(p)
                    idx = self.__cn.add_solid(CollisionBox(p.values, Point3(p.x+1, p.y+1, p.z-z_offset)))
                    assert idx == (len(self.__points) - 1)
                else:
                    try:
                        i = self.__points.index(p)
                    except ValueError:
                        continue
                    self.__cn.remove_solid(i)
                    self.__points.pop(i)

    def destroy(self) -> None:
        self.__cqueue.clearEntries()
        self.__ctrav.clear_colliders()
        self.__cnp.remove_node()
        self.__pnp.remove_node()