10章 花のCG
いろいろな曲線を用いて花びらや葉を描いてみる。ここでは、いくつかの基本的な関数によってどのような形状が描かれるのかを見てみることにする。
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
関数のnの値を変化させることにより、下図のように原点(0,0)と点(1,1)を通る曲線を描くことが出来る。
や
は基礎数学でもおなじみのものである。nの値が大きくなるに従って、曲線の傾きはより極端に(原点付近で傾斜が小さく、点(1,1)付近で傾斜が大きい)なっていく。下図では、n=1(直線)の場合からn=10の場合を示している。
プログラム:
(先頭部分省略)
var Form1: TForm1; function power_calc( n:integer; x:real ): real ; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var i , pw, xc, yc, n_div: integer; scale, dx, x1, y1, x2, y2 : real; begin xc := 50; //( 作図中心位置 ) yc := 250; scale := 200; //( 図の拡大倍率 ) n_div := 100; //( 曲線の分割数 ) dx := 1 / n_div; for pw := 1 to 10 do //( x1 から x10までを順に描くための繰返し ) begin x1 := 0; y1 := 0; for i := 1 to n_div do begin x2 := x1 + dx; y2 := power_calc( pw, x2 );(xn を計算する関数の呼出し) Canvas.MoveTo( xc + round(x1*scale), yc-round(y1*scale) ); Canvas.LineTo( xc + round(x2*scale), yc-round(y2*scale) ); y1 := y2; x1 := x1 + dx; end; end; end; function power_calc( n:integer; x:real ) : real;//(xnを計算する関数) var i : integer; val : real; begin val := 1; for i := 1 to n do begin val := val * x; end; power_calc := val; end; end.
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
関数によって描かれる曲線を用いて「葉」の形を描く:
左から順に、と
、
と
、
と
、そして
と
を組み合わせたものである。(
でそれぞれの2つの曲線の始点・終点が一致していることに注意。)
プログラム)以下においては、葉の形を描く処理を手続きdraw_polynomialで行なうようにした。
( 先頭部分省略 )
var Form1: TForm1; function power_calc( n:integer; x:real ): real ; procedure draw_polynomial( xa:real; ya:real; n1:integer; n2:integer; p:integer ); var xc, yc : integer; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); begin xc := 300; yc := 300; draw_polynomial( 100, 100, 2, 4, 0 ); //(曲線を描く手続きの呼出し) draw_polynomial( 100, 100, 2, 4, 1 ); end; ( 葉の形の曲線を描く手続き ) procedure draw_polynomial( xa:real; ya:real; n1:integer; n2:integer; p:integer ); var i, k, n_div, xp, yp : integer; x, y, dx, scale: real; begin n_div := 100; scale := 100; dx := 1 / n_div; for k := 1 to 2 do //(葉の内側と外側を描くための繰返し) begin x := 0; for i := 1 to n_div+1 do begin if k = 1 then y := power_calc( n1, x ) //( 葉の内側の曲線 ) else y := power_calc( n2, x ); //( 葉の外側の曲線 ) if ( p = 1 ) then xp := xc + round( -x * scale + xa ) //( 左右対象に描く ) else xp := xc + round( x*scale + xa ); yp := yc - round( y * scale + ya ); if i = 1 then Form1.Canvas.MoveTo( xp, yp ) else Form1.Canvas.LineTo( xp, yp ); x := x + dx; end; end; end; function power_calc( n:integer; x:real ) : real; var i : integer; val : real; begin val := 1; for i := 1 to n do begin val := val * x; end; power_calc := val; end; end.
実行結果:
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
補足説明: 曲線を描く手続き
上記のプログラム中の手続き
procedure draw_polynomial(xa:real;ya:real;n1:integer;n2:integer;p:integer);
の各引数の内容は次のようになっている。
xa,ya:作図開始位置
n1: 内側の曲線の次数
n2: 外側の曲線の次数
p: 作図パラメータ(p=0:そのまま描く、p=1:左右対称に描く)
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
上記のプログラムを用いて、複数の図を重ねて描くと次のような図を描くことが出来る。
作図時のパラメータ:
xc := 80; yc := 300;
draw_polynomial(100,100,4,10,0);
draw_polynomial(100,100,4,10,1);
xc := 100; yc := 300;
draw_polynomial(100,100,4,10,0);
draw_polynomial(100,100,4,10,1);
xc := 100; yc := 300;
draw_polynomial(120,100,4,10,0);
draw_polynomial(120,100,4,10,1);
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
mとnの値を変えることによって、山を左右に移動させたり、山の高さを変えたりすることが出来る。
注) 葉の形状を作るには、この関数のグラフとそれを上下に反転させたグラフを組み合わせれ
プログラム)
(先頭部分省略)
var Form1: TForm1; function power_calc2( m:integer; n:integer; x:real ) : real; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var i, m, n, xc, yc, n_div : integer; xscale, yscale, dx, x1, y1, x2, y2 : real; begin xc := 50; yc := 250; xscale := 400; yscale := 500; n_div := 100; Canvas.MoveTo( xc, yc ); Canvas.LineTo( xc + round( xscale ), yc ); dx := 1 / n_div; for m := 1 to 2 do begin for n := 1 to 2 do begin x1 := 0; y1 := 0; for i := 1 to n_div do begin x2 := x1 + dx; y2 := power_calc2( m, n, x2 ); Canvas.MoveTo( xc+round(x1*xscale), yc-round(y1*yscale)); Canvas.LineTo( xc+round(x2*xscale), yc-round(y2*yscale)); y1 := y2; x1 := x1 + dx; end; end; end; end; function power_calc2( m:integer; n:integer; x:real ) : real; var i : integer; v, w : real; begin v := 1; for i := 1 to m do begin v := v * x; end; w := 1; for i := 1 to n do begin w := w * ( 1 - x ); end; power_calc2 := v * w; end; end.
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
関数 によって2n枚の花びらを持った花の形を描くことが出来る。以下の図(左)はnが5の場合で、10枚の花びらを持っている。右の図はn=1(花びら2枚)の場合からn=5(花びら10枚)の場合までを重ねて描いたものである。
上記の関数をさらにm乗すると()、幅の狭い花びらを描くことができる。下図左はm=10の場合、下図右はm=1からm=10の場合までを重ねて描いたものである。
プログラム)
(先頭部分省略)
var Form1: TForm1; var xc, yc : integer; scale : real; function power_calc(m:integer; x:real):real; procedure draw_flower_curve( m:integer; n:integer ); implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var i : integer; begin scale := 120; xc := 200; yc := 200; for i := 1 to 10 do //(nの値を1から10まで変えながら描く) begin draw_flower_curve( i, 5 ); end; end; //(花びらの曲線を描く手続き) procedure draw_flower_curve( m:integer; n:integer ); var i, ndiv : integer; a, r, pai, angle, th, delta, x1, y1 : real; begin pi := 3.1415; a := 1; ndiv := 200; delta := 360 / ndiv; angle := 0; for i := 1 to ndiv do begin th := pai * ( angle * n ) / 180; r := abs( a * sin(th) ); //(絶対値の計算) r := power_calc( m, r ); //(半径の値をm乗する) th := pai * angle / 180; x1 := r * cos(th); //(x座標をもとめる) y1 := r * sin(th); //(y座標をもとめる) if i = 1 then Form1.Canvas.MoveTo( xc + round(scale*x1), yc - round(scale*y1) ) else Form1.Canvas.LineTo( xc + round(scale*x1), yc - round(scale*y1) ); angle := angle + delta; end; end; function power_calc(m:integer; x:real):real; var i : integer; val : real; begin val := 1; for i := 1 to m do begin val := val * x; end; power_calc := val; end; end.
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
補足説明1: 極座標
極座標系では、点の位置を原点からの距離と、基準となる軸からの角度で表現する。即ち、距離を、角度を
とすると、
これを図示すると以下のようになる。
補足説明2: 曲線を描く手続きの呼出し
上記のプログラミング例では、曲線を描くための式におけるmとnの値を指定することで、手続きdraw_flower_curveが曲線を描く。(作図中心はグローバル変数で与える)
procedure draw_flower_curve( m:integer; n:integer );
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
正弦曲線を複数周期分にわたって描き、それを与えら得た点の周囲に回転させながら配置をして、以下のような図形を得る。
プログラミング:
(先頭部分省略)
プログラミング: ( 先頭部分省略 ) var Form1: TForm1; procedure draw_wave(xa:real;ya:real;nw1:integer;nw2:integer;m:integer); var xc, yc : integer; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); begin xc := 40; yc := 300; draw_wave(100,100,0,7,50); xc := 150; yc := 300; draw_wave(100,100,1,8,30); xc := 320; yc := 300; draw_wave(100,100,3,15,150); end; ( 波型の曲線を回転させながら描く手続き ) procedure draw_wave(xa:real;ya:real;nw1:integer;nw2:integer;m:integer); var i, j, n_div,xp, yp, skip, flag : integer; y, xt, yt, th, pai, delta, th2, delta2, scale: real; begin pai := 3.1415; scale := 1; n_div := 100; delta := 2 * pai / n_div; skip := nw1 * n_div; delta2 := 2 * pai / m; th2 := 0; for j := 1 to m do begin flag := 0; th := 0; for i := 1 to nw2 * n_div do begin if i > skip then begin y := sin(th); //( sinの値の計算 ) xt := th * cos(th2) - y * sin(th2); //( 座標変換:回転 ) yt := th * sin(th2) + y * cos(th2); xp := xc + round( xa + scale*xt ); yp := yc - round( ya + scale*yt ); if flag=0 then begin Form1.Canvas.MoveTo(xp,yp); flag := 1; end else Form1.Canvas.LineTo(xp,yp); end; th := th + delta; end; th2 := th2 + delta2; end; end; end.
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
補足説明1: 曲線を描く手続きの呼出し
上記のプログラム中の手続き
procedure draw_wave(xa:real;ya:real;nw1:integer;nw2:integer;m:integer);
の各引数の内容は次のようになっている。
xa,ya: 回転の中心座標
nw1: 作図を省く区間(最初の何周期分かを指定)
nw2: 全作図区間(何周期分かを指定)
m: 描く曲線の本数
確認:前掲の出力図は次のような引数を与えて描いた。図形との対応を確かめよ。
draw_wave(100,100,0,7,50);
draw_wave(100,100,1,8,30);
draw_wave(100,100,3,15,150);
参考)前出の出力図で、描く曲線の本数を8とすると次のような図が得られる。
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
関数を用いると、2n枚の花びらの各々においてさらに変化のある花の形を描くことが出来る。以下の図で、左はn=4,a=1,b=0.3,c=0つまり、
の場合、右はn=4, a=1, b=0.5, c=0.3、つまり
の場合である。
プログラム)
(先頭部分省略)
var Form1: TForm1; procedure draw_flower_curve2( n:integer; a:real; b:real; c:real ); var xc, yc : integer; scale : real; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); begin xc := 200; yc := 200; scale := 100; draw_flower_curve2( 4, 1, 0.5, 0.3 ); end; //( sin曲線を重ね合わせて花びらの形を描く手続き ) procedure draw_flower_curve2( n:integer; a:real; b:real; c:real ); var i, n_div: integer; r, x, y, pai, th, delta : real; begin pai := 3.1415; n_div := 300; delta := 2*pai / n_div; th := 0; for i := 1 to n_div + 1 do begin r := abs(a*sin(n*th)+b*sin(3*n*th)+c*sin(5*n*th)); x := r * cos(th); y := r * sin(th); if i = 1 then Form1.Canvas.MoveTo(xc+round(scale*x),yc-round(scale*y)) else Form1.Canvas.LineTo(xc+round(scale*x),yc-round(scale*y)); th := th + delta; end; end; end.
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
補足説明1: 手続きの呼出し
曲線を描く手続きは次のようになっている。引数は、n,a,b,cそれぞれの値である。(作図中心はグローバル変数を用いて指定している。)
procedure draw_flower_curve2( n:integer; a:real; b:real; c:real );
補足説明2: 正弦曲線の重ね合せ
上述の図は極座標系で描いたものであるが、出力図の右側の図を直交座標系で描くと以下のようになる。上から順に、
(1)
(2)
(3)
(4)
である。(1)から(2)までの3個の曲線を重ね合せると、(4)の曲線になることを図の上で確かめることが出来る。
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
冒頭でも記したように、関数のグラフにおいてm,nの値を変えることによって、山を左右に移動させたり、山の高さを変えたりすることが出来る。
葉の形状を作るには、上図の関数のグラフ(例えば、)とそれを上下に反転させたものを組み合わせればよい。加えて、この形状をさらにリアルにするにはどうすれば良いのであろうか?ここでは、次の2つのことを行う。
(1)葉の輪郭にギザギザをつける
(2) 葉の葉脈(的なもの)を描く
(3)葉を任意の位置に傾けて描く
その1: 葉にギザギザをつける
出力例:
説明 )
以下の図で示す形状は、(1)上から順にの曲線、(2)ギザギザを付けるための関数(gとする)によって描かれる形状、(3)前述の(1)の関数と(2)の関数の和の形状(即ち
)、(4)
によって描かれる形状である。
式によって与えられる形状は、図からもわかるように、ギザギザが一様で、葉の形としては不自然である。そこで、葉の先端に近づくに従って、ギザギザが小さくなるように、式
を用いた。
ギザギザのもとになる、上図の2番目の形状は次のようにして描かれている。即ち、ある一定区間、関数に従って、yの値が増加した後、yの値を0に減少させる。この操作を周期的に繰り返すと、ギザギザをの形状を得ることが出来る。
その2: 葉に葉脈をつける
出力例:
説明 )
下図左のように、葉のギザギザの外形、葉の中心の線は、区間dxごとに順次描いていく。葉脈の線は、外形が変化する時点で、外形を描く線分の開始点と、中心上の1点(外形の線分の位置から一定距離だけ戻った位置との間で線分を描く(下図右)。
その3: 葉の回転と移動を行なう
枝の先に葉を描きたいような場合には、前述の葉を任意の向きに傾ける(回転させる)ことが必要になる。これまでにも記したように、原点回りの回転は以下の式を用いて行うことが出来る。
原点回りに回転した後に、並行移動を行う座標変換の式は、移動先の点の座標をとすると、次のように与えられる。
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
プログラム:
(先頭部分省略)
var Form1: TForm1; procedure draw_leaf(xm:real; ym:real; alfa:real; scale:real ); var xc, yc : integer; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); begin xc := 200; yc := 400; draw_leaf(0,0,30,200); draw_leaf(100,100,90,100); draw_leaf(100,100,180,50); draw_leaf(200,200,0,200); draw_leaf(200,200,45,100); draw_leaf(200,200,135,50); end; //( 葉の形状を任意の位置に傾けて描く手続き ) procedure draw_leaf(xm:real; ym:real; alfa:real; scale:real ); var i, k, n_div, count, interval, xs1, xs2, xs3, ys1, ys2, ys3, x1, y1, x2, y2 : integer; x, y, fx1, fx2, sf, dx, th, pai, back : real; begin pai := 3.1415; n_div := 100; interval := 10; dx := 1 / n_div; sf := 3; back := 7; //( 葉脈を描くために戻る区間個数の指定) th := pai * alfa / 180; for k := 1 to 2 do //( 葉の形状の上半分と下半分を描くための繰返し ) begin x := 0; x1 := round( xm ); //( 作図開始位置の初期化:葉の輪郭線 ) y1 := round( ym ); xs1 := round( xm ); //( 作図開始位置の初期化:葉の中心線 ) ys1 := round( ym ); count := 0; for i := 1 to n_div do begin x := x + dx; if count mod interval = 0 then fx2 := 0 //( ギザギザの高さを0にする ) else fx2 := fx2 + dx; //( ギザギザの高さを増加させる ) fx1 := x * (1-x) * (1-x); y := fx1 + sf * fx1 * fx2; if k = 2 then y := -y; //( 下半分を描くためにyの値を負に反転する ) x2 := round( scale * ( x * cos(th) - y * sin(th) ) + xm ); //( 座標変換 ) y2 := round( scale * ( x * sin(th) + y * cos(th) ) + ym ); xs2 := round( scale * ( x * cos(th) ) + xm ); ys2 := round( scale * ( x * sin(th) ) + ym ); Form1.Canvas.MoveTo( xc+x1,yc-y1 ); //( 葉の輪郭線作図 ) Form1.Canvas.LineTo( xc+x2,yc-y2 ); Form1.Canvas.MoveTo( xc+xs1,yc-ys1 );//( 葉の中心線作図 ) Form1.Canvas.LineTo( xc+xs2,yc-ys2 ); xs3 := round( scale * ( ( x - back * dx ) * cos(th) ) + xm ); //(葉脈の中心線作図点 ) ys3 := round( scale * ( ( x - back * dx ) * sin(th) ) + ym ); if (count <> 0 ) and ( count mod interval = 0 ) then //( 葉脈作図 ) begin Form1.Canvas.MoveTo(xc+xs3,yc-ys3); Form1.Canvas.LineTo(xc+x1,yc-y1); end; x1 := x2; y1 := y2; xs1 := xs2; ys1 := ys2; count := count + 1; end; end; end; end.
作図結果:
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
補足1)手続きdraw_leafは4つの変数の値を指定すると、それに従って葉の形を描く。
procedure draw_leaf(xm:real; ym:real; alfa:real; scale:real );
xm, ym : 葉元の移動先の位置
alfa : 葉の傾き角度
scale : 葉の大きさの拡大倍率 (葉の形状のもともとの長さは1であることに注意!)