251214

const { animate } = anime;
const W = 800;
const H = 800;

let palette = [];
let lightBeams = [];
let prism;
let progress = 0;
let refractedRays = [];
let lastResetTime = 0;
const ANIMATION_DURATION = 1500;
const NUM_LIGHT_BEAMS = 5;

// 屈折率
const N1 = 1.0; // 空気
const N2_BASE = 1.5; // ガラス

function setup() {
  createCanvas(W, H);
  palette = colorArray[2].colors;
  background(palette[4]);
  strokeWeight(2);
  stroke(palette[0]);

  prism = {
    pos: createVector(W / 2, H / 2),
    size: 150,
    normalAngle: 0, // 法線
  };

  // 各光線の終点をバラバラに設定
  lightBeams = [];
  for (let i = 0; i < NUM_LIGHT_BEAMS; i++) {
    let startY = random(0, 1) * H;
    let endY = random(0, 1) * H;
    lightBeams.push({
      start: createVector(0, startY),
      end: createVector(W / 2, endY),
      progress: 0,
    });
  }
  lastResetTime = millis();
  initializeRefractedRays();
  startAnimation();
}

function initializeRefractedRays() {
  refractedRays = [];
  let numRays = 10;

  // 各入射光に対して屈折光を計算
  for (let beamIdx = 0; beamIdx < lightBeams.length; beamIdx++) {
    let lightBeam = lightBeams[beamIdx];

    // 入射ベクトル
    let incident = p5.Vector.sub(lightBeam.end, lightBeam.start);
    incident.normalize();

    // 法線ベクトル
    let normal = createVector(cos(prism.normalAngle), sin(prism.normalAngle));

    // 入射角
    let cosIncident = -p5.Vector.dot(incident, normal);

    // 法線の向きを調整
    if (cosIncident < 0) {
      normal.mult(-1);
      cosIncident = -cosIncident;
    }

    for (let i = 0; i < numRays; i++) {
      let ratio = i / (numRays - 1);

      // 分散
      let n2 = N2_BASE + map(ratio, 0, 1, -0.1, 0.1);
      let n = N1 / n2; //屈折率の比

      // 屈折の法則
      // sin(θ₂) = (n₁/n₂) × sin(θ₁)
      let sinRefracted = n * sqrt(1 - cosIncident * cosIncident);
      let cosRefracted = sqrt(1 - sinRefracted * sinRefracted);

      // 全反射のチェック
      if (sinRefracted > 1) {
        continue;
      }

      let refracted = p5.Vector.mult(incident, n);
      let normalComponent = p5.Vector.mult(
        normal,
        n * cosIncident - cosRefracted
      );
      refracted.add(normalComponent);
      refracted.normalize();

      let rayLength = (W - lightBeam.end.x) * 1.5;
      let end = p5.Vector.add(
        lightBeam.end,
        p5.Vector.mult(refracted, rayLength)
      );

      refractedRays.push({
        start: lightBeam.end.copy(),
        end: end,
        progress: 0,
        color: palette[i % palette.length],
        beamIdx: beamIdx,
      });
    }
  }
}

function startAnimation() {
  lightBeams.forEach((beam, idx) => {
    animate(beam, {
      progress: 1,
      duration: 300,
      delay: idx * 10,
      ease: 'inOutQuad',
      onComplete: () => {
        if (idx === lightBeams.length - 1) {
          refractedRays.forEach((ray, i) => {
            animate(ray, {
              progress: 1,
              duration: 400,
              delay: i * 10,
              ease: 'inOutQuad',
            });
          });
        }
      },
    });
  });
}

function draw() {
  background(palette[4]);

  if (millis() - lastResetTime > ANIMATION_DURATION) {
    resetAnimation();
  }

  // 入射光の描画
  for (let beam of lightBeams) {
    drawAnimatedLine(beam.start, beam.end, beam.progress);
  }

  // 屈折光の描画
  let allBeamsComplete = lightBeams.every((beam) => beam.progress >= 1);
  if (allBeamsComplete) {
    for (let ray of refractedRays) {
      if (ray.progress > 0) {
        drawAnimatedLine(ray.start, ray.end, ray.progress);
      }
    }
  }
}

function drawAnimatedLine(start, end, prog) {
  let direction = p5.Vector.sub(end, start);
  let currentEnd = p5.Vector.add(start, direction.mult(prog));

  line(start.x, start.y, currentEnd.x, currentEnd.y);
}

// リセット
function resetAnimation() {
  progress = 0;
  lastResetTime = millis();

  // 各ビームの開始位置と終点を再設定
  for (let i = 0; i < lightBeams.length; i++) {
    lightBeams[i].progress = 0;
    let startY = random(0, 1) * H;
    let endY = random(0, 1) * H;
    lightBeams[i].start.y = startY;
    lightBeams[i].end.set(W / 2, endY);
  }

  initializeRefractedRays();
  startAnimation();
}

const colorArray = [
  {
    id: 0,
    colors: ['#253276', '#dfdad3', '#ffffff', '#000000'],
  },
  {
    id: 1,
    colors: ['#2E5400', '#dfdad3', '#ffffff', '#000000'],
  },
  {
    id: 2,
    colors: ['#253276', '#f9d3cc', '#f07433', '#437800', '#dfdad3'],
  },
];

屈折の法則のベクトル計算

sin(θ₂) = (n₁/n₂) × sin(θ₁)

空気とガラスの屈折率の違いのシミュレーションだが、 正確に表現するとほとんど見えないので、だいぶ誇張している

Last Update :