・ 変換マトリックスで座標変換入門 その2(マトリックスの合成と逆行列)

座標変換入門 その1では、変換マトリックスは、変換先の座標系を表していると考えることができるということがわかりました
その2では、実際の変換プログラムを作っていきます

座標変換が必要な訳

そもそも、こんな面倒な座標変換なんてする必要があるのだろうか?
移動や回転・尺度変更なんて、それぞれコマンドが用意されている
WCS・UCS・DCS・OCS 座標間での変換は (trans) 関数で変換できる
マニュアルによると、(trans) 関数よりマトリックス変換(アフィン変換)のほうが高速らしいけど・・・
でも、それって、プログラムの書き方にもよるだろうに・・・?

ただ、INSERT図形にネストされたオブジェクトの座標系(MCS)とWCSの間の変換はちょっと厄介です
特に、ブロックが何重にもネストされていたりするとなおのこと
WCSからMCSへの変換は、ブロック定義から、挿入点・縮尺・回転のデータを拾い出し順番にMOVE・SCALE・ROTATEを繰り返していけば可能です
でも、MCSからWCSへの変換はコマンドではできません (とりあえずσ(^^)には思いつかない・・・)

そこで、マトリックス変換(アフィン変換)の登場となります
MCSからWCSへの変換マトリックスは(nentselp)が返すリストの3番目の要素として提供されます
WCSからMCSへの変換マトリックスはブロック定義から、挿入点・縮尺・回転のデータを拾い出し順番に合成していくことで取得します
(nentselp)で取得できる変換マトリックスは、選択したオブジェクトの中でネストの一番深いオブジェクトをWCSに変換するためのもので、その中間のINSERT図形の変換マトリックスは提供させません
これは、WCSからMCSへの変換マトリックスを取得して、その逆行列を計算することで、はじめて取得できることになります

ちなみに(nentsel)も変換マトリックスを返しますが4行4列ではなく4行3列なので注意が必要です

マトリックスの合成

マトリックスの合成とは、マトリックスの外積を計算することだそうです
マトリックスnにマトリックスmを合成するには

これをLispで書くと

  ;マトリックスの合成(外積)mat1にmat2を合成する;
  (defun Jof_mat_outp (mat2 mat1 / i n c temp ret)
    (setq i 0)
    (setq n (length (car mat1)))
    (repeat n
      (setq c 0)
      (setq temp nil)
      (repeat n
	(setq temp (append temp (list (apply '+ (mapcar '* (nth i mat2) (mapcar '(lambda (x) (nth c x)) mat1))))))
	(setq c (1+ c))
	)
      (setq ret (append ret (list temp)))
      (setq i (1+ i))
      )
    ret
    )
マトリックスの合成を考える上で重要なことは、合成する順番があるということです
INSERT図形のMCSへの変換マトリックスを取得する場合、
縮尺->回転->移動
の順番に合成します
さらに、押出し方向が違う場合には、最後にOSC変換のマトリックスも合成です

元に戻すマトリックスを求める

ある変換マトリックスAに合成したら、

( (1.0  0.0  0.0  0.0)
  (0.0  1.0  0.0  0.0 )
  (0.0  0.0  1.0  0.0 )
   (0.0  0.0  0.0  1.0) )

になるようなマトリックスinvAを求めるということです
マトリックスAで変換して、さらにマトリックスinvAで変換したら元に戻る、つまり何もしなかったってこと
そんなinvAはどうやって求めるのかというと・・・・
もう一度、変換マトリックスの意味を思い出してください

移動のマトリックスを元に戻すには、m03 m13 m23 をそれぞれ-1倍してあげればよさそうです
縮尺のマトリックスを元に戻すには、XYZ軸方向のベクトル長さの逆数を求めれば良いわけです
そして、回転はというと、回転のマトリックスの行と列を入れ替えればよいのです

これを、移動→回転→縮尺の順に合成すれば元に戻すマトリックスの完成です
Lispで書くと

  ;逆行列を求める  Jof_inv_mat 
 ;*******************************************************************

  ;ベクトルの長さを返す     
  (defun Jof_v_len (
		    v;ベクトル;
		    / )
    (sqrt (apply '+ (mapcar '(lambda (x) (expt x 2)) v)))
    )

 ;正方行列の行と列を入れ替え ;
  (defun Jof_xch_mat ( mat / ret )
    (setq n 0)
    (repeat (length mat)
      (setq ret (append ret (list (mapcar '(lambda (x) (nth n x)) mat))))
      (setq n (1+ n))
      )
    ret
    )
    
  ;移動のマトリックス;
  (defun Jof_move_mat ( v_xyz / )
    (list
      (list 1.0 0   0   (car v_xyz)  )
      (list 0   1.0 0   (cadr v_xyz) )
      (list 0   0   1.0 (caddr v_xyz))
      (list 0   0   0   1.0          )
      )
    )

  ;スケールのマトリックス;
  (defun Jof_scale_mat ( s_xyz / )
    (list
      (list (car s_xyz) 0            0             0  )
      (list 0           (cadr s_xyz) 0             0  )
      (list 0           0            (caddr s_xyz) 0  )
      (list 0           0            0             1.0)
      )
    )

  ;Z軸回転のマトリックス;
  (defun Jof_zrot_mat (ang / )
    (list
      (list (cos ang) (- (sin ang)) 0   0  )
      (list (sin ang) (cos ang)     0   0  )
      (list 0         0             1.0 0  )
      (list 0         0             0   1.0)
      )
    )

  (defun Jof_inv_mat ( mat / )
    (setq mat (Jof_xch_mat mat))
    (setq inv_move (Jof_move_mat (mapcar '- (reverse (cdr (reverse (last mat)))))))
    (setq mat (reverse (cdr (reverse mat))))
    (setq lst_v_len (mapcar '(lambda (x) (Jof_v_len (reverse (cdr (reverse x))))) mat))
    (setq inv_scale (Jof_scale_mat (mapcar '(lambda (x) (/ 1.0 x)) lst_v_len)))
    (setq mat (list (car mat) (cadr mat) (caddr mat) '(0.0 0.0 0.0 1.0)))
    (setq inv_zrot (Jof_mat_outp inv_scale mat))
    (Jof_mat_outp inv_scale (Jof_mat_outp inv_zrot inv_move))
    )