直線の式のパラメーター表示とコンピュータグラフィックスのC言語プログラム

教育用の覚書。グラフィックス座標とワールド座標の変換についてのラフな説明。

問題: y=x2-2x-2 のグラフを -1≦x≦3 の範囲で描きなさい。

数学的知識をプログラミングに応用するときの要点

  1. [出発点となる基礎知識] 始点 A(x0,y0), 終点 B(x1,y1) をつなぐ線分上の点の座標 P(X,Y) を表す式は
    \left(\begin{array}{c}X\\Y\end{array}\right)=(1-t)\left(\begin{array}{c}x_{0}\\y_{0}\end{array}\right) + t \left(\begin{array}{c}x_{1}\\y_{1}\end{array}\right) ただし 0\leq t \leq 1
    と必ず書ける。理由は「t=0を代入するとAの座標になる」「t=1を代入するとBの座標になる」「Pの座標はtの1次関数である」からである。
  2. [tを作る] X が区間 X0 ≦ X ≦ X1 にあるとき、t:=\frac{X-X_{0}}{X_{1}-X{0}} は 「X0のとき t=0」「X1のとき t=1」となる1次関数である。
  3. だからグラフィック座標が Imim ≦ i ≦ Imax でワールド座標(数学的な値の座標)が Xmin ≦ x ≦ Xmax ならば、「グラフィック座標を数学座標に変換する計算」は
    1. t:=\frac{\rm{i-Imin}}{\rm{Imax-Imin}}を計算する
    2. この t を使って x = (1-t) Xmin + t Xmax を計算する
    すると i=Imin のとき x=Xmin, i=Imax のとき x=Xmax となる関数を作ることができる。ここでCプログラミングを考えると 1 の t の式を 2 の x の式に代入したややこしい式をわざわざ作る必要はない
  4. この考え方は「数学座標をグラフィックス座標に変換する計算」にも使えて
    1. t:=\frac{\rm{y-Ymin}}{\rm{Ymax-Ymin}}を計算する
    2. この t を使って j = (1-t) Jmax + t Jmin を計算する
    すると y=Ymin のとき j=Jmax, y=Ymax のとき j=Jmin となる関数を作ることができる。
  5. 区間の両端, Imin と Imax, Xmin と Xmax の値の対応さえきちんとしていれば、途中も大丈夫。

コードを書く上での要点

  1. 数学的な座標(x,y)の数値とCG的な座標の数値(i,j)の変換は関数で作っておいて、変換が必要なときは関数の呼び出しで済ませること。目的はプログラムを書くときに、ピクセル座標と数学座標の変換をいちいち考えないため。
  2. プログラム上で固定されるパラメーターは定数として宣言しておき、定数名を使うこと。目的はピクセル座標や数学座標のサイズ変更を容易にするため。

教育用サンプルコード(教育用なので1ピクセル程度の表示の狂いが出ることもある。*1

#include <[何か描画用].h>

// ピクセル座標(i,j)の数値の最大値と最小値の設定
#define Imin (0)
#define Imax (800)
#define Jmin (0)
#define Jmax (800)
// 数学座標(x,y)の数値の最大値と最小値の設定
#define Xmin (-1.0)
#define Xmax (3.0)
#define Ymin (-1.0)
#define Ymax (1.0)

// x方向のピクセル座標を数学座標にする関数
double doubleX (int I) {
  double t=(((double)(I-Imin))/((double)(Imax-Imin)));
  double x= (1.0-t)*Xmin + t*Xmax;
  return x;
}

// y方向の数学座標をピクセル座標にする関数
int pixelY (double Y) {
  double t=(Y-Ymin)/(Ymax-Ymin);
  double y= (1.0-t)*Jmax + t*Jmin;
  return (int) y;
}

// 描きたいグラフを数学座標の値で計算する関数
double F (double X) {
  return x*x-2*x-2; // このサンプルは2次関数 f(x)=x^2-2x-2
}

// エントリポイント関数
int main (void) {
  int i,j;
 double x,y;

  // ピクセル座標でのx方向の for ループ
  for ( i=Imin ; i<=Imax ; i++ ) {

    // 関数のグラフ(x,y)の計算
    x= doubleX(i); // ピクセル座標 i の数学座標 x への変換
    y= F(x);       // 数学座標 x のときの y の値の計算

  // 描画
    j= pixelY(y); // 数学座標 y のピクセル座標 j への変換
    [描画用関数]( i , j );
  }
  return 0;
}

書き出してみると案外と長くなるものだな。

*1:「801ピクセルある」というツッコミは今は無しの方向で…。