def __apply_panning(self): """ apply panning camera transformation :return: """ pos = self.window.devices.mouse.pos_instant if self.__prf is None: self.__prf = self.__cam_off_ref k, m = self.window.devices.keyboard, self.window.devices.mouse if k.get_key_status('lshift') and m.get_button_status(2): # actuator d = (self.__prf - self.camera.tripod.plane.origin).length acc = (pos - self.__pp) * d * self.__pa self.__panning = True else: acc = Vec(0, 0, 0) self.__prf = None self.__panning = False # update camera move self.__pv = (self.__pv + acc) * self.__pf if 0.001 < self.__pv.length: self.camera.tripod.move_local(self.__pv) else: self.__pv = Vec(0, 0, 0) self.__pp = pos
def __update_rot(self): self.__rot_val = (self.__rot_val + self.__rot_acc) / self.__rot_fric # simple friction self.__rot_acc = Vec(0, 0, 0) # reset acceleration if self.__rot_val.length < 0.001: # stop if velocity is too small self.__rot_val = Vec(0, 0, 0) else: # move camera # rotate vertically and horizontally tripod = self.camera.tripod tripod.pitch(self.__rot_val.y) axis = Lin.from_pnt_vec(tripod.plane.origin, Vec(0, 0, 1)) tripod.rotate_around(axis, -self.__rot_val.x)
def pitch(self, rad): """ rotate along x axis :return: """ origin, camerax, cameray, cameraz = self.plane.components new_y = cameray.copy().amplify(np.cos(rad)) + cameraz.copy().amplify( np.sin(rad)) new_z = Vec.cross(camerax, new_y) self.__pln = Pln(origin.xyz, camerax.xyz, new_y.xyz, new_z.xyz)
def __update_zoom_accel(self, glfw_window, xoff, yoff, cursor): """ update zoom acceleration when callbacked by scroll move :param glfw_window: :param xoff: scroll left right :param yoff: scroll top bottom :param cursor: :return: """ if self.__zrf is not None: d = (self.__zrf - self.camera.tripod.plane.origin).length # adaptive acceleration related to camera, cursor target distance if yoff < 0: # zoom out self.__za += Vec(0, yoff * d) else: # zoom in self.__za += Vec(0, yoff * d * 0.5) else: # if zoom target is not given use camera origin - world origin distance as default magnitude self.__za += Vec( 0, yoff * self.camera.tripod.plane.origin.as_vec().length * 0.5)
def __init__(self, window, camera, cursor): self.__camera = wr.ref(camera) self.__cursor = wr.ref(cursor) with window.context.glfw as glfw_window: glfw.set_input_mode(glfw_window, glfw.CURSOR, glfw.CURSOR_DISABLED) self.__move_spd = 2 self.__move_boost = 4 window.append_predraw_callback(self.__update_move, keyboard=window.devices.keyboard) mpapf = 100 # max pixel acceleration per frame maapf = 1 # max angle acceleration per frame self.__rot_acc_lim = (-mpapf, mpapf) self.__rot_acc_unit = maapf / mpapf # acceleration unit for 1 pixel cursor move self.__rot_acc = Vec(0, 0, 0) self.__rot_val = Vec(0, 0, 0) self.__rot_fric = 4 window.append_predraw_callback(self.__update_rot) window.devices.mouse.append_cursor_pos_callback(self.__update_rot_acc)
def __apply_zooming(self): """ apply zooming camera :return: """ self.__zv = (self.__zv + self.__za ) * self.__zf # simple acceleration/frictional velocity self.__za = Vec(0, 0, 0) if 1 < abs(self.__zv.y): self.__zooming = True if self.__zrf is None: self.__zd = -self.camera.tripod.plane.axis_z else: self.__zd = self.__zrf - self.camera.tripod.plane.origin self.camera.tripod.move(self.__zd.amplify(self.__zv.y)) else: self.__zv = Vec(0, 0, 0) self.__zooming = False self.__zrf = None
def lookat(self, eye, at, up): """ move plane to look at from eye :param eye: to look from :param at: to look at :param up: direction of camera up :return: """ if all(isinstance(i, tuple) for i in (eye, at, up)): eye = Vec(*eye) at = Vec(*at) up = Vec(*up) else: raise NotImplementedError # calculate plane zaxis = at - eye # vector from eye to at zaxis /= zaxis.length # normalize xaxis = Vec.cross(zaxis, up) # find perpendicular of z and up(y) -> x xaxis /= xaxis.length # normalize yaxis = Vec.cross(xaxis, zaxis) # find true up zaxis *= -1 # reverse z self.__pln = Pln.from_ori_axies(eye, xaxis, yaxis, zaxis)
def __update_rot_acc(self, glfw_window, xpos, ypos, mouse): """ update camera move acceleration :param glfw_window: :param xpos: :param ypos: :param mouse: :return: """ # calculate acceleration v = self.cursor.accel x, y = map(lambda lim, c: max(lim[0], min(c, lim[1])), repeat(self.__rot_acc_lim), v.xy) # clamping self.__rot_acc = Vec(x, y, 0) * self.__rot_acc_unit # mapping
def rotate_around(self, axis, rad): """ rotate along given axis :param axis: to rotate along :param rad: radian value to rotate :return: """ # 1. MoveMat of camera to world origin # 2. RotMat of axis to world z # 3. RotZMat of given rad # 4. apply inverse of 1->2 to resulted plane of 3 axis = axis.as_lin() axis_o, axis_v = axis.start, axis.as_vec() axis_to_z = TrnsfMats( [MoveMat(*(-axis_o).xyz), Vec.trnsf_to_z(axis_v)]) self.__pln = axis_to_z.I * RotZMat(rad) * axis_to_z * self.__pln
def __init__(self, window, camera, cursor, def_offset): self.__camera = wr.ref(camera) self.__cursor = wr.ref(cursor) self.__window = wr.ref(window) self.__def_offset = def_offset self.__panning = False self.__prf = Pnt(0, 0, 0) # percentage describing how much of distance between camera, # cursor target is applied to panning distance self.__pa = 0.005 self.__pf = 0.25 self.__pv = Vec(0, 0, 0) self.__pp = Vec(0, 0, 0) window.append_predraw_callback(self.__apply_panning) self.__zooming = False self.__zrf = None self.__zd = Vec(1, 0, 0) self.__za = Vec(0, 0, 0) self.__zv = Vec(0, 0, 0) self.__zf = 0.25 window.append_predraw_callback(self.__apply_zooming) self.cursor.append_mouse_scroll_callback(self.__update_zoom_accel) self.__orbiting = False self.__orf = Pnt(0, 0, 0) self.__ov = Vec(0, 0, 0) self.__of = 0.25 self.__op = Vec(0, 0, 0) # one 1 pixel move derives 1*-0.05 radian orbiting # which is about 2.8 degree self.__oa = -0.05 window.append_predraw_callback(self.__apply_orbit) window.devices.keyboard.append_key_callback(self.__reset_pos)
class CadDolly(Dolly): """ sketchup like dolly """ def __init__(self, window, camera, cursor, def_offset): self.__camera = wr.ref(camera) self.__cursor = wr.ref(cursor) self.__window = wr.ref(window) self.__def_offset = def_offset self.__panning = False self.__prf = Pnt(0, 0, 0) # percentage describing how much of distance between camera, # cursor target is applied to panning distance self.__pa = 0.005 self.__pf = 0.25 self.__pv = Vec(0, 0, 0) self.__pp = Vec(0, 0, 0) window.append_predraw_callback(self.__apply_panning) self.__zooming = False self.__zrf = None self.__zd = Vec(1, 0, 0) self.__za = Vec(0, 0, 0) self.__zv = Vec(0, 0, 0) self.__zf = 0.25 window.append_predraw_callback(self.__apply_zooming) self.cursor.append_mouse_scroll_callback(self.__update_zoom_accel) self.__orbiting = False self.__orf = Pnt(0, 0, 0) self.__ov = Vec(0, 0, 0) self.__of = 0.25 self.__op = Vec(0, 0, 0) # one 1 pixel move derives 1*-0.05 radian orbiting # which is about 2.8 degree self.__oa = -0.05 window.append_predraw_callback(self.__apply_orbit) window.devices.keyboard.append_key_callback(self.__reset_pos) @property def camera(self): c = self.__camera() if c: return c raise NotImplementedError @property def cursor(self): c = self.__cursor() if c: return c raise NotImplementedError @property def window(self): w = self.__window() if w: return w raise NotImplementedError @property def __cam_off_ref(self): """ use camera offset reference point if reference is not given :return: """ o, _, __, z = self.camera.tripod.plane.components return o + z.project_on_xy().amplify(-self.__def_offset) def __apply_panning(self): """ apply panning camera transformation :return: """ pos = self.window.devices.mouse.pos_instant if self.__prf is None: self.__prf = self.__cam_off_ref k, m = self.window.devices.keyboard, self.window.devices.mouse if k.get_key_status('lshift') and m.get_button_status(2): # actuator d = (self.__prf - self.camera.tripod.plane.origin).length acc = (pos - self.__pp) * d * self.__pa self.__panning = True else: acc = Vec(0, 0, 0) self.__prf = None self.__panning = False # update camera move self.__pv = (self.__pv + acc) * self.__pf if 0.001 < self.__pv.length: self.camera.tripod.move_local(self.__pv) else: self.__pv = Vec(0, 0, 0) self.__pp = pos def __apply_zooming(self): """ apply zooming camera :return: """ self.__zv = (self.__zv + self.__za ) * self.__zf # simple acceleration/frictional velocity self.__za = Vec(0, 0, 0) if 1 < abs(self.__zv.y): self.__zooming = True if self.__zrf is None: self.__zd = -self.camera.tripod.plane.axis_z else: self.__zd = self.__zrf - self.camera.tripod.plane.origin self.camera.tripod.move(self.__zd.amplify(self.__zv.y)) else: self.__zv = Vec(0, 0, 0) self.__zooming = False self.__zrf = None def __update_zoom_accel(self, glfw_window, xoff, yoff, cursor): """ update zoom acceleration when callbacked by scroll move :param glfw_window: :param xoff: scroll left right :param yoff: scroll top bottom :param cursor: :return: """ if self.__zrf is not None: d = (self.__zrf - self.camera.tripod.plane.origin).length # adaptive acceleration related to camera, cursor target distance if yoff < 0: # zoom out self.__za += Vec(0, yoff * d) else: # zoom in self.__za += Vec(0, yoff * d * 0.5) else: # if zoom target is not given use camera origin - world origin distance as default magnitude self.__za += Vec( 0, yoff * self.camera.tripod.plane.origin.as_vec().length * 0.5) def __apply_orbit(self): """ apply orbiting camera :return: """ pos = self.window.devices.mouse.pos_instant acc = (pos - self.__op) * self.__oa self.__op = pos if self.__orf is None: self.__orf = self.__cam_off_ref self.__ov = (self.__ov + acc) * self.__of k, m = self.window.devices.keyboard, self.window.devices.mouse if not k.get_key_status('lshift') and m.get_button_status( 2) and self.__orf is not None: # actuator self.__orbiting = True # to fix orbiting target self.camera.tripod.rotate_around( Lin.from_pnt_vec(self.__orf, ZVec()), self.__ov.x) haxis = -self.camera.tripod.plane.axis_x.project_on_xy( ) # maintain camera up self.camera.tripod.rotate_around( Lin.from_pnt_vec(self.__orf, haxis), self.__ov.y) else: self.__orbiting = False self.__orf = None def set_ref_point(self, x, y, z): """ update camera modifying reference point ! all three modifications has to have separate record :param x: coord x :param y: coord y :param z: coord z :return: """ pos = Pnt(x, y, z) if not self.__orbiting: self.__orf = pos # if not self.__zooming: self.__zrf = pos if not self.__panning: self.__prf = pos def __reset_pos(self, glfw_window, key, scancode, action, mods, keyboard): """ for debugging, reset position with 'shift + z' :param glfw_window: :param key: :param scancode: :param action: :param mods: :param keyboard: :return: """ if action and keyboard.get_char(key, mods) == 'Z': # actuator o, _, __, z = self.camera.tripod.plane.components # horizontal move on camera origin z new_o = Pnt(*z.project_on_xy().amplify(self.__def_offset).xy, o.z) mm = MoveMat(*(new_o - o).xyz) # update new plane self.camera.tripod.plane = mm * self.camera.tripod.plane