chore(git): 更新.gitignore文件以包含更多Python和项目特定文件 - 添加Python相关忽略规则(__pycache__、.pyc等) - 添加虚拟环境相关忽略(venv、env等) - 添加IDE相关忽略(.vscode、.idea等) - 添加C++/CMake相关忽略(CMakeFiles、Makefile等) - 添加Panda3D/RenderPipeline特定忽略(.bam、.egg等) - 添加样本和测试模型相关忽略 - 添加操作系统特定文件忽略 - 移除旧的忽略规则并重新组织结构 ```
159 lines
6.0 KiB
Python
159 lines
6.0 KiB
Python
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()
|