[Go to “teaching” in Morimoto Lab]
描画と模様について。
マウスでお絵かきしながらアフィン変換の一部(移動、鏡映変換、回転)を用いて模様を作ります。
サンプルコードは以下にあります。
[Sample code A]
コードの解説:
mousePressedはboolでtrueかfalseが入ってます。
trueのときは、「今マウスが押されてますよ」で
falseのときは、「今マウスが押されてません」です。
mouseX, mouseYは現在のマウスの位置、
pmouseX, pmouseYは一つ前の時間のマウスの位置が入っています。
もっとマウスを極めたい:
同じ名前の関数も用意されています。
mousePressed()
こちらはsetupとかdrawみたいに関数として書くと、
マウスが押されたときだけこの関数が実行される仕組みです。 同じように
mouseClicked(),
mouseReleased(),
mouseMoved(),
mouseWheel(), があります。
※mousePressed()の上記のリンクの下部、Relatedにそれらのリンクがあります。
mouseButton
では押されたボタンが右なのか、左なのか、真ん中なのか、 を判定します。
鏡映変換(対称移動)をします。この変換は、指定した「軸」を基準に、鏡のように描画内容を反転させて複製します。軸を増やすと万華鏡みたいになります。描画だけでなく、物体やアニメーションを組み合わせたり、適用しても面白いです。
まず原点の位置を確認します。
原点は左上です。
このままで左右の鏡映変換してみましょう。
x=0を軸とすると、お絵かきした場所が(100, 200)なら、反転した位置は(-100, 200)ですね。つまり、xの値にマイナスをつけてあげればOKです。このx=0が軸というのがすべての基礎になります。
[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でしょうか?いいえ、そうすると、マウスの位置に描画されなくなります。
0: まず今描いている位置はそのままの場所に描画したいので、そのままの位置で一つは描画します。
1: 次に、もう一つの、反転する方の描画です(↓図)。0のコピーを全く同じ位置にもう一つ用意します。それを、軸にしたい位置x=width/2とコピーの位置関係が、相対的に、x=0軸に対して同じになるように移動します。
2: -1を掛けて、反転します。
3: 1で移動した分、戻します。
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,height);//軸の描画
}
void draw(){
stroke(0);
strokeWeight(12);
if(mousePressed){
float sx = mouseX;
float sy = mouseY;
point(sx, sy); //0. no change
sx = sx - off; //1. shift (-)
sx = -sx; //2. *-1
sx = sx + off; //3. shift (+)
point(sx, sy); //mirror position
}
}
今度は画面の中央を軸として、
x軸方向の鏡映変換(2.1でやったもの)
y軸方向の鏡映変換
y, y軸の両方に対する鏡映変換
の三つの変換を作り、元の描画と合わせて4か所同時に描画できるようにしてください。できたイメージは以下になります。緑と青はx,yの軸の位置を表します。
まず左右の鏡映変換をx=width/2を軸としておこなってください。
次に、左右と同じことを上下にもして、上下の鏡映変換をおこなってください。軸はy=height/2としてください。
次に、上下左右の鏡映変換をしてください。つまり、x軸で鏡映変換したものを更にy軸で鏡映変換してください。
上の図が実装例になります。
point()の代わりにline()を使って上下左右の鏡映変換を実装してもよいですね。
lineには始点と終点があります。
せっかく関数の使い方を練習してきているので、今日の課題もできるだけ関数で実装してみてください。
Ex Aの場合、例えば、左右の鏡映変換は、点a をx=width/2を軸として鏡映変換した位置を返す関数を作りそれを使用してください。
上下も同様に行うとよいでしょう。
Ex B以降では回転を行いますが、ここでは回転を行う関数を実装しましょう。
はじめは関数を使わずに実装し、そのあと処理を関数化してみるとよいと思います。
一旦鏡映変換のことは忘れて、今度は回転をしてみましょう。
左上の原点中心に、座標(x,y)をangle(単位:ラジアン)だけ回転させた位置(rotX,rotY)は以下の式で求めます。円の極座標のときは角度がx軸からになっていましたが、今回は、与えられた座標(x, y)の位置を原点中心に任意の角度だけ移動するものですので、注意してください。
【その前にx軸からの角度の式を思い出しましょう→極座標】
【極座標はDrawing&Pattern1のExAでもやりましたね!】
そして、任意の角度分だけ回転する式↓
float rotX = cos(angle) * x - sin(angle) * y;
float rotY = sin(angle) * x + cos(angle) * y;
上記の式を使用して、以下の図のようにあるように、一点を原点中心に角度θ分回転させましょう。
x, yはfloatで定義し、適当な画面内の座標にします。
回転角は10度にして、ラジアンに変換します(PIでもradiansでも計算できます)。
例えば10度離れた点の場合、360度分(36回)、コピーします。
for文を使います。
鏡映変換の時と似ていますが、(×-1)だったところを回転に変更しましょう。手順は以下の①~③です。
①原点と画面中央の位置の差分を引いた座標を求めます
②その座標を回転します
③その座標に、原点と画面中央の位置の差分を足した座標を求めます
どうしてそれでいいのかは、考えてみましょう。鏡映変換の時は任意の軸の位置の分だけずらしましたが、回転の場合は任意の中心の位置の分だけずらしましょう。「ずらす→回転や拡大縮小などの処理→戻す」というように、ずらすのと戻すのでサンドイッチするのが大事です。ちなみに(×-1)は拡大縮小として考えられます。
(width/2, height/2)が中心の回転↓
線描画の場合は以下のようになる↓
先ほどの鏡映変換では上下左右だけでしたが、回転の考え方を元に、鏡映変換も様々な分割数に対応できるプログラムを作りましょう。
<考え方>
xまたはy軸方向に、鏡映変換を一つ作り、元の描画内容と合わせて1セットと考えます。
あとは、入力した描画と上で作った1セットを、回転しながら360度分コピーすれば任意の分割数に対応した鏡映変換ができます(↓イメージ図)。分割数から、何回コピーすれば360度埋まるか計算して、その分回転してコピーします。
360度を何回分に分割するかは任意に与えましょう。出来上がりの例です↓
①マウスで描画するように実装してください
—課題としては上記でOKです。以降は余裕のある人だけトライしてみてください。—
②分割数を与えれば、その分のコピーを自動的に作るプログラムを実装しなさい
③描画位置によって色を変えるなど工夫してみましょう。
④さらに分割数を2の?乗にすれば、折り紙を中央を基準として?回対称に折ったとき、と同じ変換になります
⑤その他、工夫して面白い結果を作ってみましょう。この場合は必ずしもマウスによる描画でなくても構いません。
マウスについてくる&点以外の何かを使うとこのような感じ↓
画像などを使ってもよい。
独自に鏡映変換を応用した面白いプログラムを作りたいとき。
まず、プログラムかどうかに関わらず、興味深い模様を探しましょう。
例えば曼荼羅と言うキーワードを少しずつ工夫しつつ、画像検索します。
動画検索も面白いかもしれません。
英語にしてみてもいいでしょう。
その中でもこれはと思うものを見つけたら、どのように表現できるか挑戦してみましょう。
多分一味違うものが作れると思います。
実はこの作業がとても大事で、時間をかけるべきところかもしれません。
【参考1】【参考2】
もっときれいな線を描きたい、一考察:
今の線描画はギザギザがあって滑らかじゃないですね。
point()で描いていることが一つの理由です。
でもline()で書いても、細かい手ブレなどを拾ってしまうと結局ギザギザになります。
これを防ぐには、点と点の間があくように描画位置をサンプリングし、curve()などで描画するとよいかもしれません。
他にサンプリングした位置を滑らかになるように処理する方法もあります。
こうしてみると、手描きの線をきれいにするのはなかなか大変ですね…。
そういう意味では数式を使って描画すると、手ブレの心配などがなくなります。
手っ取り早くきれいにするには画像処理でぼかすとか、アニメーションの例のように、軌跡があまりはっきり残らないような表現にするとよいかもしれません。
参考リンク:
順番があと先になりましたが、最後にアフィン変換について、演習はありませんが、知識として知っていてもらいたいことを書きます。
アフィン変換とは、以下をまとめたものです。
・平行移動
・拡大縮小
・回転
・せん断
2Dアフィン変換は3x3の行列で計算でき、簡潔に表現できるようになるので便利です。
以下は、平行移動、拡大縮小、回転を式、2D行列式、3D行列式で表したものです。
2Dアフィン変換なのにわざわざ次元を一つあげて3Dにするのは、全て行列の積で変換可能とするためです。
この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()
上記だけでなく他にも関連する関数が豊富に用意されています。
余裕のある人は今回の内容を上記の関数で置き換えてみてもよいかと思います。
※ただし、ここまでの課題は基本、上記の関数を使わずに行ってください!
今日やったように自分で式から実装するより、簡単なはずです。楽さ(便利さ)を知ることも大事です。