forked from Rowland/EG
395 lines
14 KiB
Python
395 lines
14 KiB
Python
#!/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() |