Ejemplo n.º 1
0
    def init_dragging(self):
        """ save original position """

        r0_obj = math_utils.p3d_to_np(self.pickablepoint.getPos())

        self.position_before_dragging = Vec3(*r0_obj)

        DragDropEventManager.init_dragging(self)
Ejemplo n.º 2
0
    def init_dragging(self):
        """ """
        self.current_line = primitives.Stroke2d()
        coords_2d = np.array([self.getCoords2dForStroke()])
        coords_3d = self.convertPanel2dTo3dCoords(coords_2d)

        self.current_line.append_point((coords_3d[0][0], coords_3d[0][2]))
        self.current_line.reparentTo(self.panel_graphics)

        DragDropEventManager.init_dragging(self)
Ejemplo n.º 3
0
    def init_dragging(self):
        """ save original position """

        print("######## init drag drawing")

        # init stroke graphics
        self.current_line = primitives.SegmentedLinePrimitive(
            color=get_color("blue"), thickness=2)

        coords_2d = np.array([self.getCoords2dForStroke()])
        coords_3d = self.convertPanel2dTo3dCoords(coords_2d)

        self.current_line.extendCoords(coords_3d)
        self.current_line.reparentTo(self.panel_graphics)

        DragDropEventManager.init_dragging(self)
Ejemplo n.º 4
0
class OrbiterOrtho(CameraGear):
    """ """
    # when flipping over, the eye vector and z-vector are multiplied by -1.
    # but still, to prevent graphics glitches in the situation where
    # the lookat vector and view vector are perfectly aligned ("theta = 0"),
    # theta_epsilon provides a small but visually unnoticeable offset
    # theta_epsilon = 1.0e-10
    theta_epsilon = 1.0e-5
    phi_0 = 0.
    theta_0 = np.pi / 3.
    r0 = 1.
    orbit_center_0 = np.array([0., 0., 0.])

    def __init__(self, camera, r_init=2., enable_visual_aids=True):
        """ """
        CameraGear.__init__(self)

        base.disableMouse()

        self._orbit_center = None
        self.set_orbit_center(OrbiterOrtho.orbit_center_0, not_just_init=False)

        self.before_aspect_ratio_changed_at_init = True  # ?

        self.radius_init = r_init
        self.radius = r_init
        self.phi = OrbiterOrtho.phi_0
        self.theta = OrbiterOrtho.theta_0

        # self._previous_zoom_step_plane_coords = None
        # self.set_previous_zoom_step_plane_coords(0., 0.)

        # camera stuff
        self.camera = camera

        # # init the camera pos
        x, y, z = self.get_cam_coords(correct_for_camera_setting=True,
                                      fixed_phi=self.phi,
                                      fixed_theta=self.theta,
                                      fixed_r=self.radius)

        self.camera.setPos(
            x, y, z
        )  # TODO: is this really necessary in addition to the setViewMatrix ?

        # --- set the lens
        self.lens = OrthographicLens()
        self.lens.setNearFar(-500., 500.)
        self.camera.node().setLens(self.lens)

        # --- initial setting of the position
        self.update_camera_state(  # recalculate_film_size=True
        )

        self.set_view_matrix_after_updated_camera_pos()

        # --- event handling to reorient the camera
        from panda3d.core import ModifierButtons
        base.mouseWatcherNode.setModifierButtons(ModifierButtons())

        from direct.showbase.ShowBase import DirectObject

        # changing phi
        base.accept('wheel_down', self.handle_wheel_down)

        base.accept('wheel_up', self.handle_wheel_up)

        # changing theta
        base.accept('control-wheel_down', self.handle_control_wheel_down)

        base.accept('control-wheel_up', self.handle_control_wheel_up)

        # 'changing r' (although that's not what directly changes the 'zoom' in an ortho lens)
        base.accept('shift-wheel_up', self.handle_zoom_plus)

        base.accept('shift-wheel_down', self.handle_zoom_minus)

        # polling for zoom is better than events
        self.plus_button = KeyboardButton.ascii_key('+')
        self.minus_button = KeyboardButton.ascii_key('-')
        # taskMgr.add(self.poll_zoom_plus_minus, 'Zoom')

        # blender-like usage of 1, 3, 7 keys to align views with axes
        # base.accept('1', self.set_view_to_xz_plane)
        base.accept('3', self.set_view_to_yz_plane)
        base.accept('7', self.set_view_to_xy_plane)

        base.accept('1', self.set_view_to_xz_plane_and_reset_zoom)

        # base.accept('wheel_up', self.handle_wheel_up)

        # --- fix a point light to the side of the camera
        from panda3d.core import PointLight
        self.plight = PointLight('plight')
        self.pl_nodepath = engine.tq_graphics_basics.tq_render.attachNewNode_p3d(
            self.plight)
        self.set_pointlight_pos_spherical_coords()
        engine.tq_graphics_basics.tq_render.setLight(self.pl_nodepath)

        # -- set faint ambient white lighting
        from panda3d.core import AmbientLight
        self.alight = AmbientLight('alight')
        self.alnp = engine.tq_graphics_basics.tq_render.attachNewNode_p3d(
            self.alight)
        self.alight.setColor(Vec4(0.25, 0.25, 0.25, 1))
        engine.tq_graphics_basics.tq_render.setLight(self.alnp)

        self.visual_aids = OrbiterVisualAids(self)
        self.visual_aids.attach_to_render()

        if enable_visual_aids == True:
            self.visual_aids.on()
        else:
            self.visual_aids.off()

        # TODO: append a drag drop event manager here
        base.accept(
            'shift-mouse1',
            self.handle_shift_mouse1  # , extraArgs=[ob]
        )

        # p3d throws an aspectRatiochanged event after calling MyApp.run()
        # This variable controls which aspect ratio to take (initial aspect ratio (hard-coded or in configuration file)
        # or asking for it from the window manager)

        self.set_film_size_from_window_dimensions(
            called_from_orbiter_init=True)

        base.accept("aspectRatioChanged", self.run_window_resize_hooks)
        self.add_window_resize_hook(self.set_film_size_from_window_dimensions)
        self.add_window_resize_hook(self.scale_aspect2d_from_window_dimensions)

    def handle_shift_mouse1(self):
        """ """
        print("handle_shift_mouse1")
        self.ddem = DragDropEventManager()

        # ---- calculate (solely camera and object needed and the recorded mouse position before dragging) the self.p_xy_at_init_drag
        # between -1 and 1 in both x and y
        mouse_position_before_dragging = base.mouseWatcherNode.getMouse()
        p_xy_offset = conventions.getFilmCoordsFromMouseCoords(
            -mouse_position_before_dragging[0],
            -mouse_position_before_dragging[1],
            p_x_0=0.,
            p_y_0=0.)

        self.ddem.add_on_state_change_function(
            OrbiterOrtho.set_new_panning_orbit_center_from_dragged_mouse,
            args=(self, p_xy_offset, self.get_orbit_center()))
        self.ddem.init_dragging()

    @staticmethod
    def set_new_panning_orbit_center_from_dragged_mouse(
            camera_gear, p_xy_offset, original_orbit_center):
        """ There is a new mouse position, now
        Args:
            camera_gear: camera gear with camera member variable
            p_xy_offset: the origin point (lies in the camera plane) to which the change point (calculated from the mouse displacement) is being added to """

        v_cam_forward = math_utils.p3d_to_np(
            engine.tq_graphics_basics.tq_render.getRelativeVector(
                camera_gear.camera,
                camera_gear.camera.node().getLens().getViewVector()))
        v_cam_forward = v_cam_forward / np.linalg.norm(v_cam_forward)
        # print("--- > View Vector", camera_gear.camera.node().getLens().getViewVector())

        v_cam_up = math_utils.p3d_to_np(
            engine.tq_graphics_basics.tq_render.getRelativeVector(
                camera_gear.camera,
                camera_gear.camera.node().getLens().getUpVector()))
        v_cam_up = v_cam_up / np.linalg.norm(v_cam_up)

        r_cam = math_utils.p3d_to_np(camera_gear.camera.getPos())

        e_up = math_utils.p3d_to_np(v_cam_up / np.linalg.norm(v_cam_up))

        e_cross = math_utils.p3d_to_np(
            np.cross(v_cam_forward / np.linalg.norm(v_cam_forward), e_up))

        # determine the middle origin of the draggable plane (where the plane intersects the camera's forward vector)
        # r0_middle_origin = math_utils.LinePlaneCollision(v_cam_forward, r0_obj, v_cam_forward, r_cam)

        # print("r0_obj", r0_obj)
        # print("v_cam_forward", v_cam_forward)
        # print("v_cam_up", v_cam_up)
        # print("r_cam", r_cam)
        # print("e_up", e_up)
        # print("e_cross", e_cross)
        # print("r0_middle_origin", r0_middle_origin)

        # -- calculate the bijection between mouse coordinates m_x, m_y and plane coordinates p_x, p_y

        mouse_pos = base.mouseWatcherNode.getMouse(
        )  # between -1 and 1 in both x and y
        # filmsize = self.camera.node().getLens().getFilmSize()  # the actual width of the film size

        # print("p_xy_offset: ", p_xy_offset)

        p_x, p_y = conventions.getFilmCoordsFromMouseCoords(
            mouse_pos[0], mouse_pos[1], p_xy_offset[0], p_xy_offset[1])
        # p_x, p_y = conventions.getFilmCoordsFromMouseCoords(mouse_pos[0], mouse_pos[1], 0., 0.)

        drag_vec = p_x * e_cross + p_y * e_up

        # print("px: ", p_x, ", py: ", p_y)
        # print("drag_vec: ", drag_vec)

        # camera_new_pos = camera_original_pos + drag_vec
        new_orbit_center = original_orbit_center + (-drag_vec)

        # print("original orbit center: ", original_orbit_center)
        # print("new orbit center: ", new_orbit_center)
        # print("current orbit center: ", camera_gear.get_orbit_center())
        camera_gear.set_orbit_center(
            math_utils.indexable_vec3_to_p3d_Vec3(new_orbit_center))
        # print("updated orbit center: ", camera_gear.get_orbit_center())

    def toggle_visual_aids(self, p):
        """
        Args:
            p: True or False to enalbe or disable visual aids """
        if p == True:
            if self.visual_aids is not None:
                self.visual_aids = OrbiterVisualAids(self)
                self.visual_aids.on()
        else:
            self.visual_aids.off()
            self.visual_aids = None

    def poll_zoom_plus_minus(self, task):

        is_down = base.mouseWatcherNode.is_button_down

        if is_down(self.plus_button):
            self.handle_zoom_plus()

        if is_down(self.minus_button):
            self.handle_zoom_minus()

        return task.cont

    def on_other_side_p(self):
        return self.theta % (2. * np.pi) > np.pi

    def correct_cam_spherical_coords(self, fixed_phi, fixed_theta, fixed_r,
                                     correct_for_camera_setting):
        # print("theta = ", self.theta, ", ",
        #       "phi = ", self.phi, ", ",
        #       "r = ", self.radius)

        # prevent over-the-top flipping
        # self.theta = self.theta % np.pi

        # keep r positive
        if self.radius < 0:
            self.radius = 0.001

        # # keep phi in the range [0, 2*pi]
        # if self.phi > 2.*np.pi:
        #     self.phi = self.phi % 2.*np.pi
        # elif self.phi < 0.:
        #     self.phi = self.phi + 2.*np.pi
        # elif self.phi > 2.*np.pi:
        #     self.phi = self.phi - 2.*np.pi

        theta = None
        phi = None
        r = None

        if fixed_theta:
            theta = fixed_theta
        else:
            theta = self.theta

        if correct_for_camera_setting == True:
            if theta % np.pi < OrbiterOrtho.theta_epsilon:
                theta = theta + OrbiterOrtho.theta_epsilon

        if fixed_phi:
            phi = fixed_phi
        else:
            phi = self.phi

        if fixed_r:
            r = fixed_r
        else:
            r = self.radius

        return theta, phi, r

    def get_cam_spherical_coords_only(self, theta, phi, r):
        return r * np.array([
            np.sin(theta) * np.cos(phi),
            np.sin(theta) * np.sin(phi),
            np.cos(theta)
        ])

    # def get_ortho_zoom_coords_offset_3d(self):
    #     ortho_zoom_coords_offset = self.get_ortho_zoom_coords_offset_2d()
    #     # TODO: do it with arbitrary orientation, not just x right, z up
    #     ortho_zoom_coords_offset = np.array([ortho_zoom_coords_offset[0], 0., ortho_zoom_coords_offset[1]])
    #     return ortho_zoom_coords_offset

    def get_and_update_total_lookat_from_coords(self):
        """ orbiter center and orthographic zoom offset """
        orbit_center = self.get_orbit_center()

        # offset_coords_2d = self.get_ortho_zoom_coords_offset_2d()
        # offset_coords_3d = self.get_ortho_zoom_coords_offset_3d()

        # self.set_previous_zoom_step_plane_coords(*offset_coords_2d)

        return orbit_center  # + offset_coords_3d

    def get_cam_coords(self,
                       offset_theta=0.,
                       offset_phi=0.,
                       fixed_phi=None,
                       fixed_theta=None,
                       fixed_r=None,
                       correct_for_camera_setting=False):
        """ """
        # get corrected quantities
        theta_c, phi_c, r_c = self.correct_cam_spherical_coords(
            fixed_phi, fixed_theta, fixed_r, correct_for_camera_setting)

        total_lookat_from_coords = self.get_and_update_total_lookat_from_coords(
        )

        spherical_coords_only = self.get_cam_spherical_coords_only(
            theta_c + offset_theta, phi_c + offset_phi, r_c)

        return total_lookat_from_coords + spherical_coords_only

    # def get_ortho_zoom_coords_offset_2d(self):
    #     """ the camera coordinates are offset, in order to zoom into the
    #         spot under the mouse """
    #     # x1_p, x2_p = self.get_previous_zoom_step_plane_coords()
    #     x1_p, x2_p = 0., 0.
    #     if not base.mouseWatcherNode.hasMouse():
    #         return np.array([x1_p, x2_p])

    #     # x1_p, x2_p = np.array([0., 0.])
    #     x1_n, x2_n = self.get_zoom_step_plane_coords()
    #     # calculate the difference
    #     offset_coords = np.array([x1_n - x1_p, x2_n - x2_p])

    #     return offset_coords

    def set_orbit_center(self, orbit_center, not_just_init=True):
        """
        Args:
           orbit_center: Vec3 for the new orbit center """

        # print(type(orbit_center))
        # import ipdb; ipdb.set_trace()  # noqa BREAKPOINT
        if orbit_center is not None:
            self._orbit_center = orbit_center

        if not_just_init == True:
            if self.visual_aids:
                self.visual_aids.update()

            self.update_camera_state()
            self.set_pointlight_pos_spherical_coords()

    def get_orbit_center(self, numpy=True):
        """ 
        args:
            numpy: return in numpy or in p3d format """
        if self._orbit_center is not None:
            if numpy == True:
                return math_utils.p3d_to_np(self._orbit_center)
            else:
                # otherwise it should be Vec3
                return self._orbit_center
        else:
            print("self._orbit_center is None")
            exit(1)

    def get_eye_vector_prime(self):
        """ """
        if self.on_other_side_p() == True:
            # in the case where -z is up, to provide visual consistency when flipping over
            return Vec3(-1., 0., 0.)
        else:
            return Vec3(1., 0., 0.)  # in the case where z is up

    def get_up_vector_prime(self):
        """ """
        if self.on_other_side_p() == True:
            return Vec3(0., 0., -1)  # -z is up
        else:
            return Vec3(0., 0., 1.)  # z is up

    def update_camera_state(
            self,
            fixed_phi=None,
            fixed_theta=None,
            fixed_r=None,  # recalculate_film_size=True
            on_init=False):
        """ based on this class' internal variables """
        print("update_camera_state")
        x, y, z = self.get_cam_coords(correct_for_camera_setting=True,
                                      fixed_phi=fixed_phi,
                                      fixed_theta=fixed_theta,
                                      fixed_r=fixed_r)
        self.camera.setPos(
            x, y, z
        )  # TODO: is this really necessary in addition to the setViewMatrix ?
        self.set_view_matrix_after_updated_camera_pos()
        self.set_film_size_from_window_dimensions()
        self.run_camera_move_hooks()

    def set_view_matrix_after_updated_camera_pos(self):
        """ this arcball camera's eye and up vectors change upon
            change of spherical coordinates and offset change """

        # old method: p3d's OrthographicLens' ViewMatrix and projection matrix
        # aren't really the same thing as in OpenGL, but I got it to work

        # --- new method

        # eye = self.get_cam_pos()
        # print("eye: ", eye)

        # at = self.get_orbit_center()
        # print("at: ", at)

        # up = -math_utils.get_e_theta(self.theta, self.phi)
        # print("up: ", up)

        # vm = math_utils.get_lookat_view_matrix(eye, at, up)

        # vm_forrowvecs = math_utils.to_forrowvecs(vm)
        # vm_forrowvecs_tuple = np.ravel(vm_forrowvecs)

        # print("vm_forrowvecs: ", vm_forrowvecs)

        # self.camera.node().getLens().setViewMat(Mat4(*vm_forrowvecs_tuple))

        # --- old method
        # attention! these aren't really eye and up vectors in the global picture
        eye_vector_prime = self.get_eye_vector_prime()
        up_vector_prime = self.get_up_vector_prime()
        self.camera.node().getLens().setViewMat(
            Mat4(eye_vector_prime[0], 0, 0, 0, 0, 1, 0, 0, 0, 0,
                 up_vector_prime[2], 0, 0, 0, 0, 1))

        # print("getViewMat 1: ", self.camera.node().getLens().getViewMat())
        # print("get_projection_mat 1: ", self.camera.node().getLens().get_projection_mat())

        lookat_vec = self.get_and_update_total_lookat_from_coords()
        # lookat_vec = self.get_orbit_center()
        self.camera.lookAt(Vec3(*lookat_vec))

        # print("getViewMat 2: ", self.camera.node().getLens().getViewMat())
        # print("get_projection_mat 2: ", self.camera.node().getLens().get_projection_mat())

    def set_view_to_xz_plane_and_reset_zoom(self):
        self.radius = self.radius_init
        self.set_view_to_xz_plane()

    def set_pointlight_pos_spherical_coords(self):
        x, y, z = self.get_cam_coords(offset_phi=np.pi / 2.)
        self.pl_nodepath.setPos(x, y, z)

    def handle_wheel_up(self):
        self.phi = self.phi + 0.05
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()

    def handle_wheel_down(self):
        self.phi = self.phi - 0.05
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()

    def set_view_to_xy_plane(self):
        """ set view to the xy plane """
        self.phi = -np.pi / 2.
        self.theta = 0.  # np.pi/2.
        # self.theta = OrbiterOrtho.theta_epsilon
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()

    def set_view_to_yz_plane(self):
        self.phi = 0.
        self.theta = np.pi / 2.
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()

    def set_view_to_xz_plane(self):
        self.phi = -np.pi / 2.
        self.theta = np.pi / 2
        # self.theta = OrbiterOrtho.theta_epsilon
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()

    def handle_control_wheel_down(self):
        self.theta = self.theta - 0.05
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()

    def handle_control_wheel_up(self):
        # print("previous theta: \t", self.theta)
        self.theta = self.theta + 0.05
        # print("after setting theta: \t", self.theta)
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()
        # print("aftera updating theta: \t", self.theta)

    def handle_zoom_plus(self):
        print("zooming in")
        # to give an effective zoom effect in orthographic projection
        # the films size is adjusted and mapped (in update_camera_state())
        # to self\.r + r_0
        self.radius = self.radius - 0.05
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()

    def handle_zoom_minus(self):
        print("zooming out")
        # to give an effective zoom effect in orthographic projection
        # the films size is adjusted and mapped (in update_camera_state())
        # to self\.r + r_0
        self.radius = self.radius + 0.05
        # print("r: ", self.radius)
        self.update_camera_state()
        self.set_pointlight_pos_spherical_coords()

    def set_film_size_from_window_dimensions(self,
                                             called_from_orbiter_init=False):
        """ """
        print("set_film_size_from_window_dimensions called, self.radius=",
              self.radius)

        aspect_ratio = None
        if self.before_aspect_ratio_changed_at_init == True or called_from_orbiter_init == True:
            aspect_ratio = conventions.winsizex_0 / conventions.winsizey_0

            if called_from_orbiter_init == False:
                self.before_aspect_ratio_changed_at_init = False
        else:
            aspect_ratio = engine.tq_graphics_basics.get_window_aspect_ratio()
            # print("aspect ratio : ", aspect_ratio)
            # print(engine.tq_graphics_basics.get_window_size_x(),
            #       engine.tq_graphics_basics.get_window_size_y())
            # self.set_orthographic_zoom(None, 2. * aspect_ratio)
            scale_factor = 1. / (conventions.winsizey_0 / 2.)

            scale_factor *= self.convert_r_to_filmsize_offset_scale_factor()

            filmsize_x = engine.tq_graphics_basics.get_window_size_x(
            ) * scale_factor
            filmsize_y = engine.tq_graphics_basics.get_window_size_y(
            ) * scale_factor

            print("filmsize_x, filmsize_y: ", filmsize_x, filmsize_y)

            print("not at init")
            self.lens.setFilmSize(filmsize_x, filmsize_y)
            self.lens.setNearFar(-500., 500.)

    # def set_previous_zoom_step_plane_coords(self, x1, x2):
    #     """ """
    #     self._previous_zoom_step_plane_coords = np.array([x1, x2])

    def get_previous_zoom_step_plane_coords(self):
        return self._previous_zoom_step_plane_coords

    def get_zoom_step_plane_coords(self):
        """ the plane coords on which to re-center the view such that
            the objects under the cursor are not moved while zooming, relative to the stationary cursor.
            call this after set_film_size_from_window_dimensions and after setting all coords """
        if base.mouseWatcherNode.hasMouse():
            # print("get_coords_2d_for_mouse_zoom: ", self.get_coords_2d_for_mouse_zoom())
            return self.get_coords_2d_for_mouse_zoom()
        else:
            # mouse not in window
            return np.array([0., 0.])

    def convert_r_to_filmsize_offset_scale_factor(self):
        return self.radius / OrbiterOrtho.r0

    def scale_aspect2d_from_window_dimensions(self):
        """ when window dimensions change by resizing the window, I want the aspect2d viewport to scale with it, so that
            e.g. fonts attached to it stay the same size w.r.t the screen """

        # engine.tq_graphics_basics.tq_aspect2d.
        # print("getMat p3d native format: \n", aspect2d.getMat())
        # print("getMat from_forrowvecs: \n",
        #       math_utils.from_forrowvecs(aspect2d.getMat()))

        T, R, S = math_utils.decompose_affine_trafo_4x4(
            math_utils.from_forrowvecs(aspect2d.getMat()))
        # import ipdb; ipdb.set_trace()  # noqa BREAKPOINT

        # S = np.array([[0.5497, 0.,     0.,     0.],
        #               [0.,     1.,     0.,     0.],
        #               [0.,     0.,     1.,     0.],
        #               [0.,     0.,     0.,     1.]])

        # aspect2d.setMat(math_utils.to_forrowvecs(T.dot(R).dot(S)))

        # render2d.setPos

        # scale = 1./(engine.tq_graphics_basics.get_window_size_y()/conventions.winsizey_0)
        # aspect2d.setScale(scale,
        #                   1.,
        #                   scale)

        aspect2d.setScale(
            conventions.winsizex_0 /
            engine.tq_graphics_basics.get_window_size_x() * 1. /
            (conventions.winsizex_0 / conventions.winsizey_0), 1.,
            conventions.winsizey_0 /
            engine.tq_graphics_basics.get_window_size_y())

        aspect2d.setPos(
            -1 + conventions.winsizex_0 /
            engine.tq_graphics_basics.get_window_size_x() * 1. /
            (conventions.winsizex_0 / conventions.winsizey_0), 0.,
            1 - conventions.winsizey_0 /
            engine.tq_graphics_basics.get_window_size_y())

        # print("window sizes: ", engine.tq_graphics_basics.get_window_size_x(),
        #       engine.tq_graphics_basics.get_window_size_y())

        # aspect2d.setScale(1., 1., 1.)
        # aspect2d.setScale(1./400, 1, 1./300)

    def get_cam_forward_vector_normalized(self):
        v_cam_forward = math_utils.p3d_to_np(
            engine.tq_graphics_basics.tq_render.getRelativeVector(
                self.camera,
                self.camera.node().getLens().getViewVector()))

        return v_cam_forward / np.linalg.norm(v_cam_forward)

    def get_cam_up_vector_normalized(self):
        v_cam_up = math_utils.p3d_to_np(
            engine.tq_graphics_basics.tq_render.getRelativeVector(
                self.camera,
                self.camera.node().getLens().getUpVector()))
        return v_cam_up / np.linalg.norm(v_cam_up)

    def get_cam_pos(self):
        return math_utils.p3d_to_np(self.camera.getPos())

    def get_e_x_prime(self):
        return math_utils.p3d_to_np(
            np.cross(self.get_cam_forward_vector_normalized(),
                     self.get_cam_up_vector_normalized()))

    def get_e_y_prime(self):
        return self.get_cam_up_vector_normalized()

    # --- for zooming orthographically

    def set_orthographic_zoom(self, width, height):
        """ an orthographic lens' `zoom`
            is controlled by the film size
        args:
            width:  width of the orthographic lens in world coordinates
            height:  height ---------------- '' ------------------------
        """

        if width is None and height:
            width = height * engine.tq_graphics_basics.get_window_aspect_ratio(
            )
        elif height is None:
            print("ERR: height is None")
            exit(1)

        width = height * engine.tq_graphics_basics.get_window_aspect_ratio()
        print("Ortho Lens Film: ", "width: ", width, ", height: ", height)
        # setFilmSize specifies the size of the Lens box
        # I call it a *viewing box* if the projection matrix produces
        # orthogonal projection, and *viewing frustum* if the projection
        # matrix includes perspective)
        self.lens.setFilmSize(width, height)

        print("-------- self.lens.setFilmSize(width, height): ", width, height)

        self.width = width
        self.height = height

    def get_coords_2d_for_mouse_zoom(self):
        """ mouse_pos as returned from base.mouseWatcherNode.getMouse()
            # planeNormal, planePoint, rayDirection, rayPoint
            """
        mouse_pos = base.mouseWatcherNode.getMouse()
        print("mouse_pos: ", mouse_pos[0], mouse_pos[1])
        mouse_p_x, mouse_p_y = conventions.getFilmCoordsFromMouseCoords(
            mouse_pos[0],
            mouse_pos[
                1]  # , self.p_xy_at_init_drag[0], self.p_xy_at_init_drag[1]
        )
        # print("self.p_xy_at_init_drag: ", self.p_xy_at_init_drag)
        print("mouse_p_x, mouse_p_y: ", mouse_p_x, mouse_p_y)

        cam_pos = self.get_cam_pos()
        # print("cam_pos: ", cam_pos)
        r0_shoot = (
            cam_pos +  # camera position
            self.get_e_x_prime() * mouse_p_x +
            self.get_e_y_prime() * mouse_p_y  # camera plane
        )

        # print("self.get_e_y_prime(): ", self.get_e_y_prime())
        # print("self.get_e_x_prime(): ", self.get_e_x_prime())

        # print("r0_shoot: ", r0_shoot)
        print("self.get_cam_up_vector_normalized(): ",
              self.get_cam_up_vector_normalized())

        # planeNormal, planePoint, rayDirection, rayPoint
        r = math_utils.LinePlaneCollision(
            self.get_cam_forward_vector_normalized(), self.get_cam_pos(),
            self.get_cam_forward_vector_normalized(), r0_shoot)

        # print("self.get_cam_forward_vector_normalized():, ", self.get_cam_forward_vector_normalized())
        # print("r: ", r)

        in_plane_vec = r - self.get_cam_pos()

        x1 = np.dot(self.get_e_x_prime(), in_plane_vec)
        x2 = np.dot(self.get_e_y_prime(), in_plane_vec)

        # print("x1, x2: ", x1, x2)
        return np.array([x1, x2])
