5_4:連結アーム p5.js JavaScript

translate()とrotate()とline()の組み合わせを3回行って、3つの線が互いにつながった連結アームのようにアニメーションするサンプルです。

次のリンクをクリックすると、実際の動作が確認できます。[連結アーム]

// ラジアン単位の角度
let angle = 0.0;
// 回転の方向 1なら時計回り、-1なら反時計回り
let angleDirection = 1;
// 1フレーム当たりの角度の増減分
const speed = 0.005;
let p;

function setup() {
    createCanvas(120, 120);
    p = createP();
}

function draw() {
    background(204);
    // 上腕
    translate(20, 25);
    rotate(angle);
    strokeWeight(12);
    line(0, 0, 40, 0);

    // 前腕
    translate(40, 0);
    rotate(angle * 2.0);
    strokeWeight(6);
    line(0, 0, 30, 0);

    // 手
    translate(30, 0);
    rotate(angle * 2.5);
    strokeWeight(3);
    line(0, 0, 20, 0);

    // rotate()に渡すラジアン単位の角度。
    // angleDirectionが1のときはspeed分だけ大きくなり、
    // angleDirectionが-1のときはspeed分だけ小さくなる
    angle += speed * angleDirection;

    // QUARTER_PIは45度
    // angleが45度より大きくなるか、0未満になったら、
    // angleDirectionを反転(1のときは-1に、-1のときは1にする)
    if ((angle > QUARTER_PI) || (angle < 0)) {
        angleDirection *= -1;
    }
    p.html(angleDirection);
}

やっていることの1つ1つはこれまでと同じなのですが、コードが長くなっているので複雑に見え、変換は累積されるという性質を利用しているので、実際の動作も複雑です。そこで以降では、コードを分解して追っていくことにします。

変数angle

連結アームをシミュレーションするこのサンプルのポイントは変数angleにあります。この変数はラジアン単位の角度としてrotate()関数に渡します。angleDirectionとspeedはangleを変化させるための変数です。これらは、メインのJavaScriptのどこからでもアクセスできるグローバル変数として、次の値を代入して作成しています。

// ラジアン単位の角度
let angle = 0.0;
// 回転の方向 1なら時計回り、-1なら反時計回り
let angleDirection = 1;
// 1フレーム当たりの角度の増減分
const speed = 0.005;

この3つの変数が活躍するのはdraw()関数の後半です。draw()関数は1秒間に60回、p5.jsによって呼び出されるので、同じ頻度でangleは0.005*1だけ増えていきます。これをrotate()関数に渡すと、座標軸が少しずつ反時計回りに回転していきます(描画物は時計回りに回転するように見えます)。しかしこのままではアームが元に戻るアニメーションにならないので、angleの範囲に制限を設ける必要があります。

angle += speed * angleDirection;
if ((angle > QUARTER_PI) || (angle < 0)) {
    angleDirection *= -1;
}

QUARTER_PIはラジアン値を参照するp5.jsの定数で、度単位で言うと45度です。このifステートメントでは、angleが45度より大きくなるか、0未満になったら、angleDirectionを反転しています。つまりangleDirectionが1のときは-1に、-1のときは1に設定しています。この反転により、draw()が次に呼び出されるフレームでは、angleDirectionが1のときには、angleは0.005だけ大きくなり、angleDirectionが-1のときには0.005だけ小さくなっていきます。

angleのこの変化は次のグラフで表されます。変数angleは0.005という非常に小さな数値分だけ増減するので、グラフは連続して見える直線になります。

上腕部

draw()関数の前半部に、グリッドと上腕部を作成するコードを追加します。ただし位置取りが分かりやすく、ほかの部位と区別しやすいように、上腕の線は細い赤に変えます。またtranslate()は書いていません。

function draw() {
    background(204);

    // 10刻みのグリッドを描画
    const step = 10;
    for (let i = 0; i < width; i += step) {
        for (let j = 0; j < height; j += step) {
            stroke(125, 50);
            strokeWeight(1);
            line(i, 0, i, height);
            line(0, j, width, j);
        }
    }

    // 上腕 分かりやすいように線を細く赤色にする
    rotate(angle);
    strokeWeight(3);
    stroke(255, 0, 0);
    line(0, 0, 40, 0);

    angle += speed * angleDirection;
    if ((angle > QUARTER_PI) || (angle < 0)) {
        angleDirection *= -1;
    }
    p.html(angleDirection);
}

これを実行すると、赤い線がキャンバスの原点と重なるその左隅を中心に、x軸方向に一致する0度から時計回りの45度までを往復するアニメーションになります。これは、座標軸をangle分だけ時計回りか反時計回りに回転させ、線を描画する作業を毎秒60回行っている、ということです。前に述べたように画面では座標軸が回転するのではなく、線が回転するように見えます。描画のし直しは高速で行われるので、脳が錯覚を起こしてアニメーションして見えるのです。

rotate()の上にtranslate(20, 25)を挿入します。これにより線は(20, 25)を中心に回転して見えるようになります。

// 上腕
translate(20, 25);
rotate(angle);
strokeWeight(3);
stroke(255, 0, 0);
line(0, 0, 40, 0);

前腕部

つづいて前腕部に移ります。まず移動先、つまりtranslate()に指定する値を考えてみましょう。移動先は、上腕の線の右端(40,0)です。これは、上腕のtranslate()とrotate()をコメントし、前腕部でtranslate(40, 0)を記述すると確認できます。

// 前腕
translate(40, 0);
strokeWeight(3);
stroke(0, 255, 0);
line(0, 0, 30, 0);

上腕のtranslate()とrotate()を実行しない状態のまま、translate(40, 0);の下にrotate(angle * 2.0);を挿入します。角度のangle * 2.0は、angleの2倍、座標軸が回転するということです。

// 前腕
translate(40, 0);
rotate(angle * 2.0);
strokeWeight(3);
stroke(0, 255, 0);
line(0, 0, 30, 0);

コードを実行すると、前腕部の緑の線だけが回転します。angleの2倍回転するので、緑の線は0度から90度まで曲がります。

上腕部のtranslate()とrotate()を実行するようにコメント削除します。

// 上腕
translate(20, 25);
rotate(angle);
strokeWeight(3);
stroke(255, 0, 0);
line(0, 0, 40, 0);

// 前腕
translate(40, 0);
rotate(angle * 2.0);
strokeWeight(3);
stroke(0, 255, 0);
line(0, 0, 30, 0);

上腕の付け根が(20, 25)に移動し、前腕の付け根も上腕の先端につながった状態で描画されます。上腕は前腕より深く曲がっているように見えます。

手部

前腕部の下に手の部分を追加します。rotate()にangle * 2.5を与えると、手が前腕よりもさらに深く曲がって見えるようになります。

// 手部
translate(30, 0);
rotate(angle * 2.5);
strokeWeight(3);
stroke(0, 0, 255);
line(0, 0, 20, 0);

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA