ロゴシュミグラム

JavascriptでCanvasに幾何学模様を描こう!その2

プログラム内で使う角度について

プログラム内で使う角度はラジアン(radian)という単位に変換しなければなりません。これは角度に円周率(π)を掛けて180で割ることで求めることができます。

たとえば、60°をラジアンに変換すると次のようになります。

ラジアンに変換

const radian = 60 * Math.PI / 180
// 結果は1.0471975511965976

そこでラジアンを計算する関数を作っておきます。

functions.jsに次のように書き込みます。

functions.js

/**
* 度からラジアンを計算する関数
* @param 角度(単位:度)
* @return 角度(単位:ラジアン)
*/
const calcRadian = (angle) => {
return angle * Math.PI / 180;
};

MEMO
変数名の radian は角度を表します。一方、変数名の radius は線の長さ(半径)を表します。 スペルが似ているため間違わないよう気をつけてください。

回転後の座標位置の求め方

次のような長さが1の線を60度回転させたら、座標位置はどうなるでしょうか?

三角関数で求める座標位置

これは三角関数のサインとコサインを使って求めることができます。

この場合のx座標は、cos60°となります。Javascriptでは Math.cos で計算できます。引数には角度を渡しますが、単位はラジアンでなければなりません。そこでx座標は次のようにして求めます。

x座標の求め方

const radian = calcRadian(60)
const x = Math.cos(radian);
// xの値は0.5

次にy座標は、sin60°となります。 Math.sin を使って同じように計算します。

y座標の求め方

const radian = calcRadian(60)
const x = Math.sin(radian);
// xの値は0.8660254037844386

よって、この場合の座標位置は { x: 0.5, y: 0.8660254037844386 } となります。

線の長さと原点が変わった場合

中心が(30, 40)で線の長さが50の場合の回転後の座標位置はどうなるでしょうか?

三角関数で求める座標位置

同じようにサインとコサインで求めますが、線の長さを掛けて、中心座標を足す必要があります。

座標位置の求め方

const radian = calcRadian(60)
const x = Math.cos(radian) * 50 + 30;
const y = Math.sin(radian) * 50 + 40;

よって、この場合の座標位置は { x: 55, y: 83.30127018922192 } となります。

回転後の座標位置を求める関数の作成

functions.jsに次のように書き込みます。

functions.js

/**
* 回転後の座標位置を求める関数
* @param centerPos 中心となる座標位置
* @param radian 回転させる角度(単位:ラジアン)
* @param radius 線の長さ
* @return 回転後の座標位置
*/
const calcPos = (centerPos, radian, radius) => {
return {
x: Math.cos(radian) * radius + centerPos.x,
y: Math.sin(radian) * radius + centerPos.y
};
};

現在の節の座標位置の求め方

現在の節が存在している角度は線の回転角度×回転回数+初期角度で求めることができます。

たとえば、線の長さが100で、初期角度を90°に設定しているとします。

回転針の初期位置

回転角度が-160°で、それを3回、回転させたとします。

回転後の回転針の位置

そうすると現在の角度は-160×3+90=-390°となります。よって、現在の節の座標は次のようにして求められます。

節の座標

const radius = 100;
const centerPos = { x: 0, y: 0 };
const radian = calcRadian(-390);
const nodePos = calcPos(centerPos, radian, radius);
// nodePosは、{ x: 86.6025403784439, y: -50 }

すべての節の座標位置を計算する関数の作成

functions.jsに次のように書き込みます。

関数内の rotateCount は回転回数、 initialRadian は初期角度のことで、main.jsで設定する変数です。

functions.js

/**
* すべての節の座標位置を計算する関数
* @return 新たな節の座標位置の配列
*/
const createNodePosList = () => {
// 回転後の節の座標位置を入れていく配列
// 最初の節は常に原点(0, 0)にある
const nodePosList = [{ x: 0, y: 0 }];
lineDataList.forEach((lineData, i) => {
// 中心となる座標位置
const centerPos = nodePosList[i];
// 現在の節の角度
const currentRadian = calcRadian(lineData.angle) * rotateCount + initialRadian;
// 回転後の節の座標位置
const nodePos = calcPos(centerPos, currentRadian, lineData.radius);
nodePosList.push(nodePos);
});
return nodePosList;
};

