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(θ₁)
空気とガラスの屈折率の違いのシミュレーションだが、 正確に表現するとほとんど見えないので、だいぶ誇張している