A=(aij)を4行4列の行列とするとき,
点( x,y,z )を行列演算
( x' y' z' c ) = ( x y z 1 ) |
|
により定まる点(x'/c, y'/c, z'/c)に対応させる変換を行列Aの定める射影変換という。
射影変換は,合同変換,相似変換,アフィン変換を包含する概念である。
たとえば,x軸のまわりの角θの回転は,
( |
|
) |
が定める射影変換であり,y軸のまわりの角θの回転は,
( |
|
) |
が定める射影変換である。また,ベクトル(a,b,c)が定める平行移動は
( |
|
) |
が定める射影変換である。
<Note>
Full BASICでは変換を行ベクトルの形で書く。そのため,行列Aが表す変換に続けて行列Bが表す変換を行う合成変換を表す行列はA*Bである。
DRAW文で絵定義を呼び出すとき,WITH句に4行4列の行列を指定して変形を行うことができる。
AをDIM A(4,4)で宣言された4行4列の行列とするとき,
DRAW a_pict with A を実行すると,絵定義に行列Aの定める変換が適用されて描かれる。
ただし,最終的に画面上に描画されるときには,z座標は無視される。
DRAW文のWITH句には,4行4列の行列または組込みの変換関数の任意個の積を書くことができる。積は * で表す。
組込みの変換関数は,たとえば,
ROTATE(α) = |
|
SHIFT(a,b) = |
|
のように定義されている。
そこで,y座標をx座標に,z座標をy座標に,x座標をz座標に対応させる変換を表す行列
A = |
|
と,x座標をy座標に,y座標をz座標に,z座標をx座標に対応させる変換を表す行列
B = |
|
を用意すると,x軸のまわりの角θの回転を表す行列を,A*ROTATE(θ)*Bで求めることができる。
例
次のプログラムの1000行以降に示す外部絵定義tetraは,底面をxy平面上にもち,底面の重心が原点で,点(1,0,0)に頂点をもつ正四面体(辺のみ)を描く(詳細は後述)。
このプログラムは,tetraをx軸のまわりに120°回転して描く。
100 DECLARE EXTERNAL PICTURE tetra 110 OPTION ANGLE DEGREES 120 DIM a(4,4), b(4,4) 130 MAT READ a 140 DATA 0,0,1,0 150 DATA 1,0,0,0 160 DATA 0,1,0,0 170 DATA 0,0,0,1 180 MAT READ b 190 DATA 0,1,0,0 200 DATA 0,0,1,0 210 DATA 1,0,0,0 220 DATA 0,0,0,1 230 SET WINDOW -2,2,-2,2 240 DRAW tetra WITH a*ROTATE(120)*b 250 END 260 ! 1000 EXTERNAL PICTURE tetra 1010 OPTION ANGLE DEGREES 1020 PICTURE face 1030 PLOT LINES: 1,0; COS(120),SIN(120); COS(240),SIN(240); 1,0 1040 END PICTURE 1050 DIM ry(4,4) 1060 MAT ry=IDN ! IDNは単位行列を表す 1070 LET ry(1,1)=1/3 1080 LET ry(1,3)=-SQR(8)/3 1090 LET ry(3,1)=SQR(8)/3 1100 LET ry(3,3)=1/3 1110 DRAW face 1120 DRAW face WITH SHIFT(1/2,0)*ry*SHIFT(-1/2,0) 1130 DRAW face WITH SHIFT(1/2,0)*ry*SHIFT(-1/2,0)*ROTATE(120) 1140 DRAW face WITH SHIFT(1/2,0)*ry*SHIFT(-1/2,0)*ROTATE(240) 1150 END PICTURE
(注) 100行の DECLARE EXTERNAL PICTURE 文は,(仮称)十進BASICでは省略可。規格に合わせるために記述してある。
Full BASICにはxy平面上の図形を描く描画命令しかないが,描画命令を絵定義の内側に描き,その絵定義を行列で変換すれば空間の点,線分,あるいは平面上の領域を描くことができる。
上述の例でいえば,描画命令は1020〜1040行の内部絵定義faceに存在する。faceを空間に変換して描くことで正四面体を描く絵定義になる。
上の例で,1020〜1040行の絵定義faceは,xy平面上に,重心が原点,一頂点が点(1, 0)の正三角形(底面)を描く。 正四面体の面角をαとすると,cosα=1/3,sinα=SQR(8)/3 であるので,y軸のまわりの角αの回転を表す行列
ry = |
|
を用意する。Full BASICのDATA文に計算式を書くことはできないので,1060〜1100行に示すように,一度,単位行列を代入して,単位行列と異なる成分は個別に値を与えて作成する。
側面のうちの一つは,底面を直線x=-1/2について角αの回転を行えば得られる。この変換は,直線x=-1/2がy軸に移るように図形を平行移動してy軸のまわりで回転し,再度,y軸が直線x=-1/2に移る平行移動を行えば実現できる。(1120行)
残りの2面は,この側面をz軸のまわりに120°,240°回転すれば得られる。
面を描く場合には,見える面と隠れて見えない面の識別が必要になる。
正多面体のような凸多面体を描く場合には,面に表裏を定義しておいて,面の表が手前を向いている場合にかぎり面を描画する手法が使える。
右手座標系を採用すると,z軸の正の向きは画面手前方向である(マイクロソフト社の標準は左手系のなので注意)。従って,面の表がz軸の正の向きを向いている面を描くことにすればよい。
Full BASICには絵定義実行中に現在適用される変換を表す行列を取得する行列関数TRANSFORMが用意されている。それを利用して面の正の向きを表す法線ベクトルを変換した結果を配列に取得することができる。さらに,そのベクトルとある特定の方向ベクトル(光源の向き)との内積を計算して面の明度を変えて描くことができる。
今回は,底面の1辺がx軸に平行で,頂点がz軸上にある正四面体を元に描く。
x軸上に1辺があって頂点がy軸上にある一辺の長さ2の正三角形を描く絵定義
1210 EXTERNAL PICTURE face 1260 PLOT AREA: -1,0; 1,0; 0, SQR(3) 1280 END PICTURE
を用意し,これを変換することで各面(4面)を作る。
正四面体の側面が底面となす角の余弦は1/3であるので,cosα=1/3となる角αを求め,x軸のまわりにαだけfaceを回転して描けば側面の'もと'が得られる。ここでは,頂点がz軸上にあるようにしたいので,それをさらにy軸の負の向きにSQR(3)/3だけ平行移動する。そして,それをさらに120°,240°回転したものを描けば,3側面が完成する。
底面は裏面が表になるので,faceを,一度,裏返ししてから,y軸の負の向きにSQR(3)/3だけ平行移動する。裏返しは,y軸に関する180°回転で実現する。
1000 EXTERNAL PICTURE tetra 1010 DECLARE EXTERNAL PICTURE face 1020 OPTION ANGLE DEGREES 1030 DIM rx(4,4),ry(4,4) 1040 MAT rx=IDN ! rxはx軸のまわりのACOS(1/3)の回転 1050 LET a=ACOS(1/3) ! 面角 1060 LET rx(2,2)=COS(a) 1070 LET rx(2,3)=SIN(a) 1080 LET rx(3,2)=-SIN(a) 1090 LET rx(3,3)=COS(a) 1100 MAT ry=IDN ! ryはy軸のまわりの180°回転 1110 LET ry(1,1)=-1 1120 LET ry(3,3)=-1 1130 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) ! 側面 1140 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) * ROTATE(120) 1150 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) * ROTATE(240) 1160 DRAW face WITH ry * SHIFT(0,-SQR(3)/3) ! 底面 1170 END PICTURE
合同変換(あるいは相似変換)のみを行う場合には,法線ベクトル(0,0,1)が,描かれるときにどう変わるか計算すれば,その面がどちらを向いているか判断できる。ベクトル(0,0,1)が移る先のベクトルは,原点(0,0,0)と点(0,0,1)が移る先を計算し,差を計算すれば求まる。外部絵定義faceの実行時に次の副プログラムを呼び出せば,法線ベクトルが計算できる。なお,Full BASICでは配列値をとる関数は定義できないので,副プログラムの配列引数として結果を受け取る形で記述する。
1310 EXTERNAL SUB makeNormal(N()) 1320 DIM m(4,4),A(4),B(4) 1330 MAT m=TRANSFORM 1340 MAT READ A 1350 DATA 0,0,1,1 ! 点(0,0,1) 1360 MAT A=A*M 1370 MAT A=(1/A(4))*A ! 点(0,0,1)が移る先 1380 MAT READ B 1390 DATA 0,0,0,1 ! 原点(0,0,0) 1400 MAT B=B*M 1410 MAT B=(1/B(4))*B ! 原点が移る先 1420 MAT A=A-B ! ベクトル(0,0,1)の変換結果 1430 LET N(1)=A(1) 1440 LET N(2)=A(2) 1450 LET N(3)=A(3) 1460 END SUB
1490 EXTERNAL SUB setBrightness(N()) 1500 DIM A(3) 1510 MAT READ A ! 光源の向き 1520 DATA -1,1,1 1530 LET s=DOT(A,N)/(SQR(DOT(A,A))*SQR(DOT(N,N))) 1540 LET s=(0.8*s+1)/2 1550 SET COLOR MIX(8) s,s,s 1560 SET AREA COLOR 8 1570 END SUB
面を描く絵定義のなかで上述の2つの副プログラムを呼び出す(現在変形は,変形を受ける絵定義の実行中でのみ取得できる)。 そのため,外部絵定義faceを次のように修正する。
1200 EXTERNAL PICTURE face 1210 DECLARE EXTERNAL SUB makeNormal, setBrightness 1220 DIM N(3) 1230 CALL makeNormal(N) 1240 IF N(3)>0 THEN ! 外側が手前なら面を描く 1250 CALL setBrightness(N) 1260 PLOT AREA: -1,0; 1,0; 0, SQR(3) 1270 END IF 1280 END PICTURE
<補足>
絵定義tetraのなかで法線ベクトルを計算することができないわけではない。たとえば,
1150 DRAW face WITH rx * SHIFT(0,-SQR(3)/3)
の実行で描かれる面の法線ベクトルは,
DIM m(4,4),n(3) MAT m=TRANSFORM MAT m=rx * SHIFT(0,-SQR(3)/3) * m
とすれば求まる。
我々が目で見たのと同じように立体を描くためには,近いものを大きく,遠いものを小さく描く必要がある。それを実現するのが射影変換である。
z軸上の点(0, 0, t)から点(x, y, z)を見たとき,それをxy平面上に射影すると,
点( x/(1-z/t), y/(1-z/t), 0)に移る。
この変換は,結果のz座標は無視することにすると,行列
( |
|
) |
の定める射影変換である。
射影変換では,平面の法線ベクトルが変換された平面の法線ベクトルに移るとは限らない。そこで,平面上の2つのベクトルが移る先を求め,それらの外積を求めることで平面の法線ベクトルを計算する。
100 ! 正四面体 110 DECLARE EXTERNAL PICTURE tetra 120 OPTION ANGLE DEGREES 130 LET z0=10 ! 視点 140 DIM p(4,4) ! 点 0,0,z0を中心とする射影 150 MAT p=IDN 160 LET p(3,4)=-1/z0 170 LET theta0=70 180 REM z軸のまわりにtheta0回転し, 190 REM x軸のまわりにt°回転したtetraを, 200 REM 点0, 0, 10から見たように描く。 210 SET WINDOW -2, 2, -2, 2 220 FOR t=0 TO -100 STEP -0.1 230 DIM rotx(4,4) ! x軸のまわりの回転 240 MAT rotx=IDN 250 LET rotx(2,2)=COS(t) 260 LET rotx(2,3)=SIN(t) 270 LET rotx(3,2)=-SIN(t) 280 LET rotx(3,3)=COS(t) 290 SET DRAW mode hidden 300 CLEAR 310 DRAW tetra WITH ROTATE(theta0) * rotx * p 320 SET DRAW mode explicit 330 WAIT DELAY 0.01 340 NEXT t 350 END 360 ! 980 ! 正四面体を描く 990 ! 底面をxy平面上に置き,底面の重心を原点とする。 1000 EXTERNAL PICTURE tetra 1010 DECLARE EXTERNAL PICTURE face 1020 OPTION ANGLE DEGREES 1030 DIM rx(4,4),ry(4,4) 1040 MAT rx=IDN ! rxはx軸のまわりのACOS(1/3)の回転 1050 LET a=ACOS(1/3) ! 面角 1060 LET rx(2,2)=COS(a) 1070 LET rx(2,3)=SIN(a) 1080 LET rx(3,2)=-SIN(a) 1090 LET rx(3,3)=COS(a) 1100 MAT ry=IDN ! ryはy軸のまわりの180°回転 1110 LET ry(1,1)=-1 1120 LET ry(3,3)=-1 1130 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) ! 側面 1140 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) * ROTATE(120) 1150 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) * ROTATE(240) 1160 DRAW face WITH ry * SHIFT(0,-SQR(3)/3) ! 底面 1170 END PICTURE 1180 ! 1190 ! 面を描く 1200 EXTERNAL PICTURE face 1210 DECLARE EXTERNAL SUB makeNormal, setBrightness 1220 DIM N(3) 1230 CALL makeNormal(N) 1240 IF N(3)>0 THEN ! 外側が手前なら面を描く 1250 CALL setBrightness(N) 1260 PLOT AREA: -1,0; 1,0; 0, SQR(3) 1270 END IF 1280 END PICTURE 1290 ! 1300 ! 変換された座標系における法線ベクトルを求める 1310 EXTERNAL SUB makeNormal(N()) 1320 DIM m(4,4),A(4),B(4),C(4) 1330 MAT m=TRANSFORM 1340 MAT READ A 1350 DATA 0,0,0,1 1360 MAT READ B 1370 DATA 1,0,0,1 1380 MAT READ C 1390 DATA 1,1,0,1 1400 MAT A=A*M 1410 MAT B=B*M 1420 MAT C=C*M 1430 MAT A=(1/A(4))*A 1440 MAT B=(1/B(4))*B 1450 MAT C=(1/C(4))*C 1460 MAT REDIM A(3) 1470 MAT REDIM B(3) 1480 MAT REDIM C(3) 1490 MAT A=B-A 1500 MAT B=C-B 1510 MAT N=CROSS(A,B) 1520 END SUB 1530 ! 1540 ! 法線ベクトルの向きから面の明るさを決定する 1550 EXTERNAL SUB setBrightness(N()) 1560 DIM A(3) 1570 MAT READ A ! 光源の向き 1580 DATA -1,1,1 1590 LET s=DOT(A,N)/(SQR(DOT(A,A))*SQR(DOT(N,N))) 1600 LET s=(0.8*s+1)/2 1610 SET COLOR MIX(8) s,s,s 1620 SET AREA COLOR 8 1630 END SUB
<Note>
290行と320行のSET DRAW MODE命令は十進BASICの独自拡張であるが,削除しても動作する。
また,1510行の配列値関数CROSSは外積を求める十進BASIC独自の拡張関数である。外積を計算する副プログラムに置き換えることでJIS合致プログラムにすることができる。
主プログラムを次のように変更すると,マウスを画面上で縦方向にドラッグしたときx軸のまわりに図形を回転させ,横方向にドラッグするとy軸のまわりに図形を回転させる。
ただし,480行で用いているMOUSE POLL文は(仮称)十進BASIC独自の拡張で,JIS Full BASICとの互換性はない。(JIS付属書の範囲まで許せば,単文字入力文CHARACTER INPUT NOWAITで文字キーあるいは数字キーを使うことで代用できる)
<補足>Linux,Intel MACではMOUSE POLL文は動作が緩慢で,スムーズに動作しません。
100 ! 正四面体 110 DECLARE EXTERNAL PICTURE tetra 120 OPTION ANGLE DEGREES 130 LET z0=20 ! 視点 140 DIM p(4,4) ! 点 0,0,z0を中心とする射影 150 MAT p=IDN 160 LET p(3,4)=-1/z0 170 LET theta0=45 ! z軸のまわりの回転角 180 LET t=-60 ! x軸のまわりの回転角初期値 190 LET s=0 ! y軸のまわりの回転角初期値 200 REM z軸のまわりにtheta0回転し, 210 REM x軸のまわりにt°回転, 220 REM y軸のまわりにs°回転したtetraを 230 REM 点(0, 0, z0) から見たように描く。 240 REM 画面上でマウスを横方向にドラッグすると図形をy軸のまわりに回転させ, 250 REM マウスを縦方向にドラッグすると,図形をx軸のまわりに回転させる。 260 DIM m(4,4) 270 MAT m=ROTATE(theta0) 280 SET WINDOW -2, 2, -2, 2 290 DO 300 DIM rotx(4,4) ! x軸のまわりの回転 310 MAT rotx=IDN 320 LET rotx(2,2)=COS(t) 330 LET rotx(2,3)=SIN(t) 340 LET rotx(3,2)=-SIN(t) 350 LET rotx(3,3)=COS(t) 360 DIM roty(4,4) ! y軸のまわりの回転 370 MAT roty=IDN 380 LET roty(1,1)=COS(s) 390 LET roty(1,3)=-SIN(s) 400 LET roty(3,1)=SIN(s) 410 LET roty(3,3)=COS(s) 420 MAT m=m * rotx * roty 430 SET DRAW mode hidden 440 CLEAR 450 DRAW tetra WITH m * p 460 PLOT TEXT, AT -2,-2: "マウスでドラッグすると回転。右クリックで終了" 470 SET DRAW mode explicit 480 MOUSE POLL x,y,l,r 490 IF r<>0 THEN EXIT DO 500 LET t=0 510 LET s=0 520 IF l<>0 THEN 530 LET t=-20*(y-y0) 540 LET s= 20*(x-x0) 550 END IF 560 LET x0=x 570 LET y0=y 580 LOOP 590 END
3次元描画のもうひとつの手法は,SAMPLEフォルダの3DPLOT.BASを参照してください。