canvasを初期状態にする関数の作成

functions.jsに次のように書き込みます。

functions.js

/**
* canvasを初期状態にする関数
*/
const initDraw = () => {
rotateCount = 0;
// 初期状態の節の座標位置を計算する
nodePosList = createNodePosList();
// main-canvasを背景色で塗りつぶす
fillBgCanvas(bgColor, mainCanvasCtx);
// 回転針を消す
clearCanvas(needleCanvasCtx);
// 回転針を描く
drawNeedle(nodePosList, lineDataList, needleCanvasCtx);
};

回転針を回転させる関数の作成

functions.jsに次のように書き込みます。

functions.js

/**
* 回転針を回転させる関数
*/
const rotateNeedle = () => {
rotateCount++;
// 回転後の節の座標位置を計算する
const nextNodePosList = createNodePosList();
// 回転前の節と回転後の節とで軌道線を描く
drawTrajectories(nodePosList, nextNodePosList, lineDataList, mainCanvasCtx);
// 回転針を消す
clearCanvas(needleCanvasCtx);
// 回転針を描く
drawNeedle(nextNodePosList, lineDataList, needleCanvasCtx);
nodePosList = nextNodePosList;
};

「自動描画」ボタンを押したときの処理

functions.jsに次のように書き込みます。

functions.js

/**
* 「自動描画」ボタンを押したときの処理
*/
const autoDraw = () => {
// すでに描いている最中であれば何もしない
if (runId !== null) return;
const run = () => {
rotateNeedle();
// このrun関数を繰り返し実行させる
runId = window.setTimeout(run, drawInterval);
};
run();
};

runId はmain.jsで定義している変数です。自動描画しているときはsetTimeoutの戻り値が入ります。nullの場合は自動描画を止めていることを意味します。

「描く」ボタンを押したときの処理

functions.jsに次のように書き込みます。

functions.js

/**
* 「描く」ボタンを押したときの処理
*/
const draw = () => {
// 自動描画している最中であれば何もしない
if (runId !== null) return;
rotateNeedle();
};

「自動描画」ボタンを押したときの処理

functions.jsに次のように書き込みます。

functions.js

/**
* 「中断」ボタンを押したときの処理
*/
const stopDraw = () => {
if (runId === null) return;
// run関数の実行を止める
window.clearInterval(runId);
runId = null;
};

main.jsの作成

main.jsに次のように書き込みます。

functions.js

// 回転針の初期角度。時計の12時方向を初期角度にしている
const initialRadian = calcRadian(90);
// 360度をラジアンにした変換した値。点を描くときに使う
const radian360 = calcRadian(360);
// 回転した回数
let rotateCount = 0;
// 現在の節の座標位置の配列
let nodePosList = [];
// setTimeoutの戻り値を入れる。nullの場合は描画を止めていることを意味する。
let runId = null;
// 2つのcanvasの描画コンテキストの初期設定する
const mainCanvasCtx = initCanvasCtx('main-canvas');
const needleCanvasCtx = initCanvasCtx('needle-canvas');
// 各ボタンにイベントリスナーを登録する
document.getElementById('draw-button').addEventListener('click', draw);
document.getElementById('start-button').addEventListener('click', autoDraw);
document.getElementById('stop-button').addEventListener('click', stopDraw);
document.getElementById('reset-button').addEventListener('click', initDraw);
// canvasを初期状態にする
initDraw();

これで完成です!index.htmlをブラウザから開いてみてください。

設定を変更していろんな模様を描こう!

settings.jsの設定を変更するといろんな模様を描くことができます。

それぞれの設定項目の入力欄を作成すれば、次のような幾何学模様メーカーを作成することもできます。