単位円上の点Pと三角関数の結果をProcessingで描画する

この記事について

f:id:jakalada:20180206011024p:plain

数学ガールの秘密ノート/丸い三角関数」で"sinθは点Pのy座標"という話が出てきたときに、自分が三角関数に苦手意識があるのは対応づけが腑に落ちていないからだと気づき、次の値についてProcessingで値と図の対応付けを行った。

  • θ
  • sinθ
  • cosθ

気づいた点

y座標と正負が、数学の勉強の中でよく出てくる定義と、PC画面上の定義(今回であればp5.jsにおける描画の座標系)が異なることを意識してコードを書いておらず、場当たり的に変換して済ましていた。

atan2関数が-π/2~π/2の範囲の値を返すことや、そもそも単位がラジアンであることをしっかり認識していなかった。

描画

スケッチ

p5.jsを使用した

function setup() {
  createCanvas(windowWidth, windowHeight);

  // 背景色
  background(0xff, 0xff, 0xff);
    
    // 単位円の色
    otherColor = color(0xcf, 0xd8, 0xdc);
    
    // 斜辺と単位円上の点の色
    hypotenuseColor = color(0x60, 0x7d, 0x8b);
    
    // θの色
    positiveThetaColor = color(0x21, 0x96, 0xf3);
    negativeThetaColor = color(0xf4, 0x43, 0x36);

  // cosθの色
  cosThetaColor = color(0xff, 0xc1, 0x07);

  // sinθの色
  sinThetaColor = color(0x4c, 0xaf, 0x50);
    
    // 原点の位置を算出
    originX = windowWidth / 2.0;
    originY = windowHeight / 2.0;
    
    // 基準にする描画領域の正方形のサイズと位置を算出
    squareSize = min(windowWidth, windowHeight) / 10.0 * 9.0;
    squareX = originX - squareSize / 2.0;
    squareY = originY - squareSize / 2.0;

    // 単位円のサイズを算出
    unitCircleSize = squareSize / 2.0;
    unitCircleRadius = unitCircleSize / 2.0;
    
    // 単位円上の点のサイズを算出
    pointSize = squareSize / 75.0;
    
    // θの弧のサイズを算出
    thetaArcSize = unitCircleSize / 10.0;
    
    // レイアウトグリッドのサイズを算出
    gridSize = squareSize / 50.0;
    
    // テキストサイズを算出
    minTextSize = gridSize * 2.0;
    
    // 計算結果のラベルの表示位置を算出
    labelTextX = squareX + gridSize * 2;
    thetaRadTextY = squareY + minTextSize + gridSize;
    thetaDegTextY = thetaRadTextY + minTextSize;
    cosThetaTextY = thetaDegTextY + minTextSize;
    sinThetaTextY = cosThetaTextY + minTextSize;
    
    // ラベルの中で最も長い表示幅を取得
    // (計算結果の値の表示位置を揃えるため)
    textSize(minTextSize);
    maxLabelWidth = max(textWidth("θ(rad)"), textWidth("θ(deg)"))
    maxLabelWidth = max(maxLabelWidth, textWidth("cosθ"))
    maxLabelWidth = max(maxLabelWidth, textWidth("sinθ"))
    
    // 計算結果の値の表示位置を算出
    valueTextX = labelTextX + maxLabelWidth + gridSize;
    
    // 計算結果の値の表示幅を算出
    maxValueWidth = textWidth("-000.000000");
}
 
