Chapter 18

軌跡

miku
miku
2021.11.12に更新

軌跡

円運動などのアニメーションの動きは、今まで通ってきた位置の軌跡を残すと、動きの全体像がわかりやすくなる場合がある。実装する方法としては、一番単純なのが画面をクリアしないことだが、描画し続けると画面がそれで埋まってしまうので、ある程度時間が経ったら一番古い位置から消えていくと都合がいい。

軌跡の保存と描画

const n = 100; // 保存量
const hist = []; // 軌跡(位置)を保存する配列

function draw() {
  // 計算して得た (x, y) を配列に入れる
  hist.push({ x, y });
  // 保存量より大きくなったら、前から要素を抜いていく
  if (hist.length > n) {
    hist.shift();
  }
  
  let prev = hist[0];
  for (let i = 1; i < hist.length; i++) {
    const cur = hist[i];
    // prev ~ cur までの線を引く
    line(prev.x, prev.y, cur.x, cur.y);
    prev = cur;
  }
}

計算した位置を毎回(毎フレーム)配列に保存しておき、ある程度の量が貯まると、それ以上は保存しないように前から要素を抜いていく。

描画の方法は hist[0] ~ hist[1], hist[1] ~ hist[2] ... のように、hist[i] ~ hist[i + 1] の位置を線で描画する。

円運動の軌跡

const n = 100;
const r = 200;
let hist, angle;

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

  hist = [];
  angle = 0;
}

function draw() {
  const x = cos(angle) * r;
  const y = sin(angle) * r;

  hist.push({ x, y });
  angle += 2;

  if (hist.length > n) {
    hist.shift();
  }

  clear();
  stroke(240);
  noFill();
  translate(width / 2, height / 2);

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

円運動の軌跡を描画する作例。線の長さは配列の長さ n に依存する。

螺旋の軌跡

const n = 600;
let hist, angle, r;

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

  hist = [];
  r = 0;
  angle = 0;
}

function draw() {
  const x = cos(angle) * r;
  const y = sin(angle) * r;
  hist.push({ x, y });
  r += 0.2;
  angle += 0.1;

  if (hist.length > n) {
    hist.shift();
  }

  clear();
  stroke(240);
  noFill();
  translate(width / 2, height / 2);

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

先程の作例と同じ形だが、半径を少しずつ増やすことでらせん型の軌跡を描く作例。

移動の軌跡

const n = 300;
let x, y, hist;

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

  x = width / 2;
  hist = [];
}

function draw() {
  clear();

  noStroke();
  fill(240);

  y = height / 2 + sin(frameCount / 20) * 100;
  hist.push({ x, y });

  if (hist.length > n) {
    hist.shift();
  }

  rect(x, y, 20, 20);

  stroke(240);
  noFill();

  let prev = hist[0];
  for (let i = 0; i < hist.length; i++) {
    const cur = hist[i];
    line(prev.x, prev.y, cur.x, cur.y);
    cur.x--; // 軌跡のx座標を毎回減らして左に移動させる
    prev = cur;
  }
}

sin() の利用による上下運動を行い、軌跡を保存するが、軌跡のx座標を毎フレーム減らして左側に移動させる作例。中央の矩形部分は上下にしか移動していないが、軌跡の動きにより右側に移動しているように見える。

移動の軌跡 その2

const n = 15;
let x, y, hist;

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

  x = width / 2;
  hist = [];
}

function draw() {
  clear();

  y = height / 2 + sin(frameCount / 20) * 100;
  hist.push({ x, y, d: 60 });

  if (hist.length > n) {
    hist.shift();
  }

  for (const h of hist) {
    h.x -= 20;
    h.d -= 4;
    circle(h.x, h.y, h.d);
  }
}

先程の作例と同じ形だが、軌跡の描画を円にして、円の大きさを少しずつ減らすようにした作例。

サイクロイド

動いている円の一点を線として繋げて描画したものをサイクロイドと呼ぶ。

const r = 20;
let x, y, angle, hist;

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

  hist = [];

  x = 0;
  y = height / 2;
  angle = 0;
}

function draw() {
  clear();
  circle(x, y, r * 2);

  const tx = x + cos(angle) * r;
  const ty = y + sin(angle) * r;

  // 円が画面内にいる間だけ位置の記録を行う
  if (x < width + r) {
    hist.push({ x: tx, y: ty });
  }

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

  x++;
  angle += 0.1;
}

矩形の軌跡

const size = 20;
let d, x, y, angle, hist;

function setup() {
  createCanvas(windowWidth, windowHeight);
  angleMode(DEGREES);
  rectMode(CENTER);
  stroke(240);
  noFill();

 // 矩形の中央から頂点までの距離
  d = dist(0, 0, size, size);
  x = 0;
  y = height / 2;
  angle = 0;

  hist = [];
}

function draw() {
  clear();
  
  // 角度angleで矩形を描画する
  translate(x, y);
  rotate(angle);
  rect(0, 0, size * 2, size * 2);
  resetMatrix();

  // 矩形が画面内にいる間だけ位置の記録を行う
  if (x < width + size * 2) {
    const h = [];
    // 矩形の4つの頂点の位置を記録する
    for (let i = 0; i < 4; i++) {
     // 頂点の位置は45度からの90度刻みの、45度、135度、225度、315度になり、
      // 矩形は回転しているので、angleの下駄を履かせる
      const a = angle + 90 * i + 45;
      const tx = x + cos(a) * d;
      const ty = y + sin(a) * d;

      h.push({ x: tx, y: ty });
    }
    hist.push(h);
  }

  let prev = hist[0];
  for (let i = 1; i < hist.length; i++) {
    const cur = hist[i];
    for (let j = 0; j < 4; j++) {
      line(prev[j].x, prev[j].y, cur[j].x, cur[j].y);
    }
    prev = cur;
  }

  x++;
  angle += 0.6;
}

こちらは矩形の各頂点の軌跡として描画する作例。