#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Panda3D 视频播放示例 演示如何在Panda3D中播放内嵌视频 """ from direct.showbase.ShowBase import ShowBase from panda3d.core import ( MovieTexture, CardMaker, TextureStage, NodePath, WindowProperties, loadPrcFileData, Vec3, Point3, AmbientLight, DirectionalLight, GeomNode ) import os # 配置Panda3D loadPrcFileData("", """ win-size 1280 720 window-title Panda3D 视频播放器 show-frame-rate-meter true """) class VideoPlayerDemo(ShowBase): def __init__(self): ShowBase.__init__(self) # 设置背景色 self.setBackgroundColor(0.1, 0.1, 0.1) # 设置相机 self.cam.setPos(0, -10, 0) self.cam.lookAt(0, 0, 0) # 添加光照 self.setup_lighting() # 创建视频播放器 self.setup_video_players() # 设置控制 self.setup_controls() print("视频播放器已启动!") print("控制键:") print(" SPACE - 播放/暂停") print(" R - 重新播放") print(" + - 加速播放") print(" - - 减速播放") print(" ESC - 退出") def setup_lighting(self): """设置基础光照""" # 环境光 alight = AmbientLight('ambient') alight.setColor((0.3, 0.3, 0.3, 1)) alnp = self.render.attachNewNode(alight) self.render.setLight(alnp) # 定向光 dlight = DirectionalLight('directional') dlight.setColor((0.8, 0.8, 0.8, 1)) dlnp = self.render.attachNewNode(dlight) dlnp.setHpr(45, -45, 0) self.render.setLight(dlnp) def setup_video_players(self): """设置视频播放器""" self.videos = [] # 方法1: 平面视频播放器 self.create_flat_video_player() # 方法2: 3D模型上的视频纹理 self.create_3d_video_screen() # 方法3: 球形全景视频 self.create_spherical_video() def create_flat_video_player(self): """创建平面视频播放器""" try: # 创建视频纹理 (这里使用示例路径,需要替换为实际视频文件) video_path = "video/sample.mp4" # 支持 .mp4, .avi, .mov 等格式 # 如果文件不存在,创建一个简单的色彩动画作为演示 if not os.path.exists(video_path): self.create_color_animation_demo() return # 加载视频 movie_texture = MovieTexture() success = movie_texture.read(video_path) if success: # 创建卡片几何体 cm = CardMaker("video_card") cm.setFrame(-2, 2, -1.5, 1.5) video_card = self.render.attachNewNode(cm.generate()) video_card.setTexture(movie_texture) video_card.setPos(-4, 0, 0) # 播放视频 movie_texture.play() self.videos.append({ 'texture': movie_texture, 'node': video_card, 'name': 'flat_video' }) print(f"✓ 平面视频播放器创建成功: {video_path}") else: print(f"✗ 无法加载视频: {video_path}") except Exception as e: print(f"创建平面视频播放器失败: {str(e)}") def create_3d_video_screen(self): """在3D模型上创建视频纹理""" try: # 创建一个简单的立方体作为"电视屏幕" from panda3d.core import CardMaker # 创建屏幕边框 frame_cm = CardMaker("screen_frame") frame_cm.setFrame(-2.2, 2.2, -1.7, 1.7) screen_frame = self.render.attachNewNode(frame_cm.generate()) screen_frame.setColor(0.1, 0.1, 0.1, 1) screen_frame.setPos(0, 0, 0) # 创建屏幕 screen_cm = CardMaker("video_screen") screen_cm.setFrame(-2, 2, -1.5, 1.5) video_screen = screen_frame.attachNewNode(screen_cm.generate()) # 创建程序化视频内容 (颜色渐变动画) movie_texture = self.create_procedural_video(panoramic=False) video_screen.setTexture(movie_texture) self.videos.append({ 'texture': movie_texture, 'node': video_screen, 'name': '3d_screen' }) print("✓ 3D视频屏幕创建成功") except Exception as e: print(f"创建3D视频屏幕失败: {str(e)}") def create_spherical_video(self): """创建球形全景视频""" try: # 创建球体几何(程序化生成) sphere = self.create_sphere_geometry(radius=2) sphere.reparentTo(self.render) sphere.setPos(4, 0, 0) sphere.setScale(2) # 反转法线以便从内部观看 sphere.setTwoSided(True) # 创建全景视频纹理 panoramic_texture = self.create_procedural_video(panoramic=True) sphere.setTexture(panoramic_texture) self.videos.append({ 'texture': panoramic_texture, 'node': sphere, 'name': 'spherical_video' }) print("✓ 球形全景视频创建成功") except Exception as e: print(f"创建球形全景视频失败: {str(e)}") def create_sphere_geometry(self, radius=1): """程序化创建球体几何""" # 创建简化的球体(立方体的6个面) sphere_root = self.render.attachNewNode("sphere") faces = [ # (位置, 旋转) (Vec3(0, radius, 0), Vec3(0, 0, 0)), # 前面 (Vec3(0, -radius, 0), Vec3(0, 180, 0)), # 后面 (Vec3(radius, 0, 0), Vec3(0, 90, 0)), # 右面 (Vec3(-radius, 0, 0), Vec3(0, -90, 0)), # 左面 (Vec3(0, 0, radius), Vec3(-90, 0, 0)), # 上面 (Vec3(0, 0, -radius), Vec3(90, 0, 0)), # 下面 ] cm = CardMaker("sphere_face") cm.setFrame(-radius, radius, -radius, radius) for i, (pos, hpr) in enumerate(faces): face = sphere_root.attachNewNode(cm.generate()) face.setPos(pos) face.setHpr(hpr) return sphere_root def create_procedural_video(self, panoramic=False): """创建程序化视频内容作为演示""" from panda3d.core import PNMImage, Texture # 创建动态纹理 texture = Texture() if panoramic: texture.setup2dTexture(512, 256, Texture.TUnsignedByte, Texture.FRgba) else: texture.setup2dTexture(256, 256, Texture.TUnsignedByte, Texture.FRgba) # 启动更新任务 - 修复参数传递使用lambda task_name = f"update_video_{'panoramic' if panoramic else 'normal'}" self.taskMgr.add(lambda task: self.update_procedural_video(task, texture, panoramic), task_name) return texture def update_procedural_video(self, task, texture, panoramic): """更新程序化视频内容""" try: from panda3d.core import PNMImage import math # 获取当前时间 time = task.time # 根据纹理类型创建不同尺寸的图像 if panoramic: img = PNMImage(512, 256, 4) # 全景纹理 width, height = 512, 256 else: img = PNMImage(256, 256, 4) # 普通纹理 width, height = 256, 256 for y in range(height): for x in range(width): # 创建动态色彩效果 if panoramic: # 全景模式:创建旋转的渐变 u = x / float(width) # 经度 (0-1) v = y / float(height) # 纬度 (0-1) angle = u * 2 * math.pi + time * 0.5 height_factor = (v - 0.5) * math.pi r = (math.sin(angle * 3) + 1) * 0.5 g = (math.cos(angle * 2 + height_factor * 4) + 1) * 0.5 b = (math.sin(height_factor * 6 + time) + 1) * 0.5 else: # 普通模式:创建波纹效果 center_x = width // 2 center_y = height // 2 dist_x = (x - center_x) / float(center_x) dist_y = (y - center_y) / float(center_y) distance = math.sqrt(dist_x**2 + dist_y**2) wave = math.sin(distance * 10 - time * 5) r = (wave + 1) * 0.5 g = (math.sin(dist_x * 5 + time * 2) + 1) * 0.5 b = (math.sin(dist_y * 5 + time * 3) + 1) * 0.5 img.setXel(x, y, r, g, b) img.setAlpha(x, y, 1.0) # 更新纹理 texture.load(img) return task.cont except Exception as e: print(f"更新程序化视频失败: {str(e)}") return task.done def create_color_animation_demo(self): """创建颜色动画演示(当没有视频文件时)""" try: # 创建卡片 cm = CardMaker("color_demo") cm.setFrame(-2, 2, -1.5, 1.5) demo_card = self.render.attachNewNode(cm.generate()) demo_card.setPos(-4, 0, 0) # 创建动态颜色纹理 demo_texture = self.create_procedural_video(panoramic=False) demo_card.setTexture(demo_texture) self.videos.append({ 'texture': demo_texture, 'node': demo_card, 'name': 'color_demo' }) print("✓ 颜色动画演示创建成功") except Exception as e: print(f"创建颜色动画演示失败: {str(e)}") def setup_controls(self): """设置控制键""" self.accept("space", self.toggle_play_pause) self.accept("r", self.restart_videos) self.accept("plus", self.speed_up) self.accept("minus", self.slow_down) self.accept("escape", self.quit_app) # 鼠标控制相机 self.accept("mouse1", self.start_camera_rotation) self.accept("mouse1-up", self.stop_camera_rotation) self.camera_rotating = False self.last_mouse_x = 0 self.last_mouse_y = 0 # 启动相机更新任务 self.taskMgr.add(self.update_camera_rotation, "camera_rotation") def toggle_play_pause(self): """切换播放/暂停""" for video in self.videos: if hasattr(video['texture'], 'isPlaying'): if video['texture'].isPlaying(): video['texture'].stop() print(f"暂停视频: {video['name']}") else: video['texture'].play() print(f"播放视频: {video['name']}") def restart_videos(self): """重新播放所有视频""" for video in self.videos: if hasattr(video['texture'], 'play'): video['texture'].setTime(0) video['texture'].play() print(f"重新播放视频: {video['name']}") def speed_up(self): """加速播放""" for video in self.videos: if hasattr(video['texture'], 'getPlayRate'): current_rate = video['texture'].getPlayRate() new_rate = min(current_rate * 1.5, 4.0) video['texture'].setPlayRate(new_rate) print(f"加速播放 {video['name']}: {new_rate:.2f}x") def slow_down(self): """减速播放""" for video in self.videos: if hasattr(video['texture'], 'getPlayRate'): current_rate = video['texture'].getPlayRate() new_rate = max(current_rate / 1.5, 0.25) video['texture'].setPlayRate(new_rate) print(f"减速播放 {video['name']}: {new_rate:.2f}x") def start_camera_rotation(self): """开始相机旋转""" if self.mouseWatcherNode.hasMouse(): self.camera_rotating = True self.last_mouse_x = self.mouseWatcherNode.getMouseX() self.last_mouse_y = self.mouseWatcherNode.getMouseY() def stop_camera_rotation(self): """停止相机旋转""" self.camera_rotating = False def update_camera_rotation(self, task): """更新相机旋转""" if self.camera_rotating and self.mouseWatcherNode.hasMouse(): mouse_x = self.mouseWatcherNode.getMouseX() mouse_y = self.mouseWatcherNode.getMouseY() delta_x = mouse_x - self.last_mouse_x delta_y = mouse_y - self.last_mouse_y # 更新相机方向 self.cam.setH(self.cam.getH() - delta_x * 50) new_p = self.cam.getP() + delta_y * 50 self.cam.setP(max(min(new_p, 89), -89)) self.last_mouse_x = mouse_x self.last_mouse_y = mouse_y return task.cont def quit_app(self): """退出应用""" print("退出视频播放器...") self.userExit() if __name__ == "__main__": app = VideoPlayerDemo() app.run()