複素数による平面幾何プログラミング

ICPCが近いので、ライブラリを作らないといかん…。
というわけで、平面幾何。
平面幾何を扱うのにcomplexを使うのはもはや常識(?)
なのかもしれないが、そこをちょっとおさらいしてみることにする。
最初このテクニックを見たとき、
自分でstruct point{ int x,y; }; とか、
演算子の定義とかを書く必要がないのか、と思ったのだが
私の考えが浅かった。複素数で点を扱うと、
複素数平面上で平面幾何を扱うことになり、
便利な関数がたくさん利用できるということになる。
さらに、回転が簡単にかけたりするので、問題を回転させて
より簡単な問題に帰着したりできるかもしれない。

  • 加算、スカラ倍
typedef complex<double> pt;

pt x,y;
pt z=x+y;
double c;
pt w=c*z;

このようなことが何もせずに使える。

  • 二点間の距離
abs(x-y);
  • ベクトルのなす角

ベクトルvのx軸となす角は、

arg(v);

ベクトルuとvのなす角は

arg(u/v);

と書ける。


ベクトルuをベクトルvに移す写像fを

pt f=polar(abs(u)/abs(v),arg(u/v));

簡潔に書ける。

ベクトルu,vの内積

real(u*conj(v));

で求めることができる。
(成分を計算することにより簡単に確かめられる)

ベクトルu,vに対して行列[u,v]の行列式

imag(conj(u)*v);

で求めることができる。
(同上)

  • 三角形の符号付面積
double area(pt a,pt b,pt c)
{
  return imag(conj(b-a)*(c-a))*0.5;
}

点aを原点に平行移動させて、行列式より計算できる。

  • 二直線の交点

直線 a+su,b+tv (a,b,u,v はベクトル、s,tはスカラ変数)
の交点。(s,tを返す)

pair<double,double> cross_lines(pt a,pt u,pt b,pt v)
{
  double det=imag(conj(u)*v);
  if (abs(det)<epsilon) throw "交差しない";
  return make_pair(
    -imag(conj(v)*(b-a))/det,
    -imag(conj(u)*(b-a))/det);
}

証明は…成分を計算すると確かに正しいのだが、
式の意味するところは良く分からんです…。
交差しないときに例外を投げてしまっているが、
これはあんまり良くないので、実際に使うときは
うまいインターフェースを考えよう。

  • 円と直線の交点

直線 x=a+su と円 |x-c|=r の交点を求める。
(sを二つ返す)

pair<double,double> cross_circle_and_line(pt a,pt u,pt c,double r)
{
  pt ac=a-c;
  double b=real(u*conj(ac));
  double d=b*b-norm(u)*(norm(ac)-r*r);
  if (d<0) throw "交差しない";
  return make_pair(
    (-b+sqrt(d))/norm(u),
    (-b-sqrt(d))/norm(u));
}

単純に直線と円の式を解くと上の式は得られる。

  • 二円の交点

円 |x-c|=r と |x-d|=s の交点を求める。

pair<pt,pt> cross_circles(pt c,double r,pt d,double s)
{
  double di=abs(c-d);
  if (di>r+s || di<abs(r-s))
    throw "交差しない";
  double l=((r*r-s*s)/di+di)/2.0;
  double n=sqrt(r*r-l*l);
  pt e=c+(d-c)*l/di;
  pt p=(d-c)*n/di*pt(0,-1);
  return make_pair(e+p,e-p);
}

二つの円の交点と、二つの円の中心のなす凧形を考えれば
式が導出できるが、ややこしいので省略。
複素数ならではなことはできていないが、
短くする効果は十分だろう。