function draw() {
    clear();
    
    // 単位円を描画
    drawUnitCircle();
    
    // 原点とマウスまでの距離を算出
    // (y軸の座標系が逆なので符号を反転する)
    var deltaX = mouseX - originX;
    var deltaY = -(mouseY - originY);
    
    // 原点からマウスの位置までの直線を斜辺とする直角三角形のθの値を算出
    var theta = atan2(deltaY, deltaX);
    
    // cosθとsinθを算出
    var cosTheta = cos(theta);
    var sinTheta = sin(theta);
    
    // 原点からマウスの位置までの直線と単位円の交点の座標を算出
    var pointX = originX + unitCircleRadius * cosTheta;
    var pointY = originY - unitCircleRadius * sinTheta;

    // θの値が0〜3.14、-3.14〜0と変化するので、それを0〜6.18になるように変換する
    if (theta < 0) {
        theta = PI + (PI + theta);
    }
    
  // 算出した角度の弧を描画
  drawTheta(theta);
    
    // 原点から単位円上の点までの直線を描画
    drawHypotenuse(pointX, pointY);
    
    // 計算結果を描画
    drawInfoText(theta, cosTheta, sinTheta);
}

// 原点から(cosTheta, sinTheta)までの直線などを描画
function drawHypotenuse(toX, toY) {
  strokeWeight(1.0);

  // 原点から(cosθ, sinθ)までの直線を描画
    stroke(hypotenuseColor);
  line(originX, originY, toX, toY);
    
    // (0, sinθ)から(cosθ, sinθ)までの直線を描画
    stroke(cosThetaColor);
  line(originX, toY, toX, toY);

  // (cosθ, 0)から(cosθ, sinθ)までの直線を描画
    stroke(sinThetaColor);
  line(toX, originY, toX, toY);
    
    // 単位円上の点を描画
    stroke(hypotenuseColor);
    fill(hypotenuseColor);
    ellipse(toX, toY, pointSize);
}

function drawTheta(theta) {
  strokeWeight(0.0);
  stroke(positiveThetaColor);
  fill(positiveThetaColor);
  arc(originX, originY, thetaArcSize, thetaArcSize, -theta, 0, PIE);    
}

// 計算結果のテキスト描画
function drawInfoText(theta, cosTheta, sinTheta) {
    rectMode(CORNER);
    textAlign(RIGHT);
    textSize(minTextSize);
    
  stroke(positiveThetaColor);
  fill(positiveThetaColor);
    text("θ(rad)", labelTextX, thetaRadTextY, maxLabelWidth);
    text("θ(deg)", labelTextX, thetaDegTextY, maxLabelWidth);
    text(theta.toFixed(6), valueTextX, thetaRadTextY, maxValueWidth);
    text(degrees(theta).toFixed(6), valueTextX, thetaDegTextY, maxValueWidth);

    stroke(cosThetaColor);
    fill(cosThetaColor);
    text("cosθ", labelTextX, cosThetaTextY, maxLabelWidth);
    text(cosTheta.toFixed(6), valueTextX, cosThetaTextY, maxValueWidth);
    
    stroke(sinThetaColor);
    fill(sinThetaColor);
    text("sinθ", labelTextX, sinThetaTextY, maxLabelWidth);
    text(sinTheta.toFixed(6), valueTextX, sinThetaTextY, maxValueWidth);
}

// 単位円の描画
function drawUnitCircle() {
  strokeWeight(1.0);
    stroke(otherColor);
    noFill();
    ellipse(originX, originY, unitCircleSize);
    line(originX - unitCircleSize / 1.5, originY, originX + unitCircleSize / 1.5, originY);
    line(originX, originY - unitCircleSize / 1.5, originX, originY + unitCircleSize / 1.5);
}

参考

LEDドームと電子ピアノのArduinoスケッチ

でんでんタウン電子工作教室で展示した - jakaladaのブログ

上記の記事で展示したLEDドームと電子ピアノのArduinoのスケッチが下記。

電子ピアノは2016年のストリートフェスタでも日本橋小学校で展示していて、そのときに音程?音階?がずれているという指摘を受けていたので修正した。ドレミはABCの順だろうと決め込んでいたけど違うということを知りました。

LEDドームのスケッチは次の記事を参考に色相を生成する関数を実装したけど、結構行き当たりばったりになっているので暇を見て修正したい。

続きを読む