Imaginantia

思ったことを書きます

具体的すぎてわかりにくい

例えば2次元図形で。

左上 (a,b) 右下 (c,d) で定義される矩形がある。 これを左上 (e,f) 右下 (g,h) にするような平行移動と拡大縮小のパラメータを計算したいとする。

前提から x' = f(x) = \alpha x + \beta, y' = g(y) = \gamma y + \delta の4つのパラメータを計算すればいいことはすぐわかる。 でもそれを具体的に計算するには方程式を解くしかない。ちょっとめんどくさい。

そこで、平行移動と拡大縮小しかないという条件から、一度矩形を (0,0){-}(1,1) を基準に考えてみる。これは情報を失わない。

  • (0,0){-}(1,1)(a,b){-}(c,d) に持っていくには (\alpha,\beta,\gamma,\delta) = (a,c-a,b,d-b) で良い。
  • (0,0){-}(1,1)(e,f){-}(g,h) に持っていくには (\alpha,\beta,\gamma,\delta) = (e,g-e,f,h-f) で良い。

そして x' = \alpha x + \beta は容易に逆変換を構成でき、\displaystyle x = (x' - \beta) / \alpha = \frac{1}{\alpha} x' - \frac{\beta}{\alpha} となる。 よって、(a,b){-}(c,d) \longrightarrow (0,0){-}(1,1) \longrightarrow (e,f){-}(g,h) という経路を使って、最初に逆変換・次に順変換を通せば確かに求める変換が構成できるはずである。というのは今その変換を具体的には知らないから。

だけどこれをプログラムに落とすことは容易にできる。

struct Transform {
  float alpha, beta;
  float gamma, delta;
};

Transform inverse(Transform t){
  return Transform {
    1/t.alpha, -t.beta/t.alpha,
    1/t.gamma, -t.delta/t.gamma
  };
}

Transform compose(Transform a, Transform b){
  return Transform {
    a.alpha*b.alpha, a.beta*b.alpha+b.beta,
    a.gamma*b.gamma, a.delta*b.gamma+b.delta
  };
}

Transform rect(float left, float top, float right, float bottom){
  return Transform {
    left, right-left,
    top, bottom-top
  };
}

void apply(Transform t, float &x, float &y){
  x = t.alpha * x + t.beta;
  y = t.gamma * y + t.delta;
}

Transform start = rect(a,b,c,d);
Transform end = rect(e,f,g,h);
Transform trans = compose(end,inverse(start));

このプログラムでは、変換を具体的に保持する。通常1つの式で書いていた変換式は、もはやオブジェクトとして管理することができるようになった。 そしてこれは効率を失わない。何故なら変換は一度計算すればそれ以降決まったTransformを使いまわせるため、わざわざ逆変換を考える必要もない。 何も問題は起きない。


要は具体的にプログラムに式を書く必要はもはやなくて、こういう構造を作ることができれば十分という話。線形変換なら実用性ありそう。 「方程式を解く」みたいなシチュエーション結構あるので、わざわざ手で解いてよくわからない式を書き並べるよりいいとおもう。間違えないし。