Chapter 20

単振動・円運動の合成

miku
miku
2021.11.12に更新

単振動の動きはメトロノームのように繰り返すだけだが、これを組み合わせるとなかなかおもしろい動きをすることがある。

単振動の合成

円運動の動きを軸ごとに分解すると、上下あるいは左右に振動する動きができる。逆にこの2つの振動を結んだ点を作ると、もとの円運動の動きになる。

もし、このとき、振動の動きがバラバラだった場合、どのような動きになるだろう。振動の動きを分けるには新たな円運動が必要になる。

2つの円運動を用意して、一方のx座標、もう一方のy座標を結んだ点を作る。円の大きさや円を回る速度、点の初期値はバラバラでもいい。

このときの移動によって描かれる線のことをリサージュ曲線と呼ぶ。

let rx, ry, angleX, angleY, speedX, speedY, px, py, angle;

function setup() {
  createCanvas(windowWidth, windowHeight);
  stroke(240);
  noFill();

  angle = 0;
  // 2つの円の半径、円運動のための角度と角度を更新する速度を用意する
  rx = random(width / 4, width / 2);
  ry = random(height / 4, height / 2);
  angleX = random(TWO_PI);
  angleY = random(TWO_PI);
  speedX = random(0.04, 0.08);
  speedY = random(0.04, 0.08);

  px = width / 2 + cos(angleX) * rx;
  py = height / 2 + sin(angleY) * ry;
}

function draw() {
  const x = width / 2 + cos(angleX) * rx;
  const y = height / 2 + sin(angleY) * ry;
  line(px, py, x, y);

  angleX += speedX;
  angleY += speedY;

  px = x;
  py = y;
}

もととなる円運動と補助線を消して、リサージュ曲線だけを描く作例。

円運動の合成

const r1 = 50;
const r2 = 120;
const speed1 = 0.03;
const speed2 = 0.01;
let angle1, angle2, hist;

function setup() {
  createCanvas(windowWidth, windowHeight);
  stroke(240);
  noFill();

  angle1 = 0;
  angle2 = 0;

  hist = [];
}

function draw() {
  clear();

  angle1 += speed1;
  angle2 += speed2;

  translate(width / 2, height / 2);
  const x1 = cos(angle1) * r1;
  const y1 = sin(angle1) * r1;
  const x2 = cos(angle2) * r2;
  const y2 = sin(angle2) * r2;

  const v = createVector(x2 - x1, y2 - y1);
  v.setMag(300);
  hist.push({ x: v.x, y: v.y });
  if (hist.length > 100) {
    hist.shift();
  }

  circle(0, 0, r1 * 2);
  circle(0, 0, r2 * 2);
  line(x1, y1, v.x, v.y);

  for (let i = 0; i < hist.length - 1; i++) {
    line(hist[i].x, hist[i].y, hist[i + 1].x, hist[i + 1].y);
  }
}

2つの円運動を用意し、点同士を結んで、さらにはみでるように線を描き、線の先の軌跡を描画する作例。

円運動の合成 その2

let circle1, circle2, hist;

function setup() {
  createCanvas(windowWidth, windowHeight);
  stroke(240);
  noFill();

  circle1 = {
    x: width / 4,
    y: height / 2,
    r: 50,
    angle: 0,
    speed: 0.08,
  };

  circle2 = {
    x: width / 2,
    y: height / 2,
    r: 100,
    angle: 0,
    speed: 0.03,
  };

  hist = [];
}

