代码、原理:计算机图形学(OpenGL)第三版 第4章
我在其中加上了注释
流程
需要用户从文件中读取多边形数据
1、由用户画出第一条射线,需调用函数raytrace2d_mouse,raytrace2d_motion。
2、函数raytrace2d_keyboard中,当按下空格键时,针对每个多边形调用函数timeToHitPoly,计算射线击中该多边形的时间,取最小的那个时间,根据该时间得到射线的可见部分,放入射线列表rays中。接着计算下一条射线,即反射射线:记录反射射线的起点,计算反射射线的方向向量。
3、函数raytrace2d_render绘制各个多边形,并绘制保存在rays中的射线。
timeToHitPoly:针对当前多边形的所有边,调用LipInterval,将射线与每条相交的时间保存在hitTimes中,并不断更新射线留在多边中的时间[t_in, t_out]。判断当前多边形是最外围的多边形,还是里边的小多边形,如果是前者,则击中多边形的时间hit_t = t_out;否则,hit_t = t_in。根据hit_t与hitTimes中的元素比较,得出多边形的哪条边被击中了。
LipInterval:根据射线与当前边的法向量的夹角,得到射线是射入多边形,还是射出多边形,并求出与当前边相交的时间t_hit。如果是射入多边形,t_in = max(t_in, t_hit);否则,t_out = min(t_out, t_hit)
—————————————-原理———————————–
判断射线是射向物体,还是射出物体
则对于多边形P:(n是直线的外法向量)
射线与凸多边形的交点以及裁剪问题
进入是的击中点:A + c * t_in
出去是的击中点:A + c * t_out
射线在多边形中的部分对应的t在区间[t_in, t_out]中。
裁剪算法如下:
将[t_in, t_out]看成候选区间。
1、 初始化候选区间[0, 无穷]
2、对每一条直线,计算击中时间t_hit,并判断这一击中是射入还是射出:
如果t_in大于t_out,说明射线与多边形完全不相交,结束检测。
3、检测完多边形的所有边后,如果候选区间非空,那么从A + c * t_in
到A + c * t_out是在多边形内的。
RayTrace2D.cpp
#include
#include
#include
#include
#include
#include "GlutWin.h"
#include "Line2.h"
#include "Vector2.h"
using namespace std;
// globals //////////////////////////////////////////////////////////
Vector2 C, S, // ray vars p(t) = C + tS
EndPt; // for displaying
bool showNormals;// draw line norms or not
int numPoints; // number of points placed by user
vector< vector > polys; // line lists
vector rays; // list of rays we already traced
GlutWin * win;
// loadpolys ////////////////////////////////////////////////////////
bool loadPolys( const char * fileName )
{
/* Expected file format:
*
* x0 y0
* x1 y1
* x2 y2
* .
* x3 y3
* etc...
*
* where . represents the end of a poly
*
* NOTE:
* all polys must be wound CCW
* this is to make normal generation less complicated
*/
Vector2 first, curr, last;
bool newPoly = true;
vector v;
Line2 currSeg;
int n = 0;
char c;
ifstream i( fileName );
if( ! i.is_open() )
return( false );
while( ! i.eof() )
{
i >> c;
if( c == '.' )
{
// end of a polygon
newPoly = true;
// set seg to last point and first one
currSeg.setVectors( curr, first, LINE_SEGMENT );
v.push_back( currSeg );
// if less than 2 points in poly then error
if( n < 1 )
{
cerr << "Bad Poly\n";
i.close();
return( false );
}
// put poly onto vector of polys
polys.push_back( v );
v.clear();
// unset c (prevent bug out)
c = 0;
continue;
}
else
{
i.putback( c );
if( newPoly )
{
// read first point
i >> curr;
first = last = curr;
n = 0;
newPoly = false;
}
else
{
last = curr;
// read next point
i >> curr;
currSeg.setVectors( last, curr, LINE_SEGMENT );
n++;
// put on polys line list
v.push_back( currSeg );
}
}
}
i.close();
return( true );
}
// LipInterval /////////////////////////////////////////////////////////
//求出直线射入多边形的时间in,或射出多边形的时间out
//in:射线射入多边形的时间
//out:射线从多边形中射出的时间
//num:n * (B - A) A为射入直线的起点,B为被射入直线上的点,n为被射入直线的法向量
//den:n * c c为射入直线的方向向量
bool LipInterval( float & in, float & out, float num, float den )
{
float hit = num / den; //击中时间
if( den < 0 ) //射线是射入的
{
if( hit > out ) return( false ); // 提前退出
if( hit > in ) in = hit; // 选择较大的
}
else if( den > 0 ) //射线是射出的
{
if( hit < in ) return( false ); // 提前退出
if( hit < out ) out = hit; // 选择较小的
}
//else if( num <= 0 ) return( false ); // 射线与直线平行
return( true );
}
// timetohitpoly ////////////////////////////////////////////////////
//求射线击中多边形的时间
//nPolyInd:多边形的下标
//nLineInd:多边形中被击中边的下标
//hit:射线击中多边形的时间
bool timeToHitPoly( int nPolyInd, int & nLineInd, float & hit)
{
Vector2 t, p, n;
float num, den;
float out = 10000;
float in = 0.0f;
bool bHit = false;
vector<float> hitTimes;
//计算射线与多边形每条边相交的时间
for( int c = 0; c < polys[nPolyInd].size(); c++ )
{
polys[nPolyInd][c].getVectors( p, n, LINE_PTNORM );
t = p - S; //p为当前多边形中当前边上的点,S为射线的起点
num = n * t; //n为当前多边形中当前边的法向量
den = n * C; //C为射线的方向向量
hitTimes.push_back( num / den ); //得到射线击中时间,并加入列表
//得到射线与当前多边形中每条边相交的时间
if( ! LipInterval( in, out, num, den ) ) //射线没有击中多边形
return( false );
if( num / den < 0 ) // ray goes backwards to hit line
continue;
bHit = true;
}
//得到多边形中哪条边被击中了
// 多边形0是外围的最大多边形,包含了其他多边形
if( nPolyInd == 0 )
{
if( out < 10000.0f )
{
hit = out;
for( int c = 0; c < hitTimes.size(); c++ )
{
if( hitTimes[c] == hit )
{
nLineInd = c;
break;
}
}
}
}
else
{
// other polys would be entered by the ray
if( in > 0.0f )
{
hit = in;
for( int c = 0; c < hitTimes.size(); c++ )
{
if( hitTimes[c] == hit )
{
nLineInd = c;
break;
}
}
}
}
return( bHit );
}
// raytrace2d_render ///////////////////////////////////////////////
void raytrace2d_render()
{
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glColor3f( 1.0f, 0.0f, 0.0f );
Vector2 a, b, m, n;
//画出各个多边形
for( int x = 0; x < polys.size(); x++ )
{
glBegin( GL_LINES );
for( int y = 0; y < polys[x].size(); y++ )
{
polys[x][y].getVectors( a, b, LINE_SEGMENT );
polys[x][y].getVectors( m, n, LINE_PTNORM );
glVertex2f( a.x, a.y );
glVertex2f( b.x, b.y );
//是否画出多边形上的法向量
if( showNormals )
{
glVertex2f( (a + (0.5f * (b - a))).x, (a + (0.5f * (b - a))).y );
glVertex2f( ((a + (0.5f * (b - a)) + (10 * n))).x, ((a + (0.5f * (b - a)) + (10 * n))).y );
}
}
glEnd();
}
glColor3f( 0.0f, 0.0f, 1.0f );
glBegin( GL_LINES );
//画射线
if( numPoints == 0 ) //第一条射线已经绘制完
{
for( int c = 0; c < rays.size(); c++ )
{
rays[c].getVectors( a, b, LINE_SEGMENT );
glVertex2f( a.x, a.y );
glVertex2f( b.x, b.y );
}
}
else //画第一条射线
{
glVertex2f( S.x, S.y );
glVertex2f( EndPt.x, EndPt.y );
}
glEnd();
glutSwapBuffers();
glFlush();
}
// raytrace2d_keyboard //////////////////////////////////////////////
void raytrace2d_keyboard( unsigned char key, int x, int y )
{
Vector2 a, b, norm;
Line2 l;
bool h;
float t, f = 10000;
int nPolyHit, nFaceHit, n;
//如果按下空格键
if( key == ' ' && numPoints != 1 )
{
//求射线击中每个多边形的时间,取时间最小的那个
for( int x = 0; x < polys.size(); x++ )
{
h = timeToHitPoly(x, n, t);
cout << x << ":" << (h ? "true" : "false") << " " << t << endl;
if( h && t < f )
{
f = t; //射线击中多边形的时间
nPolyHit = x;
nFaceHit = n;
}
}
cout << "=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-" << endl;
// 计算当前射线的可见部分(线段形式)
a = S; //射线起点
b = S + (f * C); //射线击中点,C为射线的方向向量
l = Line2( a, b, LINE_SEGMENT ); //得到射线的可见部分
// 放入rays中
rays.push_back( l );
// 计算反射射线的方向
S = b; //下一条射线的起点
polys[nPolyHit][nFaceHit].getVectors( a, norm, LINE_PTNORM );
C = C - ( ( 2.0f * ( C * norm ) ) * norm ); //下一条射线的方向向量
}
if( key == 'n' )
showNormals = !showNormals;
glutPostRedisplay();
}
// raytrace2d_mouse /////////////////////////////////////////////////
void raytrace2d_mouse( int button, int state, int x, int y )
{
//鼠标按下事件
if( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN )
{
numPoints++;
if( numPoints == 1 )
{
// user is placing the starting point for the ray
S = Vector2( x, (600 - y) );
rays.clear();
EndPt = Vector2( x, 600 - y );
}
if( numPoints == 2 )
{
// user is placing point deining rays direction
C = Vector2( x, (600 - y) ) - S;
if( C == Vector2( 0, 0 ) )
C = Vector2( 1, 0 );
numPoints = 0;
raytrace2d_keyboard( ' ', 0, 0 ); // cheese tactics
}
}
glutPostRedisplay();
}
// raytrace2d_motion ///////////////////////////////////////////////
//鼠标移动事件
void raytrace2d_motion( int x, int y )
{
if( numPoints == 1 )
{
EndPt = Vector2( x, 600 - y );
glutPostRedisplay();
}
}
// main /////////////////////////////////////////////////////////////
int main( int argc, char **argv )
{
// initialize GLUT class
win = new GlutWin( 600, 800,
100, 100,
GLUT_DOUBLE | GLUT_RGB,
"2d ray tracing demo" );
if( ! loadPolys( "polys.txt" ) )
cerr << "unable to open file: polys.txt" << endl;
C = Vector2( 10, 0 );
S = Vector2( 150, 300 );
showNormals = false;
numPoints = 0;
glutDisplayFunc( raytrace2d_render );
glutKeyboardFunc( raytrace2d_keyboard );
glutMouseFunc( raytrace2d_mouse );
glutPassiveMotionFunc( raytrace2d_motion );
// start rendering loop
glutMainLoop();
delete win;
return( 0 );
}
Line2.h
#ifndef _LINE2_H_
#define _LINE2_H_
#include "Vector2.h"
#include
using namespace std;
enum LineType
{
LINE_SEGMENT,
LINE_PTNORM,
LINE_PARAM
};
class Line2
{
public:
Line2( const Vector2 & a, const Vector2 & b, LineType t ); // totally utility
Line2( float x1, float y1, float x2, float y2 ); // nasty line seg
Line2(){};
bool getVectors( Vector2 & a, Vector2 & b, LineType t ) const;
void setVectors( const Vector2 & a, const Vector2 & b, LineType t );
friend ostream & operator << ( ostream & o, Line2 & l )
{
o << l.A << " " << l.B;
return( o );
}
protected:
Vector2 C, S, // param form,p(t) = S + Ct,直线的参数形式
A, B, // line seg form,A -> B,线段形式
P, N; // point normal form,P为直线上的点,N为法线
bool isSeg; // is a segment or a line?
};
#endif
Line2.cpp
#include "Line2.h"
Line2::Line2( const Vector2 & a, const Vector2 & b, LineType t )
{
setVectors( a, b, t );
}
Line2::Line2( float x1, float y1, float x2, float y2 )
{
setVectors( Vector2( x1, y1 ),
Vector2( x2, y2 ),
LINE_SEGMENT );
}
void Line2::setVectors( const Vector2 & a, const Vector2 & b, LineType t )
{
switch( t )
{
case LINE_SEGMENT:
// init segment vars
A = a;
B = b;
isSeg = true;
// init param form
C = (a - b);
C.normalize();
S = a;
// init PN form
P = a;
N = C.perp();
break;
case LINE_PTNORM:
// init PV vars
P = a;
N = b;
N.normalize();
// init param form
S = a;
C = N.perp();
// segment form makse no sense here
isSeg = false;
break;
case LINE_PARAM:
// init param vars
S = a;
C = b;
C.normalize();
// init PV vars
P = a;
N = C.perp();
// segment form makse no sense here
isSeg = false;
break;
default: break;
}
}
bool Line2::getVectors( Vector2 & a, Vector2 & b, LineType t ) const
{
switch( t )
{
case LINE_SEGMENT:
if( isSeg )
{
a = A;
b = B;
}
else
{
// user asked for segment endpoints, and this is not a seg
return( false ); // = error
}
break;
case LINE_PTNORM:
a = P;
b = N;
break;
case LINE_PARAM:
a = S;
b = C;
break;
default:
return( false );
}
return( true );
}
Vector2.h
#ifndef _Vector2_H
#define _Vector2_H
#include
#include
using namespace std;
class Vector2
{
public:
// vars
float x, y;
// constructors
Vector2() {}
Vector2( float x1, float y1 ) : x(x1), y(y1) {}
// vector ops
void normalize()
{
float temp = 1 / length();
x *= temp;
y *= temp;
}
inline double length() const
{
return( sqrt((x * x) + (y * y)) );
}
Vector2 perp() const
{
return( Vector2( y, -x ) );
}
// operators
Vector2 operator + ( const Vector2 & rhs ) const
{
return( Vector2(x + rhs.x, y + rhs.y) );
}
Vector2 operator - ( const Vector2 & rhs ) const
{
return( Vector2( x - rhs.x, y - rhs.y ) );
}
Vector2 operator / ( float k ) const
{
return( Vector2(x / k, y / k) );
}
float operator * ( const Vector2 & rhs ) const
{
// dot product
return( (x * rhs.x) + (y * rhs.y) );
}
Vector2 operator * ( const float & rhs ) const
{
// scale by scalar
return( Vector2( x * rhs, y * rhs ) );
}
bool operator == ( const Vector2 & rhs )
{
return( rhs.x == x && rhs.y == y );
}
friend Vector2 operator * ( const float & lhs, const Vector2 & rhs )
{
// scale by scalar
return( Vector2( rhs.x * lhs, rhs.y * lhs ) );
}
float & operator [] ( int n )
{
// access like an array
switch( n )
{
case 0 : return( x );
case 1 : return( y );
default : /* bug out */;
}
}
friend istream & operator >> ( istream & i, Vector2 & v )
{
i >> v.x >> v.y;
return( i );
}
friend ostream & operator << ( ostream & o, Vector2 & v )
{
o << v.x << " " << v.y;
return( o );
}
}; // end class
#endif // _Vector2_H
GlutWin.h
#ifndef _GLUTWIN_H_
#define _GLUTWIN_H_
#include
#include
#include
#include
////////////////////////////////////////////////////////////
// GlutWin //
// //
// our class for general purpose GLUT initialization //
////////////////////////////////////////////////////////////
class GlutWin
{
public:
// constructor
GlutWin( int windowHeight, int windowWidth,
int windowPosX, int windowPosY,
unsigned int displayMode,
const char * windowTitle );
~GlutWin() {};
private:
const char * windowTitle;
int windowHeight, windowWidth;
int windowPosX, windowPosY;
int windowID;
unsigned int displayMode;
bool fullScreen;
};
#endif // _CGLUTWIN_H_
GlutWin.cpp
#include "GlutWin.h"
GlutWin::GlutWin( int windowHeight, int windowWidth,
int windowPosX, int windowPosY,
unsigned int displayMode,
const char * windowTitle )
{
// initialize members
windowTitle = windowTitle;
windowHeight= windowHeight;
windowWidth = windowWidth;
windowPosX = windowPosX;
windowPosY = windowPosY;
displayMode = displayMode;
fullScreen = false;
// make some dummy command line for glut
char cmd_line[8];
char * argv[1];
argv[0] = cmd_line;
int argc = 1;
// initialize glut
glutInit( &argc, argv );
// initialize window
glutInitWindowSize( windowWidth, windowHeight );
glutInitWindowPosition( windowPosX, windowPosY );
glutInitDisplayMode( displayMode );
// create window
windowID = glutCreateWindow( windowTitle );
// set the view frustum
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( 0, windowWidth, 0, windowHeight );
glMatrixMode( GL_MODELVIEW );
// clear rendering surface
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // background is black
glViewport(0, 0, windowWidth, windowHeight);
}
ploys.txt
70 260
150 550
600 550
700 250
450 75
.
300 240
275 260
300 300
350 250
340 240
.
500 440
475 460
500 500
550 450
540 440
.
500 240
475 260
500 300
550 250
540 240
.
500 440
475 460
500 500
550 450
540 440
.