import math from direct.showbase.ShowBase import ShowBase from panda3d.core import KeyboardButton, MouseButton, Point2, Vec3, loadPrcFileData # Basic window settings for clarity. loadPrcFileData("", "window-title Alt+LMB Orbit Demo") loadPrcFileData("", "show-frame-rate-meter #t") class OrbitDemo(ShowBase): def __init__(self): super().__init__() self.disableMouse() # We manage the camera manually. self.focus_distance = 8.0 self.orbit_active = False self.pan_active = False self.radius = self.focus_distance self.azimuth = 0.0 self.elevation = 0.0 self.last_mouse = None self.last_mouse_pan = None self.pivot = Vec3(0, 0, 0) self._setup_scene() self._refresh_camera_from_angles() self.accept("mouse1", self._on_mouse1_down) self.accept("alt-mouse1", self._on_mouse1_down) self.accept("mouse1-up", self._on_mouse1_up) self.accept("mouse2", self._on_mouse2_down) self.accept("mouse2-up", self._on_mouse2_up) self.accept("wheel_up", lambda: self._on_wheel(1)) self.accept("wheel_down", lambda: self._on_wheel(-1)) bt = self.buttonThrowers[0].node() bt.setButtonDownEvent("btn-down") bt.setButtonUpEvent("btn-up") bt.setKeystrokeEvent("keystroke") self.accept("btn-down", lambda name: print("btn-down:", name)) self.accept("btn-up", lambda name: print("btn-up:", name)) self.accept("keystroke", lambda key: print("key:", key)) self.taskMgr.add(self._update_orbit, "update_orbit") def _setup_scene(self): env = self.loader.loadModel("models/environment") env.reparentTo(self.render) env.setScale(0.1) env.setPos(-8, 42, 0) # Small marker that shows the current orbit center. self.focus_marker = self.loader.loadModel("models/smiley") self.focus_marker.reparentTo(self.render) self.focus_marker.setScale(0.15) self.focus_marker.setColor(1, 0.9, 0.2, 1) self.focus_marker.hide() # Shown only while orbiting. def _point_in_front(self): forward = self.camera.getQuat(self.render).getForward() return self.camera.getPos(self.render) + forward.normalized() * self.focus_distance def _sync_angles_from_camera(self): offset = self.camera.getPos(self.render) - self.pivot self.radius = offset.length() if self.radius < 1e-5: self.radius = self.focus_distance offset = Vec3(0, -self.radius, 0) self.azimuth = math.atan2(offset.x, offset.y) self.elevation = math.asin(max(-1.0, min(1.0, offset.z / self.radius))) def _refresh_camera_from_angles(self): # print("Orbiting around:", self.pivot) cos_el = math.cos(self.elevation) x = math.sin(self.azimuth) * cos_el y = math.cos(self.azimuth) * cos_el z = math.sin(self.elevation) self.camera.setPos(self.pivot + Vec3(x, y, z) * self.radius) self.camera.lookAt(self.pivot) def _on_mouse1_down(self): self.orbit_active = True print(self.orbit_active) self.pivot = self._point_in_front() self.focus_marker.setPos(self.pivot) self.focus_marker.show() self._sync_angles_from_camera() self.last_mouse = Point2(self.mouseWatcherNode.getMouse()) def _on_mouse1_up(self): self.orbit_active = False self.last_mouse = None self.focus_marker.hide() print(self.orbit_active) def _on_mouse2_down(self): if not self.mouseWatcherNode.hasMouse(): return self.pan_active = True self.last_mouse_pan = Point2(self.mouseWatcherNode.getMouse()) def _on_mouse2_up(self): self.pan_active = False self.last_mouse_pan = None def _on_wheel(self, direction): # direction: +1 for wheel_up (zoom in), -1 for wheel_down (zoom out) zoom_factor = 0.9 if direction > 0 else 1.1 self.radius = max(0.5, min(200.0, self.radius * zoom_factor)) self._refresh_camera_from_angles() def _update_orbit(self, task): # Middle mouse pan (translates camera and pivot together). if self.pan_active and self.mouseWatcherNode.hasMouse(): current = Point2(self.mouseWatcherNode.getMouse()) if self.last_mouse_pan is not None: delta = current - self.last_mouse_pan right = self.camera.getQuat(self.render).getRight() up = self.camera.getQuat(self.render).getUp() pan_scale = self.radius * 0.8 offset = (right * delta.x + up * delta.y) * pan_scale self.camera.setPos(self.camera.getPos(self.render) + offset) self.pivot += offset self._sync_angles_from_camera() self.last_mouse_pan = current elif self.pan_active and not self.mouseWatcherNode.isButtonDown(MouseButton.two()): self._on_mouse2_up() if not self.orbit_active: return task.cont if not self.mouseWatcherNode.isButtonDown(MouseButton.one()) or not self.mouseWatcherNode.isButtonDown(KeyboardButton.alt()): # print("Press Alt+LMB to activate orbit mode.") return task.cont if not self.mouseWatcherNode.hasMouse() or self.last_mouse is None: print("Mouse not available.") return task.cont current = Point2(self.mouseWatcherNode.getMouse()) delta = current - self.last_mouse self.last_mouse = current # Screen space is [-1, 1], so multiply by a comfortable radians-per-unit factor. rot_speed = math.pi # 180° per full screen swipe. self.azimuth += delta.x * rot_speed self.elevation -= delta.y * rot_speed self.elevation = max(math.radians(-85.0), min(math.radians(85.0), self.elevation)) self._refresh_camera_from_angles() return task.cont if __name__ == "__main__": app = OrbitDemo() app.run()