[Go to “teaching” in Morimoto Lab]
描画と模様について。
マウスでお絵かきしながらアフィン変換の一部(移動、鏡映変換、回転)を用いて模様を作ります。
サンプルコードは以下にあります。
[Sample code A]
コードの解説:
mousePressedはboolでtrueかfalseが入ってます。
trueのときは、「今マウスが押されてますよ」で
falseのときは、「今マウスが押されてません」です。
もっとマウスを極めたい:
同じ名前の関数も用意されています。
mousePressed()
こちらはsetupとかdrawみたいに関数として書くと、
マウスが押されたときだけこの関数が実行される仕組みです。 同じように
mouseClicked(),
mouseReleased(),
mouseMoved(),
mouseClicked(),
mouseWheel(), があります。
※mousePressed()の上記のリンクの下部、Relatedにそれらのリンクがあります。
mouseButton
では押されたボタンが右なのか、左なのか、真ん中なのか、 を判定します。
まず鏡映変換(対称移動)をします。
指定した「軸」を基準に、鏡のように描画内容を反転させて複製します。
軸を増やすと万華鏡みたいになります。
描画だけでなく、物体やアニメーションを組み合わせたり、適用しても面白いです。
まず原点の位置を確認します。
原点は左上です。
このままで左右の鏡映変換してみましょう。
x=0が基準なので、
お絵かきした場所が(100, 200)なら
反転した位置は(-100, 200)ですね。
つまり、xの値にマイナスをつけてあげればOKです。
[Sample Code B]
void setup(){
size(300,300);
background(255);
stroke(255,0,0);//わかりやすいように基準軸を描画
strokeWeight(10);
line(0,0,0,width);//x axis
}
void draw(){
stroke(0);
strokeWeight(12);
if(mousePressed){
float sx = mouseX;
float sy = mouseY;
point(sx, sy); //use point(),
point(-sx, sy); //it's easy to understand
}
}
しかし、鏡映変換した内容は軸の左にあるので、見えません。
軸を画面の真ん中に移動してみましょう。
これは、描画内容を全て右に(画面の幅/2)だけ移動すればOKでしょうか?いいえ、
まず今描いている位置はそのままの場所に描画したいので、そのままです。
次に、描画内容を反転させるために、まずx=width/2からx=0の位置が基準になるように、その分、描画内容を移動します。
次に、-1を掛けて、反転します。
次に、②で移動した分、戻します。-100移動したのなら、ここで+100します。
[Sample Code C]
float off;//setupでもdrawでも使いたいので、ここで定義
void setup(){
size(300,300);
background(255);
off = width/2;//軸にしたい位置(x=width/2)
float xa = 0+off;
stroke(255,0,0);
strokeWeight(10);
line(xa,0,xa,width);//軸の描画
}
void draw(){
stroke(0);
strokeWeight(12);
if(mousePressed){
float sx = mouseX;
float sy = mouseY;
point(sx, sy); //1. no change
sx = sx - off; //2
sx = -sx; //3
sx = sx + off; //4
point(sx, sy); //mirror position
}
}
今度はx=0の軸ではなく、画面中央を基点として、左右の反転のみを行います。
手順のイメージは以下の図にあります。
これができたら、
「上下」「上下・左右」に関しても反転した位置に描画を行えば、上下左右の鏡映変換が作成されます。
できた時の例は以下。
左右と同じことを上下にもしてあげます。
余裕のある人はpoint()の代わりにline()を使って上下左右の鏡映変換を実装しましょう
lineには始点と終点があります。
鏡映変換のことは一旦忘れて、今度は回転をしてみます。
左上の原点中心に、座標(x,y)をangle(単位:ラジアン)だけ回転させた位置(rotX,rotY)は以下の式で求めます。
float rotX = cos(angle) * x - sin(angle) * y;
float rotY = sin(angle) * x + cos(angle) * y;
//回転の解説はここがわかりやすい↓
//http://nobutaka-saito.hatenadiary.jp/entry/2017/12/31/103054
上記のコードに記載の式を使用して、以下の図のようにあるように、
一点を原点中心に角度θ分回転させましょう。
x, yはfloatで定義し、適当な画面内の座標にします。
回転角は10度にして、ラジアンに変換します
(PIでもradiansでも計算できます)。
例えば10度離れた点の場合、360度分(36回)、コピーします。
for文を使います。
鏡映変換の時と似ています。
①次に、原点と画面中央の位置の差分を引いた座標を求めます
②その座標を回転します
③その座標に、原点と画面中央の位置の差分を足した座標を求めます
(width/2, height/2)が中心の回転↓
線描画の場合は以下のようになる↓
先ほどの鏡映変換では上下左右だけでしたが、回転の考え方を元に、鏡映変換も様々な分割数に対応できるプログラムを作りましょう。
<考え方>
xまたはy軸方向にのみ、鏡映変換を一つ作ります。
あとは、入力した描画と上で作った鏡映変換のセットを、回転のときのように、360度分コピーすれば任意の分割数に対応した鏡映変換ができます(↓イメージ図)。
360度を何回分に分割するかは任意に与えましょう。
①マウスで描画するように実装しなさい(結構難しいかも)
—課題としては上記でOKです。以降は余裕のある人だけトライしてみてください。—
②分割数を与えれば、その分のコピーを自動的に作るプログラムを実装しなさい
③描画位置によって色を変えるなど工夫してみましょう。
④さらに分割数を2の?乗にすれば、折り紙を中央を基準として?回対称に折ったとき、と同じ変換になります
マウスについてくる&点以外の何かを使うとこのような感じ↓
画像などを使ってもよい。
もっときれいな線を描きたい、一考察:
今の線描画はギザギザがあって滑らかじゃないですね。
point()で描いていることが一つの理由です。
でもline()で書いても、細かい手ブレなどを拾ってしまうと結局ギザギザになります。
これを防ぐには、点と点の間があくように描画位置をサンプリングし、curve()などで描画するとよいかもしれません。
他にサンプリングした位置を滑らかになるように処理する方法もあります。
こうしてみると、手描きの線をきれいにするのはなかなか大変ですね…。
そういう意味では数式を使って描画すると、手ブレの心配などがなくなります。
手っ取り早くきれいにするには画像処理でぼかすとか、アニメーションの例のように、軌跡があまりはっきり残らないような表現にするとよいかもしれません。
参考リンク:
順番があと先になりましたが、最後にアフィン変換について、演習はありませんが、知識として知っていてもらいたいことを書きます。
アフィン変換とは、以下をまとめたものです。
・平行移動
・拡大縮小
・回転
・せん断
2Dアフィン変換は3x3の行列で計算でき、簡潔に表現できるようになるので便利です。
以下は、平行移動、拡大縮小、回転を式、2D行列式、3D行列式で表したものです。
平行移動 ※は移動量のとき
:普通の式
:2次元行列式
:3次元行列式
拡大縮小 ※は拡大縮小の係数のとき
:普通の式
:2次元行列式
:3次元行列式
回転 ※は回転角のとき
:普通の式
:2次元行列式
:3次元行列式
上記のように、3x3の行列式だと全て積で表すことができます。
一般化(どの変換にも対応した式の形にすること)すると以下のように表せます。
2Dアフィン変換
こうすると、全ての変換を一回の乗算で処理できてわかりやすくなります。
例えば今回の課題で、「平行移動→回転→平行移動」などとしましたが、普通の式で考えるとどんどん煩雑になっていきます。
行列の積だけで表されれば、これがかなりシンプルに表現できます。
ただ、行列の計算の自力実装は結構面倒なので、ここではおすすめしないことにします。
(あとでクラスを習ったらやってみてもいいかも??)
代わりに、Processingや、その他のグラフィックスや画像処理ライブラリなどでは、アフィン変換の関数が準備されていることが多いのでそれを以下にProcessingのものをリンクしておきます。
使い方はとても簡単なので、見てみてください。
Processingのアフィン変換の関数
1. 回転 rotate(radian)
2. 平行移動 translate(x move, y move)
3. 拡大縮小 scale(x scale, y scale)
4. 有効範囲の制御 pushMatrix() & popMatrix()
上記だけでなく他にも関連する関数が豊富に用意されています。
余裕のある人は今回の内容を上記の関数で置き換えてみてもよいかと思います。
※ただし、ここまでの課題は基本、上記の関数を使わずに行ってください!