#03 ビジュアルコーディングを始めよう

円と三角関数で曲線を描く

四角形だけではなく、滑らかな曲線や複雑な形を描きたい——そのためには三角関数が必要です。

Math.sin()Math.cos() は、ビジュアルコーディングで最もよく使う関数です。この2つを理解すれば、円・波・スパイラル・ローズ曲線・リサジュー図形など、無限の表現が手に入ります。


円を描く — arc()

Canvasで円を描くには ctx.arc() を使います。

ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.fill();    // または ctx.stroke();
引数意味
x, y円の中心座標
radius半径(ピクセル)
startAngle描き始める角度( ラジアン? 角度の単位。円一周が2π(約6.28)ラジアン。`Math.sin()`や`Math.cos()`はラジアンを受け取る。度数法との変換は「ラジアン = 度数 × π / 180」。
endAngle描き終わる角度( ラジアン? 角度の単位。円一周が2π(約6.28)ラジアン。`Math.sin()`や`Math.cos()`はラジアンを受け取る。度数法との変換は「ラジアン = 度数 × π / 180」。

完全な円を描くには endAngleMath.PI * 2 にします(360°分のラジアン)。

ctx.beginPath();
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2);
ctx.fillStyle = '#00e5ff';
ctx.fill();
arc() で描いた円
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2);
ctx.fillStyle = '#00e5ff';
ctx.fill();

ctx.beginPath() を忘れると前の描画経路が残り、意図しない形になります。新しい図形を始める前には必ず呼びましょう。

輪郭線を描く

fill() の代わりに stroke() を使うと、塗りつぶさずに輪郭だけ描けます。

ctx.beginPath();
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2);
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 3;
ctx.stroke();
stroke() で描いた円の輪郭
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2);
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 3;
ctx.stroke();

ラジアンとは

arc() の角度は** ラジアン? 角度の単位。円一周が2π(約6.28)ラジアン。`Math.sin()`や`Math.cos()`はラジアンを受け取る。度数法との変換は「ラジアン = 度数 × π / 180」。 **という単位を使います。

  • 0° = 0 ラジアン
  • 180° = Math.PI ≈ 3.14159 ラジアン
  • 360° = Math.PI * 2 ≈ 6.28318 ラジアン

変換式:ラジアン = 度数 × Math.PI / 180

// 度数からラジアンに変換するヘルパー
function deg(d) { return d * Math.PI / 180; }

ctx.arc(W/2, H/2, 100, deg(0), deg(270));  // 0°から270°まで

ビジュアルコーディングでは Math.PI をそのまま使うことが多く、* 2 で360°、/ 2 で90°などと組み合わせます。


sin と cos — 円周上の点を計算する

Math.sin()Math.cos() の核心的な使い方は円周上の点座標を求めることです。

var angle = Math.PI * 2 * (i / n);  // i番目の角度(0〜2π)
var x = cx + Math.cos(angle) * r;   // x = 中心x + 半径 × cos
var y = cy + Math.sin(angle) * r;   // y = 中心y + 半径 × sin

これで「中心 (cx, cy)、半径 r の円周を n 等分したときの i 番目の座標」が計算できます。

sin/cos で円周上に点を打つ
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var cx = W / 2, cy = H / 2, r = 110, n = 24;
for (var i = 0; i < n; i++) {
var a = Math.PI * 2 * (i / n);
var x = cx + Math.cos(a) * r;
var y = cy + Math.sin(a) * r;
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2);
ctx.fillStyle = 'hsl(' + (i / n * 360) + ', 90%, 60%)';
ctx.fill();
}

sin波を描く

Math.sin() の値域は -1 〜 1。これをy座標にマッピングすれば波の曲線になります。

