目标:
有一个3D模型物体,想要它在屏幕的某个位置显示。简单的说原3D物体在屏幕的中心显示,现在我可以把他移动到相应的屏幕坐标进行显示。通过鼠标点击屏幕上的某个位置,把物体平移到那个位置上进行显示。如何把二维的平移变成三维物体的平移呢?
原理如下图:下图是一个视景体。投影的平面为近投影平面,物体投影到近投影平面又通过视口变换变换到屏幕坐标,在设备上显示。
已知条件:
1.通过鼠标点击事件,可以获得鼠标点击的屏幕坐标。
2.物体原点坐标值,物体坐标系。(一般情况下,物体坐标原点与世界坐标原点重合)
3.已知照相机的位置,观察方向,视景体的宽高,近平面,远平面。(通过gluLookAt设置照相机坐标系。通过gluPerspective或者glFrustrum设置视景体)
求解:
物体在自身的一个垂直于视点的平面上的平移向量。resultVector.
求解过程:
- 已知屏幕上的中心点screenCenter和鼠标点击的点mousePos 。还有物体坐标的原点objOrigin,照相机坐标cameraPos.
- 首先把屏幕上的两个点screenCenter和mousePos转换成对应的三维场景中的视觉坐标。(通过gluUnProject可以达到这个目的。)
- 通过cameraPos和screenCenter两点相应的视觉坐标, 可以求得第一条射线ray1。cameraPos和mousePos求得第二天射线ray2.
- 通过照相机的观察方向eyeDir和物体上的原点,得到一个垂直于观察方向的平面plane.
- 求得这两条射线与这个平面的交点,分别为crossPoint1, crossPoint2.
- 那么crossPoint2 – crossPoint1 就得到了物体的平移向量moveVector。再调用glTranslatef进行平移。
关键代码如下:
void CBuilding::Project(float modelview[4*4], float project[4*4])
{
//视口
GLint viewPort[4];
GLfloat rayNearPoint[3];
GLfloat rayFarPoint[3];
GLfloat rayDir[3];
//物体上的原点,如果是进行平移了,则是平移后的点
float planPoint[3] = {m_xTrans, m_yTrans, m_zTrans};
float *crossPoint;
float *crossPoint1;
//垂直于视线的平面向量
m3dNormalizeVector(m_eyeVector);
glGetIntegerv(GL_VIEWPORT, viewPort);
//求出一条射线, 屏幕中心坐标
GLfloat winX = m_centerX;
GLfloat winY = m_centerY;
GLfloat winZ = 0.0;
gluUnProject(winX, winY, winZ, modelview, project, viewPort, &rayNearPoint[0], &rayNearPoint[1], &rayNearPoint[2]);
winZ = 1.0;
gluUnProject(winX, winY, winZ, modelview, project, viewPort, &rayFarPoint[0], &rayFarPoint[1], &rayFarPoint[2]);
//求得射线向量
m3dSubtractVectors3(rayDir, rayFarPoint, rayNearPoint);
m3dNormalizeVector(rayDir);
//射线与平面的交点
crossPoint = CalPlaneLineIntersectPoint(m_eyeVector, planPoint, rayDir, rayNearPoint);
//再求移动后的射线与平面的交点 crossPoint1. 计算两个点之间向量。 以这个向量作为物体的平移
winX = m_screenX;
winY = viewPort[3] - m_screenY;
winZ = 0.0;
gluUnProject(winX, winY, winZ, modelview, project, viewPort, &rayNearPoint[0], &rayNearPoint[1], &rayNearPoint[2]);
winZ = 1.0;
gluUnProject(winX, winY, winZ, modelview, project, viewPort, &rayFarPoint[0], &rayFarPoint[1], &rayFarPoint[2]);
//求得射线向量
m3dSubtractVectors3(rayDir, rayFarPoint, rayNearPoint);
m3dNormalizeVector(rayDir);
//射线向量与平面的交点
crossPoint1 = CalPlaneLineIntersectPoint(m_eyeVector, planPoint, rayDir, rayNearPoint);
//求得移动向量
m_moveVector[0] = crossPoint1[0] - crossPoint[0];
m_moveVector[1] = crossPoint1[1] - crossPoint[1];
m_moveVector[2] = crossPoint1[2] - crossPoint[2];
delete []crossPoint;
delete []crossPoint1;
}
/// <summary>
/// 求一条直线与平面的交点
/// </summary>
/// <param name="planeVector">平面的法线向量,长度为3</param>
/// <param name="planePoint">平面经过的一点坐标,长度为3</param>
/// <param name="lineVector">直线的方向向量,长度为3</param>
/// <param name="linePoint">直线经过的一点坐标,长度为3</param>
/// <returns>返回交点坐标,长度为3</returns>
float* CBuilding::CalPlaneLineIntersectPoint(float* planeVector, float* planePoint, float* lineVector, float* linePoint)
{
float* returnResult = new float[3];
float vp1, vp2, vp3, n1, n2, n3, v1, v2, v3, m1, m2, m3, t,vpt;
vp1 = planeVector[0];
vp2 = planeVector[1];
vp3 = planeVector[2];
n1 = planePoint[0];
n2 = planePoint[1];
n3 = planePoint[2];
v1 = lineVector[0];
v2 = lineVector[1];
v3 = lineVector[2];
m1 = linePoint[0];
m2 = linePoint[1];
m3 = linePoint[2];
vpt = v1 * vp1 + v2 * vp2 + v3 * vp3;
//首先判断直线是否与平面平行
if (vpt == 0)
{
returnResult = NULL;
}
else
{
t = ((n1 - m1) * vp1 + (n2 - m2) * vp2 + (n3 - m3) * vp3) / vpt;
returnResult[0] = m1 + v1 * t;
returnResult[1] = m2 + v2 * t;
returnResult[2] = m3 + v3 * t;
}
return returnResult;
}
void CBuilding::DrawData()
{
glPushMatrix();
....
glTranslatef(m_xTrans, m_yTrans, m_zTrans);
//物体移动向量
glTranslatef(m_moveVector[0], m_moveVector[1], m_moveVector[2]);
绘制物体
...
glPopMatrix();
}
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glPushMatrix();
glFrustum(-g_frum, g_frum, -g_frum/aspect, g_frum / aspect, 1.0, 100000.0);
//获得投影矩阵,用于gluUnProject
glGetFloatv(GL_PROJECTION_MATRIX, project);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(m_eye[0], m_eye[1], m_eye[2], m_ref[0], m_ref[1], m_ref[2], m_eyeUp[0], m_eyeUp[1], m_eyeUp[2]);
//获得模型视图矩阵用于gluUnProjectio
glGetFloatv(GL_MODELVIEW_MATRIX, modelview);
glPushMatrix();
DrawVecData();
glPopMatrix();
glutSwapBuffer();
}
void DrawVecData()
{
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
//求得观察方向
float vec[3];
vec[0] = m_eye[0] - m_ref[0];
vec[1] = m_eye[1] - m_ref[1];
vec[2] = m_eye[2] - m_ref[2];
//mouseUp为鼠标点击的坐标,viewport[2]/2, viewport[3]/2 为屏幕的中心点
CBuilding building(mouseUpX, mouseUpY, viewport[2]/2, viewport[3]/2);
building.LoadData(fileName[0]);
//设置物体的偏移值 m_xTrans, m_yTrans, m_zTrans
building.SetTranslation(xTran, yTran, zTran);
//设置观察方向 m_eyeVector
building.SetEyeVector(vec);
//进行投影计算,使用上面获得的modelviwe和porject矩阵
//需要注意的是,如果物体经过旋转平移等变换,那么相应的modelview也应该是平移和旋转后的。
building.Project(modelview, project);
building.DrawData();
building.ReleaseData();
}
相关讨论http://www.opengpu.org/forum.php?mod=viewthread&tid=16405
关于如何求得空间直线与平面的交点的函数CalPlaneLineIntersectPoint。
参考这里http://blog.csdn.net/abcjennifer/article/details/6688080