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)
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)
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)
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])
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)