function draw() {
  clear();

  circle1.angle += circle1.speed;
  circle2.angle += circle2.speed;

  circle(circle1.x, circle1.y, circle1.r * 2);
  circle(circle2.x, circle2.y, circle2.r * 2);

  const x1 = circle1.x + cos(circle1.angle) * circle1.r;
  const y1 = circle1.y + sin(circle1.angle) * circle1.r;
  const x2 = circle2.x + cos(circle2.angle) * circle2.r;
  const y2 = circle2.y + sin(circle2.angle) * circle2.r;

  const v = createVector(x2 - x1, y2 - y1);
  v.setMag(x1 + width / 4);
  hist.push({ x: x1 + v.x, y: y1 + v.y });
  if (hist.length > 100) {
    hist.shift();
  }

  line(x1, y1, x1 + v.x, y1 + v.y);

  for (let i = 0; i < hist.length - 1; i++) {
    line(hist[i].x, hist[i].y, hist[i + 1].x, hist[i + 1].y);
  }

  push();
  fill("#292a33");
  circle(x1, y1, 10);
  circle(x2, y2, 10);
  pop();
}

先程とは違い、円同士が入れ子の関係になっていない状態の作例。

円運動の合成 その3

const n = 30;
let r, baseRad;

function setup() {
  createCanvas(windowWidth, windowHeight);
  baseRad = 0;
}

function draw() {
  clear();
  drawItem();

  baseRad += 0.02;
  r = min(width, height) / 6;
}

function drawItem() {
  const a = [];
  const b = [];

  for (let rad = 0; rad < TWO_PI; rad += TWO_PI / n) {
    const x = width / 4 + cos(baseRad + rad) * r;
    const y = height / 2 + sin(baseRad + rad) * r;

    circle(x, y, 10);
    circle(width - x, height - y, 10);

    a.push({ x, y });
    b.push({ x: width - x, y: height - y });
  }

  push();
  stroke(240);
  noFill();

  for (let i = 0; i < a.length; i++) {
    line(a[i].x, a[i].y, b[i].x, b[i].y);
  }

  pop();
}

左に円運動を配置、右にはその上下左右対称となる円運動を配置して、同じ位置同士を線で結ぶ。

円運動の合成 その4

const n = 30;
const k = 3;
let r, baseRad;

function setup() {
  createCanvas(windowWidth, windowHeight);
  baseRad = 0;
}

function draw() {
  clear();
  drawItem();

  baseRad += 0.02;
  r = min(width, height) / 6;
}

function drawItem() {
  const a = [];
  const b = [];

  for (let rad = 0; rad < TWO_PI; rad += TWO_PI / n) {
    const x = width / 4 + cos(baseRad + rad) * r;
    const y = height / 2 + sin(baseRad + rad) * r;

    circle(x, y, 10);
    circle(width - x, height - y, 10);

    a.push({ x, y });
    b.push({ x: width - x, y: height - y });
  }

  for (let i = 0; i < k; i++) {
    b.unshift(b.pop());
  }

  push();
  stroke(240);
  noFill();

  for (let i = 0; i < a.length; i++) {
    line(a[i].x, a[i].y, b[i].x, b[i].y);
  }

  pop();
}

先ほどの作例と同じような形だが、点の位置をずらして線を結ぶ。点が格納されている配列 ab どちらをずらしてもいいのだが、解説では b の方をずらしている。ずらす回数は k にまとめている。

円運動の合成 その5

const n = 30;
const k = 3;
let baseRad, rx, ry;

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

  rx = 200;
  ry = 0;
  baseRad = 0;
}

function draw() {
  clear();
  drawItem();

  baseRad += 0.02;
  rx -= 0.4;
  ry += 0.4;
}

function drawItem() {
  const a = [];
  const b = [];

  for (let rad = 0; rad < TWO_PI; rad += TWO_PI / n) {
    const x = width / 4 + cos(baseRad + rad) * rx;
    const y = height / 2 + sin(baseRad + rad) * ry;

    circle(x, y, 10);
    circle(width - x, height - y, 10);

    a.push({ x, y });
    b.push({ x: width - x, y: height - y });
  }

  for (let i = 0; i < k; i++) {
    b.unshift(b.pop());
  }

  push();
  stroke(240);
  noFill();

  for (let i = 0; i < a.length; i++) {
    line(a[i].x, a[i].y, b[i].x, b[i].y);
  }

  pop();
}

先程の作例と同じ形だが、円の半径の横幅と縦幅を別々の変数で持ち、一方の幅を減少させ、もう一方の幅を増加させた作例。