EG/demo/color_picking_test.py
2025-12-12 16:16:15 +08:00

240 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (
Point3, Vec3, Vec4, CardMaker, Texture, GraphicsOutput,
FrameBufferProperties, WindowProperties, RenderState,
ColorWriteAttrib, DepthTestAttrib, BitMask32, PNMImage,
Camera, DisplayRegion, GraphicsStateGuardian, RenderState
)
class ColorPickingTest(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# 相机设置
self.cam.setPos(-3.87655, -188.38084, 82.602684)
self.cam.lookAt(0, 0, 0)
print("=== 颜色编码点击检测测试 ===")
# 创建测试对象
self.createTestObjects()
# 设置颜色拾取缓冲区
self.setupColorPickingBuffer()
# 测试颜色拾取
self.testColorPicking()
def createTestObjects(self):
"""创建测试对象"""
self.objects = []
self.id_to_object = {} # ID到对象的映射
# 创建几个测试立方体
positions = [
(0, 10, 3, "中心立方体"),
(-5, 10, 3, "左侧立方体"),
(5, 10, 3, "右侧立方体"),
(0, 10, 8, "上方立方体"),
(0, 15, 3, "远处立方体")
]
display_colors = [
(1, 0, 0, 1), # 红色
(0, 1, 0, 1), # 绿色
(0, 0, 1, 1), # 蓝色
(1, 1, 0, 1), # 黄色
(1, 0, 1, 1), # 紫色
]
for i, ((x, y, z, name), display_color) in enumerate(zip(positions, display_colors)):
obj_id = i + 1 # 从1开始编号
# 创建立方体
cm = CardMaker(f"cube_{i}")
cm.setFrame(-1, 1, -1, 1)
cube = self.render.attachNewNode(cm.generate())
cube.setPos(x, y, z)
cube.setScale(2, 2, 2)
cube.setColor(*display_color)
cube.setBillboardAxis()
cube.setName(name)
# 为颜色拾取创建唯一颜色
# 将ID编码为RGB颜色
pick_color = self.encodeIdToColor(obj_id)
# 存储对象信息
obj_info = {
'node': cube,
'name': name,
'world_pos': Point3(x, y, z),
'id': obj_id,
'pick_color': pick_color,
'display_color': display_color
}
self.objects.append(obj_info)
self.id_to_object[obj_id] = obj_info
print(f"创建 {name} 在位置 ({x}, {y}, {z}), ID: {obj_id}, 拾取颜色: {pick_color}")
def encodeIdToColor(self, obj_id):
"""将对象ID编码为RGB颜色"""
# 将ID分解为RGB分量24位
r = (obj_id & 0xFF0000) >> 16
g = (obj_id & 0x00FF00) >> 8
b = (obj_id & 0x0000FF)
# 转换为0-1范围的浮点数
return Vec4(r / 255.0, g / 255.0, b / 255.0, 1.0)
def decodeColorToId(self, color):
"""将RGB颜色解码为对象ID"""
if color is None:
return 0
# 转换为0-255范围的整数
r = int(color[0] * 255 + 0.5)
g = int(color[1] * 255 + 0.5)
b = int(color[2] * 255 + 0.5)
# 组合为ID
obj_id = (r << 16) | (g << 8) | b
return obj_id
def setupColorPickingBuffer(self):
"""设置颜色拾取渲染缓冲区"""
# 创建离屏渲染缓冲区
fbp = FrameBufferProperties()
fbp.setRgbColor(True)
fbp.setColorBits(8, 8, 8, 0) # RGB不需要Alpha
fbp.setDepthBits(24)
wp = WindowProperties()
wp.setSize(self.win.getXSize(), self.win.getYSize())
# 创建缓冲区
self.pick_buffer = self.graphicsEngine.makeOutput(
self.pipe, "pick_buffer", -1, fbp, wp,
GraphicsOutput.M_offscreen, self.win.getGsg(), self.win
)
if self.pick_buffer is None:
print("无法创建颜色拾取缓冲区")
return
# 创建拾取相机
self.pick_cam = self.makeCamera(self.pick_buffer)
self.pick_cam.node().setLens(self.cam.node().getLens())
self.pick_cam.setPos(self.cam.getPos())
self.pick_cam.setHpr(self.cam.getHpr())
# 创建拾取场景(带有颜色编码的对象)
self.pick_scene = self.render.attachNewNode("pick_scene")
# 为每个对象创建拾取版本
for obj_info in self.objects:
# 复制几何体
pick_node = obj_info['node'].copyTo(self.pick_scene)
pick_node.setColor(obj_info['pick_color'])
pick_node.setRenderModeWireframe(False)
pick_node.setLightOff() # 关闭光照以确保颜色准确
# 设置拾取相机只渲染拾取场景
self.pick_cam.node().setCameraMask(BitMask32.allOn())
# 创建颜色纹理
self.pick_tex = Texture()
self.pick_tex.setFormat(Texture.FRgb8)
self.pick_buffer.addRenderTexture(self.pick_tex, GraphicsOutput.RTMCopyRam)
print("颜色拾取缓冲区设置完成")
def getColorAtPixel(self, mouseX, mouseY):
"""获取指定像素的颜色值"""
if self.pick_buffer is None:
return None
# 获取窗口大小
winWidth = self.pick_buffer.getXSize()
winHeight = self.pick_buffer.getYSize()
# 确保坐标在窗口范围内
if mouseX < 0 or mouseX >= winWidth or mouseY < 0 or mouseY >= winHeight:
return None
# 强制渲染拾取缓冲区
self.pick_buffer.setActive(True)
self.graphicsEngine.renderFrame()
# 读取颜色纹理
if self.pick_tex.hasRamImage():
# 获取颜色图像
pnm = PNMImage()
self.pick_tex.store(pnm)
# Y坐标需要翻转OpenGL坐标系
flippedY = winHeight - 1 - mouseY
if flippedY < pnm.getYSize() and mouseX < pnm.getXSize():
# 获取颜色值
r = pnm.getRed(mouseX, flippedY)
g = pnm.getGreen(mouseX, flippedY)
b = pnm.getBlue(mouseX, flippedY)
return (r, g, b)
return None
def pickObjectByColor(self, mouseX, mouseY):
"""使用颜色编码选择对象"""
print(f"\n=== 颜色拾取: 鼠标位置 ({mouseX}, {mouseY}) ===")
# 获取点击位置的颜色
color = self.getColorAtPixel(mouseX, mouseY)
if color is None:
print("无法读取颜色值")
return None
print(f"拾取颜色: RGB({color[0]:.3f}, {color[1]:.3f}, {color[2]:.3f})")
# 解码颜色为对象ID
obj_id = self.decodeColorToId(color)
print(f"解码的对象ID: {obj_id}")
# 查找对应的对象
if obj_id in self.id_to_object:
obj_info = self.id_to_object[obj_id]
print(f"选中对象: {obj_info['name']}")
return obj_info
else:
print("没有找到对应的对象")
return None
def testColorPicking(self):
"""测试颜色拾取"""
print(f"\n=== 颜色拾取测试 ===")
# 等待几帧以确保场景渲染完成
for i in range(5):
self.graphicsEngine.renderFrame()
# 模拟不同的点击位置
test_clicks = [
(619, 362, "屏幕中心"),
(400, 362, "屏幕偏左"),
(800, 362, "屏幕偏右"),
(619, 200, "屏幕上方"),
(619, 500, "屏幕下方"),
]
for mouseX, mouseY, description in test_clicks:
print(f"\n--- 测试 {description}: ({mouseX}, {mouseY}) ---")
selected = self.pickObjectByColor(mouseX, mouseY)
if __name__ == "__main__":
app = ColorPickingTest()
print("颜色拾取测试完成")