はじめに
p5のrotate関数にはrotateX, rotateY, rotateZの他にrotateという関数があります。
軸を指定するので若干指定難易度が高いんですが、実は繰り返し先ほどのrotateX,Y,Zを実行した結果を、まとめて実行できるポテンシャルを持っています。それを説明しましょう。そのためにp5.Quatを使いたかったんですが、実は計算がバグっています。
/**
* Multiplies a quaternion with other quaternion.
* @method mult
* @param {p5.Quat} [quat] quaternion to multiply with the quaternion calling the method.
* @chainable
*/
multiply(quat) {
return new Quat(
this.w * quat.w - this.vec.x * quat.vec.x - this.vec.y * quat.vec.y - this.vec.z - quat.vec.z,
this.w * quat.vec.x + this.vec.x * quat.w + this.vec.y * quat.vec.z - this.vec.z * quat.vec.y,
this.w * quat.vec.y - this.vec.x * quat.vec.z + this.vec.y * quat.w + this.vec.z * quat.vec.x,
this.w * quat.vec.z + this.vec.x * quat.vec.y - this.vec.y * quat.vec.x + this.vec.z * quat.w
);
}
わかるでしょうか。w 成分(最初の成分)ですが、4つ目の項が引き算になっています。それゆえw 成分だけ正確に出ません。なので今回紹介するコードでは次のパッチを使います。
p5.Quat.prototype.multiply = function(quat){
return new p5.Quat(
this.w * quat.w - this.vec.x * quat.vec.x - this.vec.y * quat.vec.y - this.vec.z * quat.vec.z,
this.w * quat.vec.x + this.vec.x * quat.w + this.vec.y * quat.vec.z - this.vec.z * quat.vec.y,
this.w * quat.vec.y - this.vec.x * quat.vec.z + this.vec.y * quat.w + this.vec.z * quat.vec.x,
this.w * quat.vec.z + this.vec.x * quat.vec.y - this.vec.y * quat.vec.x + this.vec.z * quat.w
);
}
これで問題なく運用できます。では始めましょう。
コード全文
function setup() {
createCanvas(400, 400, WEBGL);
const qx = p5.Quat.fromAxisAngle(0.7, 1, 0, 0);
const qy = p5.Quat.fromAxisAngle(0.3, 0, 1, 0);
const qz = p5.Quat.fromAxisAngle(1.1, 0, 0, 1);
const q = qx.multiply(qy).multiply(qz);
const theta = Math.atan2(q.vec.mag(), q.w);
background(0);
rotateX(0.7);
rotateY(0.3);
rotateZ(1.1);
//rotate(theta*2, q.vec);
noStroke();
pointLight(99,200,255,-10,40,360);
fill(255);
box(200);
}
p5.Quat.prototype.multiply = function(quat){
return new p5.Quat(
this.w * quat.w - this.vec.x * quat.vec.x - this.vec.y * quat.vec.y - this.vec.z * quat.vec.z,
this.w * quat.vec.x + this.vec.x * quat.w + this.vec.y * quat.vec.z - this.vec.z * quat.vec.y,
this.w * quat.vec.y - this.vec.x * quat.vec.z + this.vec.y * quat.w + this.vec.z * quat.vec.x,
this.w * quat.vec.z + this.vec.x * quat.vec.y - this.vec.y * quat.vec.x + this.vec.z * quat.w
);
}
rotateX,Y,Zを使って描画する
まず、rotateX,Y,Zで回転させます。この場合、次のようなコードになりますね。
function setup() {
createCanvas(400, 400, WEBGL);
background(0);
rotateX(0.7);
rotateY(0.3);
rotateZ(1.1);
noStroke();
pointLight(99,200,255,-10,40,360);
fill(255);
box(200);
}
Xで0.7, Yで0.3, Zで1.1です。このように回転させると上のような見た目になります。これをrotate一本でやろうってわけです。そこでクォータニオンの登場です。まず作るためのメソッドとしてfromAxisAngleというのがあるんでそれ使いましょう。
const qx = p5.Quat.fromAxisAngle(0.7, 1, 0, 0);
const qy = p5.Quat.fromAxisAngle(0.3, 0, 1, 0);
const qz = p5.Quat.fromAxisAngle(1.1, 0, 0, 1);
最初に角度、次いで軸成分 x, y, z を指定します。上の回転に対応しているものです。次いでこれらを順に掛け算します。この掛け算はいわゆるイミュータブルなので常に新しいものが返ります。
const q = qx.multiply(qy).multiply(qz);
そこから軸と角度を抽出するんですが、角度に関しては半分の値が返ります。取り方は次の式です。まあ詳しいことは置いておきましょう。
const theta = Math.atan2(q.vec.mag(), q.w);
そんで軸ですが、詳しいことは省略しますがまあvecがそのまま使えます。そういうわけで、角度の2倍とvecでrotateしましょう。
function setup() {
createCanvas(400, 400, WEBGL);
const qx = p5.Quat.fromAxisAngle(0.7, 1, 0, 0);
const qy = p5.Quat.fromAxisAngle(0.3, 0, 1, 0);
const qz = p5.Quat.fromAxisAngle(1.1, 0, 0, 1);
const q = qx.multiply(qy).multiply(qz);
const theta = Math.atan2(q.vec.mag(), q.w);
background(0);
rotate(theta*2, q.vec);
noStroke();
pointLight(99,200,255,-10,40,360);
fill(255);
box(200);
}
同じ見た目になりましたね。比較してみましょう。
うん!ばっちりですね!もちろん現在のp5.Quatはバグってるんで、パッチ当てないとめちゃくちゃになります。一応載せておこうか。
まあそんな感じなんで、パッチ当てない限りこの手段は使い物にならないです。とはいえ個別に実行すればいいわけですから、コードを短くしたいのでもない限り使う機会はないでしょう。ちなみに一応issueは立てておきました。ただ今ちょっとレビュアーが事務処理で忙しいようで、すぐには解決しないでしょうね。
おわりに
ここまでお読みいただいてありがとうございました。
解決済みだった
なんかもう解決済みでした。
そういうわけで、遠からず反映されるでしょう。2.3.2の時点ではまだ反映されていないようです。
1系でも使えるよ
p5.Quat自体は1.11.0かそれ以前...わかりませんが、そこからあるんで、1系でもこのネタは使えます。まあパッチを当てないと正しい結果は出ませんが。結果しか要らないなら1系か2系かは関係ないですね。




