主要步骤:
1. 每次渲染设置摄像机后,用gluUnProject得到拾取射线,然后和每个三角形所在平面求交。
2. 若拾取起点到交点的向量 和 拾取射线向量的数量积大于0(即三角形在观察者前方),则判断是点是否在三角形内。
3. 若在,则 这个三角形被选择。
由于要不断求交,实际用的时候应该考虑粗略判断,以便减少求交次数。
代码如下(c#):
namespace
RayPick
{
public partial class Form1 : Form
{
Vec2i mpos; //鼠标在窗体内的位置
CDevice decice; //初始化OpenGL
Vec3 rayStart = new Vec3(), rayEnd = new Vec3(); //拾取射线的起点和终点
CTestMesh[] obj = new CTestMesh[4]; //物体
float yrot = 0;
double[] mv = new double[16], prj = new double[16]; //模型变换矩阵和投影矩阵
………………………………………………
//--------------------------------------核心代码-------------------------------------
//渲染循环
private void timer1_Tick(object sender, EventArgs e)
{
GL.glLoadIdentity();
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
GL.glRotated(15, 1, 0, 0);
GL.glRotated(yrot, 0, 1, 0);
GL.glTranslated(0, -5, 0);
yrot += 0.3f;
PickRay(); //得到拾取射线的起点和终点
for (int i = 0; i <= 3; i++) { obj[i].Render(); }
decice.EndRendering();
}
private void PickRay()
{
double o1x, o1y, o1z;
double o2x, o2y, o2z;
WGL.GetCursorPos(ref mpos);
Point p = this.PointToClient(new Point(mpos.x, mpos.y));
mpos.x = p.X; mpos.y = p.Y;
//更新矩阵
GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX , mv);
GL.glGetDoublev(GL.GL_PROJECTION_MATRIX, prj);
int[] viewPort = new int[4];
GL.glGetIntegerv(GL.GL_VIEWPORT, viewPort);
GLU.gluUnProject(mpos.x, (viewPort[3] - mpos.y - 1), 0.0, mv, prj, viewPort, out o1x, out o1y, out o1z);
GLU.gluUnProject(mpos.x, (viewPort[3] - mpos.y - 1), 1.0, mv, prj, viewPort, out o2x, out o2y, out o2z);
rayStart = new Vec3(o1x, o1y, o1z);
rayEnd = new Vec3(o2x, o2y, o2z);
}
//计算平面法向量,用来确定平面点法式方程
private static Vec3 CalcPlaneNormal(Vec3 P1, Vec3 P2, Vec3 P3)
{
Vec3 V1, V2;
Vec3 result;
V1 = P2 - P1;
V2 = P3 - P1;
result = V1 % V2;
result.Normalise();
return result;
}
//判断点是否在三角形内
public static bool IsInTrg(Vec3 vtxA, Vec3 vtxB, Vec3 vtxC, TriEqaResult pHit)
{
//把点代入三边的方程,符号一样则在三角形内
LineEqv l1, l2, l3; //LineEqv 空间直线方程
l1 = new LineEqv(vtxA, vtxB - vtxA);
l2 = new LineEqv(vtxC, vtxA - vtxC);
l3 = new LineEqv(vtxB, vtxC - vtxB);
…………………………
}
public bool Intersect(Vec3 vtxA, Vec3 vtxB, Vec3 vtxC, ref Vec3 pos)
{
//PlaneEqv 平面方程
PlaneEqv cu***ce = new PlaneEqv(vtxA, CalcPlaneNormal(vtxA, vtxB, vtxC));
Vec3 ori = rayEnd - rayStart;
LineEqv curSpd = new LineEqv(rayStart, ori);
//三元一次方程组的解,用来求平面和直线的交点
TriEqaResult pHit = new TriEqaResult(curSpd, cu***ce);
//判断有无解
if (!pHit.hasSolution) { return false; }
pos = new Vec3(pHit.x, pHit.y, pHit.z);
//判断拾取起点到交点的向量 和 拾取射线向量的数量积
if ((pos - rayStart) * ori > 0)
{
return IsInTrg(vtxA, vtxB, vtxC, pHit);
}
else { return false; }
}
private void Form1_Click(object sender, EventArgs e)
{
int bestIdx = -1; //拾取到的最近物体的索引
double bestDist = double.MaxValue; //拾取到的最近物体的距离
for (int i = 0; i < 4; i++)
{
Vec3 intersect = new Vec3(); //交点
if (Intersect(obj[i].vtx[obj[i].faces.idxA], obj[i].vtx[obj[i].faces.idxB], obj[i].vtx[obj[i].faces.idxC], ref intersect))
{
double dist = intersect & rayStart;
//拾取起点到焦点的距离 和一个最近距离值比较,小于则更新距离值和物体索引
if (dist < bestDist)
{
bestDist = dist; bestIdx = i;
}
}
}
if (bestIdx != -1) { System.Windows.Forms.MessageBox.Show(bestIdx.ToString()); }
}
…………………………
源代码:
http://files.cnblogs.com/Yuri/RayPick.rar
{
public partial class Form1 : Form
{
Vec2i mpos; //鼠标在窗体内的位置
CDevice decice; //初始化OpenGL
Vec3 rayStart = new Vec3(), rayEnd = new Vec3(); //拾取射线的起点和终点
CTestMesh[] obj = new CTestMesh[4]; //物体
float yrot = 0;
double[] mv = new double[16], prj = new double[16]; //模型变换矩阵和投影矩阵
………………………………………………
//--------------------------------------核心代码-------------------------------------
//渲染循环
private void timer1_Tick(object sender, EventArgs e)
{
GL.glLoadIdentity();
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
GL.glRotated(15, 1, 0, 0);
GL.glRotated(yrot, 0, 1, 0);
GL.glTranslated(0, -5, 0);
yrot += 0.3f;
PickRay(); //得到拾取射线的起点和终点
for (int i = 0; i <= 3; i++) { obj[i].Render(); }
decice.EndRendering();
}
private void PickRay()
{
double o1x, o1y, o1z;
double o2x, o2y, o2z;
WGL.GetCursorPos(ref mpos);
Point p = this.PointToClient(new Point(mpos.x, mpos.y));
mpos.x = p.X; mpos.y = p.Y;
//更新矩阵
GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX , mv);
GL.glGetDoublev(GL.GL_PROJECTION_MATRIX, prj);
int[] viewPort = new int[4];
GL.glGetIntegerv(GL.GL_VIEWPORT, viewPort);
GLU.gluUnProject(mpos.x, (viewPort[3] - mpos.y - 1), 0.0, mv, prj, viewPort, out o1x, out o1y, out o1z);
GLU.gluUnProject(mpos.x, (viewPort[3] - mpos.y - 1), 1.0, mv, prj, viewPort, out o2x, out o2y, out o2z);
rayStart = new Vec3(o1x, o1y, o1z);
rayEnd = new Vec3(o2x, o2y, o2z);
}
//计算平面法向量,用来确定平面点法式方程
private static Vec3 CalcPlaneNormal(Vec3 P1, Vec3 P2, Vec3 P3)
{
Vec3 V1, V2;
Vec3 result;
V1 = P2 - P1;
V2 = P3 - P1;
result = V1 % V2;
result.Normalise();
return result;
}
//判断点是否在三角形内
public static bool IsInTrg(Vec3 vtxA, Vec3 vtxB, Vec3 vtxC, TriEqaResult pHit)
{
//把点代入三边的方程,符号一样则在三角形内
LineEqv l1, l2, l3; //LineEqv 空间直线方程
l1 = new LineEqv(vtxA, vtxB - vtxA);
l2 = new LineEqv(vtxC, vtxA - vtxC);
l3 = new LineEqv(vtxB, vtxC - vtxB);
…………………………
}
public bool Intersect(Vec3 vtxA, Vec3 vtxB, Vec3 vtxC, ref Vec3 pos)
{
//PlaneEqv 平面方程
PlaneEqv cu***ce = new PlaneEqv(vtxA, CalcPlaneNormal(vtxA, vtxB, vtxC));
Vec3 ori = rayEnd - rayStart;
LineEqv curSpd = new LineEqv(rayStart, ori);
//三元一次方程组的解,用来求平面和直线的交点
TriEqaResult pHit = new TriEqaResult(curSpd, cu***ce);
//判断有无解
if (!pHit.hasSolution) { return false; }
pos = new Vec3(pHit.x, pHit.y, pHit.z);
//判断拾取起点到交点的向量 和 拾取射线向量的数量积
if ((pos - rayStart) * ori > 0)
{
return IsInTrg(vtxA, vtxB, vtxC, pHit);
}
else { return false; }
}
private void Form1_Click(object sender, EventArgs e)
{
int bestIdx = -1; //拾取到的最近物体的索引
double bestDist = double.MaxValue; //拾取到的最近物体的距离
for (int i = 0; i < 4; i++)
{
Vec3 intersect = new Vec3(); //交点
if (Intersect(obj[i].vtx[obj[i].faces.idxA], obj[i].vtx[obj[i].faces.idxB], obj[i].vtx[obj[i].faces.idxC], ref intersect))
{
double dist = intersect & rayStart;
//拾取起点到焦点的距离 和一个最近距离值比较,小于则更新距离值和物体索引
if (dist < bestDist)
{
bestDist = dist; bestIdx = i;
}
}
}
if (bestIdx != -1) { System.Windows.Forms.MessageBox.Show(bestIdx.ToString()); }
}
…………………………