ctx.beginPath();
for (var x = 0; x <= W; x++) {
  var y = H / 2 + Math.sin(x * 0.03) * 80;
  // x * 0.03 → 周波数を調整(大きいほど細かい波)
  // * 80    → 振幅(大きいほど大きい波)
  if (x === 0) ctx.moveTo(x, y);
  else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#00e5ff';
ctx.lineWidth = 2;
ctx.stroke();
Math.sin() で描いたsin波
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
ctx.beginPath();
for (var x = 0; x <= W; x++) {
var y = H / 2 + Math.sin(x * 0.03) * 90;
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#00e5ff';
ctx.lineWidth = 2;
ctx.stroke();

複数の周波数を重ね合わせると複雑な波形になります。

複数のsin波を重ね合わせた合成波
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
ctx.beginPath();
for (var x = 0; x <= W; x++) {
var y = H / 2
  + Math.sin(x * 0.02) * 60
  + Math.sin(x * 0.05) * 30
  + Math.sin(x * 0.11) * 15;
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#ff6b9d';
ctx.lineWidth = 2;
ctx.stroke();

ローズ曲線(花)を描く

** パラメトリック曲線? パラメーターtを変化させてx=f(t), y=g(t)で点の軌跡を描く方法。ローズ曲線・リサジュー曲線など複雑な形状を少ない式で表現できる。 **の力を見てみましょう。ローズ曲線は r = cos(n × θ) という式で描けます。

r(極座標の半径) = cos(n × θ)
x = r × cos(θ)
y = r × sin(θ)

n を変えると花びらの枚数が変わります(奇数のnだとn枚、偶数のnだと2n枚)。

var n = 5;  // 花びらの数
ctx.beginPath();
for (var i = 0; i <= 720; i++) {
  var a = i * Math.PI / 180;        // 0〜4π(720°)
  var r = 120 * Math.cos(n * a);    // 極座標の半径
  var x = W / 2 + r * Math.cos(a);
  var y = H / 2 + r * Math.sin(a);
  if (i === 0) ctx.moveTo(x, y);
  else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#ff6b9d';
ctx.lineWidth = 1.5;
ctx.stroke();
ローズ曲線 r = cos(5θ) — 5枚の花びら
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var n = 5;
ctx.beginPath();
for (var i = 0; i <= 720; i++) {
var a = i * Math.PI / 180;
var r = 110 * Math.cos(n * a);
var x = W / 2 + r * Math.cos(a);
var y = H / 2 + r * Math.sin(a);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = '#ff6b9d';
ctx.lineWidth = 1.5;
ctx.stroke();

n の値を変えると形が変わります。n = 3(3枚)、n = 7(7枚)などで試してみましょう。


スパイラルを描く

角度 θ を増やしながら半径 r も増やすと、螺旋(スパイラル)になります。

for (var i = 0; i < 800; i++) {
  var a = i * 0.3;       // 角度(どんどん増える)
  var r = i * 0.25;      // 半径(角度に比例して増える)
  var x = W / 2 + Math.cos(a) * r;
  var y = H / 2 + Math.sin(a) * r;
  ctx.fillStyle = 'hsl(' + (i * 0.45) + ', 80%, 60%)';
  ctx.fillRect(x - 1.5, y - 1.5, 3, 3);
}
アルキメデスの螺旋 — 角度と半径を同時に増やす
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
for (var i = 0; i < 800; i++) {
var a = i * 0.3;
var r = i * 0.25;
var x = W / 2 + Math.cos(a) * r;
var y = H / 2 + Math.sin(a) * r;
ctx.fillStyle = 'hsl(' + (i * 0.45) + ', 80%, 60%)';
ctx.fillRect(x - 1.5, y - 1.5, 3, 3);
}

黄金角スパイラル(ひまわり)

自然界の植物の種の配置に現れる黄金角(約137.5°)を使うと、ひまわりのような美しいパターンが生まれます。

var golden = Math.PI * (3 - Math.sqrt(5));  // ≈ 2.399(黄金角ラジアン)
for (var i = 0; i < 600; i++) {
  var a = i * golden;
  var r = Math.sqrt(i) * 8;
  ...
}
黄金角スパイラル — 自然界のフィボナッチパターン
ctx.fillStyle = '#0d1117';
ctx.fillRect(0, 0, W, H);
var golden = Math.PI * (3 - Math.sqrt(5));
for (var i = 0; i < 600; i++) {
var a = i * golden;
var r = Math.sqrt(i) * 7;
var x = W / 2 + Math.cos(a) * r;
var y = H / 2 + Math.sin(a) * r;
var size = Math.sqrt(i) * 0.4 + 1.5;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fillStyle = 'hsl(' + (i * 0.6) + ', 80%, 60%)';
ctx.fill();
}

まとめ

この回でやったこと:

  • ctx.arc() で円と円弧を描いた
  • ** ラジアン? 角度の単位。円一周が2π(約6.28)ラジアン。`Math.sin()`や`Math.cos()`はラジアンを受け取る。度数法との変換は「ラジアン = 度数 × π / 180」。 **の概念を理解した
  • Math.cos(a) * rMath.sin(a) * r で円周上の座標を計算した
  • パラメトリック曲線? パラメーターtを変化させてx=f(t), y=g(t)で点の軌跡を描く方法。ローズ曲線・リサジュー曲線など複雑な形状を少ない式で表現できる。 (ローズ曲線・スパイラル)で複雑な形を作った
  • 黄金角で自然界のパターンを再現した

次回はここに時間の概念を加えます。requestAnimationFrame を使ってアニメーションを作れば、これらの図形が動き出します。