SGU 110 Dungeon

题意:http://hi.baidu.com/hpfdf/item/4bc71df5cc9f3dc4a835a2a9

纯粹的数学题。初始一看非常麻烦,但是仔细思考后,其实并不复杂。需要注意一些细节。

1、用对称式的空间直线方程(一个点,一个空间向量)来表示入射光线,然后与球的方程联立方程组,求出比例系数t1,t2,再由系数求出直线与球的交点。需要注意的是:如果表示入射光线的点为起点的话,这里的系数应当是正数。另外如果系数是0的话,那么求出交点就是起点(或者说是上一个交点),直接用<=0判断的话会有误差(WA在第4组数据了),于是需要用一个变量pre来记录之前的交点。

2、由于入射直线可能与多个球体有交点,这里需要求出所有交点中,距离起点最近的那个。

3、求出交点后,便可由球心与交点求出法线向量,然后由入射向量和法线向量求出反射向量。

对于求反射向量,可参考:http://www.cnblogs.com/graphics/archive/2013/02/21/2920627.html



#include<cstdio>
#include<cmath>
#define eps 1e-10
using namespace std;
#define maxn 55
struct Point
{
    double x,y,z;
    Point (double xx,double yy,double zz) {x=xx;y=yy;z=zz;}
    Point (){};
    void read() {scanf("%lf%lf%lf",&x,&y,&z);}
};
struct Ball
{
    Point C;
    double r;
    void read()
    {
        C.read();
        scanf("%lf",&r);
    }
}ball[maxn];
int n,pre;
int cmp(double a)
{
    if(fabs(a)<eps) return 0;
    return a>0?1:-1;
}
Point add(Point a,Point b) {return Point(a.x+b.x,a.y+b.y,a.z+b.z);}
Point dec(Point a,Point b) {return Point(a.x-b.x,a.y-b.y,a.z-b.z);}
double mul(Point a,Point b) {return a.x*b.x+a.y*b.y+a.z*b.z;}
double dis(Point a,Point b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));}
double dis(Point a){return sqrt(a.x*a.x+a.y*a.y+a.z*a.z);}
void get(double &a,double &b,double &c,Ball B,Point L,Point S)  //联立球与直线方程,得到一元二次方程系数
{
    a=L.x*L.x+L.y*L.y+L.z*L.z;
    b=2*((S.x-B.C.x)*L.x+(S.y-B.C.y)*L.y+(S.z-B.C.z)*L.z);
    c=((S.x-B.C.x)*(S.x-B.C.x)+(S.y-B.C.y)*(S.y-B.C.y)+(S.z-B.C.z)*(S.z-B.C.z)-B.r*B.r);
}
bool solve(double a,double b,double c,double &x1,double &x2)    //求解一元二次方程
{
    if(cmp(b*b-4*a*c)<0) return 0;
    x1=(-b+sqrt(b*b-4*a*c))/(a+a);
    x2=(-b-sqrt(b*b-4*a*c))/(a+a);
    if(x1<=0&&x2<=0) return 0;        //由于这里以S为基准点,所求出的点应位于S->E方向,故求出的比例系数必须为正
    return 1;
}
Point getline(Point a,Point b) {return dec(b,a);}   //由两点得到直线向量
Point getrline(Point l,Point fl)//获取反射直线方程
{
    double k=2*mul(l,fl)/(dis(fl)*dis(fl));
    return dec(l,Point(fl.x*k,fl.y*k,fl.z*k));//反射向量,根据公式求出
}
int GetID(Point &l,Point &S) //求与入射直线相交的最近的球,参数为入射直线向量与入射直线上的一个点
{
    Point p,p1,p2;
    double a,b,c,t1,t2,d1,d2,Min=1e5;
    int id=0;
    for(int i=1;i<=n;++i)       //求距离S最近的那个交点
    {
        if(i==pre) continue;
        get(a,b,c,ball[i],l,S); //联立
        if(!solve(a,b,c,t1,t2)) continue;   //求解

        p1=Point(S.x+t1*l.x,S.y+t1*l.y,S.z+t1*l.z);
        p2=Point(S.x+t2*l.x,S.y+t2*l.y,S.z+t2*l.z);
        d1=dis(S,p1);
        d2=dis(S,p2);
        if(t2>0&&cmp(d1-d2)>=0)
        {
            d1=d2;
            p1=p2;
        }
        if(cmp(Min-d1)>0)
        {
            id=i;
            Min=d1;
            p=p1;
        }
    }
    if(!id) return 0;
    pre=id;
    Point fl=getline(ball[id].C,p);//法线向量
    Point rl=getrline(l,fl);
    l=rl;
    S=p;
    return id;
}
int main()
{
    Point S,E,l;
    int i,id;
    scanf("%d",&n);
    for(i=1;i<=n;++i) ball[i].read();
    S.read(),E.read();
    l=getline(S,E);
    pre=-1;
    for(i=1;;++i)
    {
        id=GetID(l,S);
        if(!id) break;
        if(i==11) {printf("etc.");break;}
        printf("%d ",id);
    }
    puts("");
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值