Ejemplo n.º 5
0
class Panner2d(CameraGear):
    """ """

    # TODO: 1 to 1 mapping from self.view_distance to filmsize, using aspect_ratio
    # filmsize_x = 2 * aspect_ratio * self.view_distance  (~ 2 * 16/9 * 1.)
    view_distance_max = 10.
    view_distance_0 = 2.
    view_distance_step_size = 0.1
    view_distance_min = 0.1

    # these two are always set together, with opposite signs
    x0 = np.array([0., 0.])
    p_previous_offset_0 = np.array([0., 0.])  # for shifting with shift+mouse

    n1 = np.array([1., 0., 0.]) # x right: horizontal coordinate: x_1
    n2 = np.array([0., 0., 1.]) # z up: vertical coordinate: x_2

    cam_pos_y_offset_vector = np.array([0., 1., 0.])

    r0 = cam_pos_y_offset_vector + x0[0] * n1 + x0[1] * n2

    near_0 = -100.
    far_0 = +100.

    plane_normal = np.cross(n1, n2)

    def __init__(self, p3d_camera, enable_visual_aids=True):
        """ """
        CameraGear.__init__(self)

        base.disableMouse()

        self._coords_3d_of_corner = None

        self.view_distance = Panner2d.view_distance_0

        # camera stuff
        self.p3d_camera = p3d_camera

        # init the camera pos
        self.x = Panner2d.x0
        self.p_previous_offset = Panner2d.p_previous_offset_0

        x, y, z = self.get_cam_coords(at_init=True)
        self.p3d_camera.setPos(x, y, z)

        self.lookat = self.get_lookat_vector(at_init=True)

        # --- hooks for camera movement
        self.p3d_camera_move_hooks = []  # store function objects

        self.p_previous_offset = np.array([0., 0.])

        self.set_corner_to_coords(Panner2d.r0, at_init=True)

        # --- set the lens
        self.lens = OrthographicLens()
        self.lens.setNearFar(Panner2d.near_0, Panner2d.far_0)
        self.p3d_camera.node().setLens(self.lens)

        # --- event handling to reorient the camera
        base.mouseWatcherNode.setModifierButtons(ModifierButtons())

        # changing zoom
        base.accept('control-wheel_down', self.handle_zoom_minus_with_mouse)
        base.accept('control-wheel_up', self.handle_zoom_plus_with_mouse)

        base.accept('control--', self.handle_zoom_minus_general)
        base.accept('control-+', self.handle_zoom_plus_general)
        base.accept('control---repeat', self.handle_zoom_minus_general)
        base.accept('control-+-repeat', self.handle_zoom_plus_general)

        # changing shift in horizontal direction
        base.accept('shift-wheel_up', self.handle_control_wheel_up)
        base.accept('shift-wheel_down', self.handle_control_wheel_down)

        # changing shift in vertical direction
        base.accept('wheel_up', self.handle_wheel_up)
        base.accept('wheel_down', self.handle_wheel_down)

        base.accept('shift-mouse1', self.handle_shift_mouse1)

        # -- window resize
        base.accept("aspectRatioChanged", self.run_window_resize_hooks)

        self.add_window_resize_hook(self.update_film_size_from_view_distance)
        self.add_window_resize_hook(self.scale_aspect2d_from_window_dimensions)

        # self.update_film_size_from_view_distance()

    # @staticmethod
    # def set_new_position_from_dragged_mouse(self, p_xy_offset, original_orbit_center):
    #     """ There is a new mouse position, now
    #     Args:
    #         p_xy_offset: the origin point (lies in the camera plane) to which the change point (calculated from the mouse displacement) is being added to """

    #     v_cam_forward = math_utils.p3d_to_np(engine.tq_graphics_basics.tq_render.getRelativeVector(
    #         self.p3d_camera, self.p3d_camera.node().getLens().getViewVector()))
    #     v_cam_forward = v_cam_forward / np.linalg.norm(v_cam_forward)
    #     # print("--- > View Vector", self.p3d_camera.node().getLens().getViewVector())

    #     v_cam_up = math_utils.p3d_to_np(engine.tq_graphics_basics.tq_render.getRelativeVector(
    #         self.p3d_camera, self.p3d_camera.node().getLens().getUpVector()))
    #     v_cam_up = v_cam_up / np.linalg.norm(v_cam_up)

    #     r_cam = math_utils.p3d_to_np(self.p3d_camera.getPos())

    #     e_up = math_utils.p3d_to_np(v_cam_up/np.linalg.norm(v_cam_up))

    #     e_cross = math_utils.p3d_to_np(
    #         np.cross(v_cam_forward/np.linalg.norm(v_cam_forward), e_up))

    #     # -- calculate the bijection between mouse coordinates m_x, m_y and plane coordinates p_x, p_y
    #     mouse_pos = base.mouseWatcherNode.getMouse()  # between -1 and 1 in both x and y
    #     p = conventions.getFilmCoordsFromMouseCoords(
    #         mouse_pos[0], mouse_pos[1], p_xy_offset[0], p_xy_offset[1])

    #     drag_vec = p[0] * e_cross + p[1] * e_up
    #     print(drag_vec)

    #     new_orbit_center = original_orbit_center + (-drag_vec)
    #     print("self.get_cam_coords: ", self.get_cam_coords())

    #     self.x = -p
    #     self.p_previous_offset = p


    def set_new_position_from_dragged_mouse(self, p_xy_offset, original_orbit_center):
        """ There is a new mouse position, now
        Args:
            p_xy_offset: the origin point (lies in the camera plane) to which the change point (calculated from the mouse displacement) is being added to """

        v_cam_forward = math_utils.p3d_to_np(engine.tq_graphics_basics.tq_render.getRelativeVector(
            self.p3d_camera, self.p3d_camera.node().getLens().getViewVector()))
        v_cam_forward = v_cam_forward / np.linalg.norm(v_cam_forward)
        # print("--- > View Vector", self.p3d_camera.node().getLens().getViewVector())

        v_cam_up = math_utils.p3d_to_np(engine.tq_graphics_basics.tq_render.getRelativeVector(
            self.p3d_camera, self.p3d_camera.node().getLens().getUpVector()))
        v_cam_up = v_cam_up / np.linalg.norm(v_cam_up)

        r_cam = math_utils.p3d_to_np(self.p3d_camera.getPos())

        e_up = math_utils.p3d_to_np(v_cam_up/np.linalg.norm(v_cam_up))

        e_cross = math_utils.p3d_to_np(
            np.cross(v_cam_forward/np.linalg.norm(v_cam_forward), e_up))

        # -- calculate the bijection between mouse coordinates m_x, m_y and plane coordinates p_x, p_y

        mouse_pos = base.mouseWatcherNode.getMouse()  # between -1 and 1 in both x and y

        p = conventions.getFilmCoordsFromMouseCoords(
            mouse_pos[0], mouse_pos[1], p_xy_offset[0], p_xy_offset[1])

        drag_vec = p[0] * e_cross + p[1] * e_up

        print(drag_vec)
        # print("p: ", p)

        new_orbit_center = original_orbit_center + (-drag_vec)

        # self.set_corner_to_coords(math_utils.indexable_vec3_to_p3d_Vec3(new_orbit_center))

        print("self.get_cam_coords: ", self.get_cam_coords())

        self.x = -np.array(p)
        self.p_previous_offset = np.array(p)

        self.set_corner_to_coords(math_utils.indexable_vec3_to_p3d_Vec3(new_orbit_center))

    def handle_wheel_up(self):
        # print("handle_wheel_up: self.x[1]: ", self.x[1])
        self.x[1] += 0.1
        self.p_previous_offset[1] -= 0.1

        self.update_camera_state()
        self.set_lookat_after_updated_camera_pos()

    def handle_wheel_down(self):
        # print("handle_wheel_down: self.x[1]: ", self.x[1])
        self.x[1] -= 0.1
        self.p_previous_offset[1] += 0.1

        self.update_camera_state()
        self.set_lookat_after_updated_camera_pos()


    def handle_control_wheel_down(self):
        self.x[0] += 0.1
        self.p_previous_offset[0] -= 0.1

        self.update_camera_state()
        self.set_lookat_after_updated_camera_pos()

    def handle_control_wheel_up(self):
        self.x[0] -= 0.1
        self.p_previous_offset[0] += 0.1

        self.update_camera_state()
        self.set_lookat_after_updated_camera_pos()

    @staticmethod
    def get_film_size_logical(aspect_ratio, view_distance):
        filmsize_x = 2. * aspect_ratio * view_distance  # (~ 2 * 16/9 * 1.)
        filmsize_y = 2. * 1.           * view_distance
        return filmsize_x, filmsize_y

    def update_film_size_from_view_distance(self):
        """ for setting the film size by a 1 to 1 mapping to view_distance """
        # filmsize_x, filmsize_y = self.get_filmsize_with_window_active()

        # winsizex = engine.tq_graphics_basics.get_window_size_x()
        # winsizey = engine.tq_graphics_basics.get_window_size_y()

        aspect_ratio = engine.tq_graphics_basics.get_window_aspect_ratio()  # x/y

        filmsize_x, filmsize_y = Panner2d.get_film_size_logical(aspect_ratio, self.view_distance)

        # print("filmsize_x, filmsize_y: ", filmsize_x, filmsize_y)

        self.lens.setFilmSize(filmsize_x, filmsize_y)
        self.lens.setNearFar(Panner2d.near_0, Panner2d.far_0)

    def get_filmsize_with_window_active(self, view_distance=None):
        """ after the window has been initialized and a default film size has been set """
        if view_distance is None:
            view_distance = self.view_distance

        aspect_ratio = engine.tq_graphics_basics.get_window_aspect_ratio()

        filmsize_x, filmsize_y = Panner2d.get_film_size_logical(aspect_ratio, view_distance)

        return filmsize_x, filmsize_y

    def scale_aspect2d_from_window_dimensions(self):
        """ when window dimensions change by resizing the window, I want the aspect2d viewport to scale with it, so that
            e.g. fonts attached to it stay the same size w.r.t the screen """
        T, R, S = math_utils.decompose_affine_trafo_4x4(math_utils.from_forrowvecs(aspect2d.getMat()))

        winsizex = engine.tq_graphics_basics.get_window_size_x()
        winsizey = engine.tq_graphics_basics.get_window_size_y()

        aspect_ratio = conventions.winsizex_0/conventions.winsizey_0

        aspect2d.setScale(conventions.winsizex_0/winsizex * 1./aspect_ratio,
                          1.,
                          conventions.winsizey_0/winsizey)

        # shift position of origin of aspect2d
        aspect2d.setPos(-1 + conventions.winsizex_0/winsizex * 1./aspect_ratio,
                        0.,
                        1 - conventions.winsizey_0/winsizey)

    # --------------------------------------------------------------------------------
    # --- getters and setters --------------------------------------------------------

    def get_coords_3d_of_corner(self, numpy=True):
        """
        args:
            numpy: return in numpy or in p3d format """
        if self._coords_3d_of_corner is not None:
            if numpy == True:
                return math_utils.p3d_to_np(self._coords_3d_of_corner)
            else:
                # otherwise it should be Vec3
                return self._coords_3d_of_corner
        else:
            print("self._coords_3d_of_corner is None")
            exit(1)

    def set_corner_to_coords(self, coords_3d_of_corner, at_init=False):
        """ """
        if coords_3d_of_corner is not None:
            self._coords_3d_of_corner = coords_3d_of_corner

        if at_init == True:
            return

        self.update_camera_state()

    def get_coords_2d_from_mouse_pos_for_zoom(self, mouse_pos=None, get_r=False):
        """ get the 2d coordinates of the panner plane from the mouse collision """
        if mouse_pos is None:
            mouse_pos = base.mouseWatcherNode.getMouse()

        # print("mouse_pos: ", mouse_pos[0], mouse_pos[1])

        mouse_p_x, mouse_p_y = conventions.getFilmCoordsFromMouseCoords(mouse_pos[0], mouse_pos[1])
        # print("mouse_p_x, mouse_p_y: ", mouse_p_x, mouse_p_y)

        cam_pos = self.get_cam_pos()
        r0_shoot = (cam_pos +  # camera position
                    self.get_e_x_prime() * mouse_p_x + self.get_e_y_prime() * mouse_p_y  # camera plane
                    )

        # args: planeNormal, planePoint, rayDirection, rayPoint
        r = math_utils.LinePlaneCollision(self.get_cam_forward_vector_normalized(), self.get_cam_pos(), self.get_cam_forward_vector_normalized(), r0_shoot)

        in_plane_vec = r - self.get_cam_pos()

        x1 = np.dot(self.get_e_x_prime(), in_plane_vec)
        x2 = np.dot(self.get_e_y_prime(), in_plane_vec)

        # print("x1, x2: ", x1, x2)

        if get_r == True:
            return np.array([x1, x2]), r

        return np.array([x1, x2])


    # ---- math getters -------

    def get_cam_forward_vector_normalized(self):
        v_cam_forward = math_utils.p3d_to_np(
            engine.tq_graphics_basics.tq_render.getRelativeVector(
            self.p3d_camera, self.p3d_camera.node().getLens().getViewVector()))

        return v_cam_forward / np.linalg.norm(v_cam_forward)

    def get_cam_up_vector_normalized(self):
        v_cam_up = math_utils.p3d_to_np(engine.tq_graphics_basics.tq_render.getRelativeVector(self.p3d_camera, self.p3d_camera.node().getLens().getUpVector()))
        return v_cam_up / np.linalg.norm(v_cam_up)

    def get_cam_pos(self):
        return math_utils.p3d_to_np(self.p3d_camera.getPos())

    def get_e_x_prime(self):
        return math_utils.p3d_to_np(np.cross(self.get_cam_forward_vector_normalized(), self.get_cam_up_vector_normalized()))

    def get_e_y_prime(self):
        return self.get_cam_up_vector_normalized()


    # ------- event handlers --------
    def handle_shift_mouse1(self):
        """ """
        print("handle_shift_mouse1")
        self.ddem = DragDropEventManager()

        # ---- calculate (solely camera and object needed and the recorded mouse position before dragging) the self.p_xy_at_init_drag
        # between -1 and 1 in both x and y

        mouse_position_before_dragging = base.mouseWatcherNode.getMouse()
        _p_xy_offset = conventions.getFilmCoordsFromMouseCoords(
            -mouse_position_before_dragging[0],
            -mouse_position_before_dragging[1],
            p_x_0=self.p_previous_offset[0],
            p_y_0=self.p_previous_offset[1])

        self.p_previous_offset = np.array(_p_xy_offset)

        self.ddem.add_on_state_change_function(
            Panner2d.set_new_position_from_dragged_mouse,
            args=(self, _p_xy_offset, self.get_coords_3d_of_corner()))

        self.ddem.init_dragging()

    # -- updating camera --

    def update_camera_state(self, view_distance=None, on_init=False):
        """ based on this class' internal variables """
        self.p3d_camera.setPos(*self.get_cam_coords())
        self.run_camera_move_hooks()

    def get_cam_coords(self, at_init=False, fixed_x_1=None, fixed_x_2=None):
        """ gets the camera coordinates in 3d, based on the x_1 and x_2 of the plane """
        x_1 = None
        x_2 = None

        if fixed_x_1 is not None:
            x_1 = fixed_x_1
        else:
            x_1 = self.x[0]

        if fixed_x_2 is not None:
            x_2 = fixed_x_2
        else:
            x_2 = self.x[1]

        cam_coords = None

        if at_init == True:
            cam_coords = tuple(
                Panner2d.cam_pos_y_offset_vector + self.get_in_plane_point_from_2d_coords(Panner2d.x0[0], Panner2d.x0[1]))
        else:
            cam_coords = tuple(
                Panner2d.cam_pos_y_offset_vector + self.get_in_plane_point_from_2d_coords(x_1, x_2))

        return np.array(cam_coords)

    def get_lookat_vector(self, at_init=False):
        return Vec3(*(self.get_cam_coords(at_init=at_init) + Panner2d.cam_pos_y_offset_vector))

    def set_lookat_after_updated_camera_pos(self, lookat=None):
        """ the lookat point is in the center of the window """
        _lookat = None
        if lookat is None:
            _lookat = self.get_lookat_vector()
        else:
            _lookat = lookat

        self.lookat = math_utils.p3d_to_np(_lookat)
        self.p3d_camera.look_at(Vec3(*self.lookat))

        assert math_utils.vectors_parallel_or_antiparallel_p(Panner2d.plane_normal, self.get_cam_forward_vector_normalized())

    def get_in_plane_point_from_2d_coords(self, x1, x2):
        return x1 * self.n1 + x2 * self.n2

    def zoom_to_2d_coords_of_mouse_task_and_doit(self):
        self.zoom_to_2d_coords_of_mouse(sign=-1., doit=True)

    def zoom_to_2d_coords_of_mouse(self, sign=1., doit=False):
        x = self.get_coords_2d_from_mouse_pos_for_zoom()
        self.zoom_to_2d_coords(x[0], x[1], sign=sign, doit=doit)

    def zoom_to_2d_coords(self, x1, x2, sign=1., doit=False):
        """
        args:
            x1, x2: coords in the plane of the panner
            sign: positive: zoom in (+)
                  negative: zoom out (-)
        """

        # print("p: ", math_utils.vectors_equal_up_to_epsilon(
        #     self.get_cam_coords(), math_utils.p3d_to_np(self.p3d_camera.getPos())))

        view_distance_step = -1. * sign * Panner2d.view_distance_step_size  # zoom in: negative

        old_view_distance = self.view_distance
        new_view_distance = np.clip(old_view_distance + view_distance_step,
                                    Panner2d.view_distance_min,
                                    Panner2d.view_distance_max)

        x_displacement = 0.
        y_displacement = 0.

        if old_view_distance != new_view_distance:
            displacements_scale_factor = -1. * view_distance_step / old_view_distance
            x_displacement = x1 * displacements_scale_factor
            y_displacement = x2 * displacements_scale_factor

        # print("zoom_to_2d_coords: ")
        # print("self.view_distance", self.view_distance, "old_view_distance", old_view_distance, "new_view_distance", new_view_distance, "view_distance_step", view_distance_step)

        if doit == True:
            self.x[0] += x_displacement
            self.x[1] += y_displacement
            self.p_previous_offset[0] -= x_displacement
            self.p_previous_offset[1] -= y_displacement
            self.view_distance = new_view_distance

        self.update_camera_state()
        self.set_lookat_after_updated_camera_pos()
        self.update_film_size_from_view_distance()

    def handle_zoom_plus_with_mouse(self):
        self.zoom_to_2d_coords_of_mouse(sign=1., doit=True)

    def handle_zoom_minus_with_mouse(self):
        self.zoom_to_2d_coords_of_mouse(sign=-1., doit=True)

    def handle_zoom_plus_general(self):
        # if base.mouseWatcherNode.hasMouse():
        #     self.handle_zoom_plus_with_mouse()
        # else:
        self.zoom_to_2d_coords(0., 0., sign=+1., doit=True)

    def handle_zoom_minus_general(self):
        # if base.mouseWatcherNode.hasMouse():
        #     self.handle_zoom_minus_with_mouse()
        # else:
        self.zoom_to_2d_coords(0., 0., sign=-1., doit=True)