真实描绘现实中的环境或物体的光影是计算机图形学中一个重要的课题,最常用的一个方法是光线追踪显示法,即当光线进入物体中间时候,计算(追踪)光的路径。试写一个程序计算在特定环境中关的路线。
为了简单起见,我们将仅仅考虑二维的情景。所有物体是完全的球面体,当光线击中这样一种球时,它被完全发射并遵守反射定律,即入射角于反射角相等,如图1所示:
w |
w |
图1:光线射中球面时光的反射 |
图2所示为某一环境下的光路:
图2: 某一环境下的光路 |
你的任务时写一特定程序,对给定一个特定环境下的光线进入的情况,确定哪些球被光线击中。
输入格式:
第一行包含一个整数n(n<=25),表示有n个球。接着n行包含三个整数,Xi, Yi, Ri,表示一个球的数据,(Xi, Yi) 是球心坐标,Ri>0是球半径。最后一行包含4个整数:X, Y, Dx, Dy, 用于描述光线,光线起源于(X, Y), 且前进方向的增量是(Dx, Dy,)。Dx, Dy,至少有一个非零。
输出格式:
输出只有一行,被光线击中的顺序,打印光线前10次改变方向时击中的球(球的编号是按它们输入是的顺序号)。如果光线击中至多10个球(然后射向无穷远),则打印“inf”在最后一个它击中的球后。如果光线击中多于10个球,在第10个球后打印三个点“…”
输入输出样例:
Input 1 | Output 1 |
3 3 3 2 7 7 1 8 1 1 3 8 1 -4 | 1 2 1 3 inf |
Input 2 | Output 2 |
1 4 4 1 -2 -2 1 0 | Inf |
Input 3 | Output 3 |
2 9 14 3 -4 6 3 0 0 3 5 | 1 2 inf |
解:
这道题有两个难点:1、确定光线于那个球相遇;2、计算光线反射后的角度。
这里光线的方程采用参数方程:
x=x0+t×dx
y=y0+t×dy
(可以推出光线的直线方程是:dy×x-dx×y+dx×y0-dy×x0=0)
1、 确定光线于哪个球相遇
计算光线于每个球的圆心距:abs(a×x+b×y+c) / sqrt(a×a+b×b)(x,y为圆心坐标,a,b,c可由光线的直线方程得到)。距离小于半径既有交点。然后联立参数方程和圆方程,解出t的值(二次方程取较小的根,即与光源距离交小的点),如果t值小于0就舍去(表示焦点在光线的反方向)。所有圆的交点t值中最小的即为所求的放射点参数。如果没有一个t满足条件,就表示光线射向无穷远。
2、计算光线反射后的角度
设光源指向放射点的角度为a1,圆心指向放射点的角度为a2。则放射后的角度为a2-(a1-a2)(在草纸上划下很容易证的)。所对应的dx,dy,即为cos(a1)和sin(a1)。
代码:
#include < math.h >
#include < string .h >
#include < stdlib.h >
#define MAX 25 // 最多25个球
#define pi 3.14159265 // 圆周率
#define sqr(a) ((a)*(a)) // 平方
struct TCircle{
double x,y,r;
}circle[MAX];
double x,y,dx,dy,t; // 光线参数
int n; // 共有n个球
inline void
readData(){ // 读取数据
int i;
scanf( " %d " , & n);
for (i = 0 ;i < n;i ++ )
scanf( " %lf%lf%lf " , & circle[i].x, & circle[i].y, & circle[i].r);
scanf( " %lf%lf%lf%lf " , & x, & y, & dx, & dy);
}
inline double
distance( int i){ // 计算光直线到圆心的距离
double aa,bb,cc;
aa = dy;
bb = dx;
cc = dx * y - dy * x;
return fabs(aa * circle[i].x - bb * circle[i].y + cc) / sqrt(aa * aa + bb * bb);
}
inline double
getAngle( double x, double y, double hx, double hy){ // 计算直线的角度
double q;
if (fabs(x - hx) < 1e - 10 ){ // 直线垂直x轴
if (y - hy > 0 ) q = pi / 2 ; // y轴正方向 90度
else q = 3 * pi / 2 ; // y轴负方向 270度
}
else {
if (x - hx > 0 ) q = atan((y - hy) / (x - hx));
else q = atan((y - hy) / (x - hx)) + pi;
}
if (q < 0 ) q = q + 2 * pi;
return q;
}
inline void
setLine( int i){ // 重置光线坐标
double q1,q2;
double hx,hy;
hx = x + t * dx;
hy = y + t * dy;
q1 = getAngle(x,y,hx,hy);
q2 = getAngle(circle[i].x,circle[i].y,hx,hy);
dx = cos( - (q1 - q2) + q2);
dy = sin( - (q1 - q2) + q2);
x = hx;
y = hy;
}
inline double
cacuT( int i){ // 计算t的值
double aa,bb,cc;
aa = dx * dx + dy * dy;
bb = 2 * (x - circle[i].x) * dx + 2 * (y - circle[i].y) * dy;
cc = sqr(x - circle[i].x) + sqr(y - circle[i].y) - sqr(circle[i].r);
return ( - bb - sqrt(bb * bb - 4 * aa * cc)) / 2 / aa;
}
inline int
findCircle(){ // 查找光线和哪个圆相遇,如果没有球相遇则放回-1否则放回球编号
int no =- 1 ,i;
double dis,tt;
t =- 1 ;
for (i = 0 ;i < n;i ++ ){
dis = distance(i);
if (dis < circle[i].r){
tt = cacuT(i);
if (tt > 0 && (tt < t || t < 0 )) {
t = tt;
no = i;
}
}
}
return no;
}
void
main(){
int fd;
fd = open( " test.txt " ,O_RDONLY);
dup2(fd, 0 );
int i,no;
readData();
for (i = 0 ;i < 10 ;i ++ )
if ((no = findCircle()) !=- 1 ){
printf( " %d " ,no + 1 );
setLine(no);
}
else {
puts( " inf " );
return ;
}
if (findCircle() ==- 1 )
puts( " info " );
else
puts( " ... " );
}