xxxxxxxxxx
284
let lightStart, lightEnd, material0, material1;
let totalWavelengths = 2;
let slider;
function setup() {
createCanvas(800, 800, WEBGL);
slider = createSlider(1, 3, totalWavelengths, 0.01);
slider.style('width', '760px');
slider.style('height', '20px');
slider.position(20, height - 40);
lightStart = { x: width * -0.4, y: height * 0.25 };
lightEnd = { x: width * -0.1, y: 0 };
material0 = { x: width * -0.1, y: height * -0.25 };
material1 = { x: width * -0.1, y: height * 0.25 };
material2 = { x: width * 0.1, y: 0 };
}
function draw() {
background(0);
blendMode(ADD);
totalWavelengths = Math.pow(10, slider.value());
const wavelengths = Array.from({ length: totalWavelengths },
(_, i) => 380 + i * (780 - 380) / totalWavelengths);
const colors = wavelengths.map(wavelengthToColor);
const refractiveIndex = wavelengths.map(getRefractiveIndex);
const lightDirection = normalize(vectorSubtract(lightEnd, lightStart));
const materialDirection = normalize(vectorSubtract(material1, material0));
const normalDirection = perpendicular(materialDirection);
const incidentDirection = complexDivide(lightDirection, normalDirection);
const intersection = getIntersection(lightStart, lightEnd, material0, material1);
strokeWeight(4);
stroke(255);
const intersects = intersection && intersection.t2 < 1 && intersection.t2 > 0 && intersection.t1 > 0;
if (intersects)
line(lightStart.x, lightStart.y, intersection.x, intersection.y);
else
line(
lightStart.x,
lightStart.y,
lightStart.x + lightDirection.x * 2000,
lightStart.y + lightDirection.y * 2000);
for (let i = 0; i < colors.length * intersects; i++) {
const n = refractiveIndex[i];
const exiting = incidentDirection.x < 0;
let refractedDirection;
if (exiting) {
const newSpeed = incidentDirection.y * n;
if (newSpeed <= 1) {
refractedDirection = { x: -Math.sqrt(1 - (newSpeed * newSpeed)), y: newSpeed };
}
} else {
const scaledVector = { x: incidentDirection.x, y: incidentDirection.y / n };
refractedDirection = { x: Math.sqrt(1 - (scaledVector.y * scaledVector.y)), y: scaledVector.y };
}
if (!refractedDirection)
continue;
const exitingDirection = complexMultiply(normalDirection, refractedDirection);
stroke(wavelengthToColor(wavelengths[i]));
line(
intersection.x,
intersection.y,
intersection.x + (exitingDirection.x * 2000),
intersection.x + (exitingDirection.y * 2000));
}
stroke(255, 128);
strokeWeight(3);
line(material0.x, material0.y, material1.x, material1.y);
line(material1.x, material1.y, material2.x, material2.y);
line(material2.x, material2.y, material0.x, material0.y);
drawDraggablePoint(lightStart);
drawDraggablePoint(lightEnd);
drawDraggablePoint(material0);
drawDraggablePoint(material1);
drawDraggablePoint(material2);
}
function drawDraggablePoint(pt) {
push();
translate(pt.x, pt.y, 0);
fill(255);
noStroke();
ellipse(0, 0, 10, 10); // Draw at origin since translation is applied
pop();
}
function normalize(v) {
let length = Math.sqrt(v.x * v.x + v.y * v.y); // Calculate the magnitude of the vector
if (length === 0) {
return { x: 0, y: 0 }; // Avoid division by zero in case the vector has zero length
}
return { x: v.x / length, y: v.y / length }; // Divide each component by the length to normalize
}
function flip(v) {
return { x: -v.x, y: -v.y };
}
function perpendicular(v) {
return { x: -v.y, y: v.x };
}
function vectorSubtract(v1, v2) {
return { x: v1.x - v2.x, y: v1.y - v2.y };
}
function normalize(v) {
let length = Math.sqrt(v.x * v.x + v.y * v.y);
return { x: v.x / length, y: v.y / length };
}
function complexDivide(v1, v2) {
return complexMultiply(v1, { x: v2.x, y: -v2.y });
}
function complexMultiply(v1, v2) {
return {
x: v1.x * v2.x - v1.y * v2.y,
y: v1.x * v2.y + v1.y * v2.x
};
}
function screenToWorld(x, y) {
const proj = [];
const modelView = [];
const viewport = [0, 0, width, height];
const worldPos = [0, 0, 0];
const invertedY = height - y; // Invert y-axis for WEBGL
const screenPos = [x, invertedY, 0];
// Apply transformations (for simplicity, assumes 2D plane z=0)
let mx = map(x, 0, width, -width / 2, width / 2);
let my = map(y, 0, height, -height / 2, height / 2);
return { x: mx, y: my };
}
function mousePressed() {
const mousePos = screenToWorld(mouseX, mouseY);
if (dist(mousePos.x, mousePos.y, lightStart.x, lightStart.y) < 10) {
lightStart.dragging = true;
} else if (dist(mousePos.x, mousePos.y, lightEnd.x, lightEnd.y) < 10) {
lightEnd.dragging = true;
} else if (dist(mousePos.x, mousePos.y, material0.x, material0.y) < 10) {
material0.dragging = true;
} else if (dist(mousePos.x, mousePos.y, material1.x, material1.y) < 10) {
material1.dragging = true;
} else if (dist(mousePos.x, mousePos.y, material2.x, material2.y) < 10) {
material2.dragging = true;
}
}
function mouseReleased() {
lightStart.dragging = false;
lightEnd.dragging = false;
material0.dragging = false;
material1.dragging = false;
material2.dragging = false;
}
function mouseDragged() {
const mousePos = screenToWorld(mouseX, mouseY);
if (lightStart.dragging) {
lightStart.x = mousePos.x;
lightStart.y = mousePos.y;
} else if (lightEnd.dragging) {
lightEnd.x = mousePos.x;
lightEnd.y = mousePos.y;
} else if (material0.dragging) {
material0.x = mousePos.x;
material0.y = mousePos.y;
} else if (material1.dragging) {
material1.x = mousePos.x;
material1.y = mousePos.y;
} else if (material2.dragging) {
material2.x = mousePos.x;
material2.y = mousePos.y;
}
}
function getIntersection(a, b, c, d) {
// Vector components for the lines
const dx1 = b.x - a.x;
const dy1 = b.y - a.y;
const dx2 = d.x - c.x;
const dy2 = d.y - c.y;
const denom = dx1 * dy2 - dy1 * dx2;
if (denom === 0) return null;
const t1 = (dy2 * (d.x - a.x) + dx2 * (a.y - d.y)) / denom;
const t2 = (1 - (dy1 * (a.x - d.x) + dx1 * (d.y - a.y)) / denom)
const intersectX = a.x + t1 * dx1;
const intersectY = a.y + t1 * dy1;
return { x: intersectX, y: intersectY, t1: t1, t2: t2 };
}
function wavelengthToColor(wavelength) {
let r = 0, g = 0, b = 0;
const uvThreshold = 420;
const irThreshold = 700;
// RGB Calculation based on wavelength
if (wavelength >= 645 && wavelength <= 780) {
r = 1.0;
g = 0.0;
b = 0.0;
} else if (wavelength >= 580 && wavelength < 645) {
r = 1.0;
g = -(wavelength - 645) / (645 - 580);
b = 0.0;
} else if (wavelength >= 510 && wavelength < 580) {
r = (wavelength - 510) / (580 - 510);
g = 1.0;
b = 0.0;
} else if (wavelength >= 490 && wavelength < 510) {
r = 0.0;
g = 1.0;
b = -(wavelength - 510) / (510 - 490);
} else if (wavelength >= 440 && wavelength < 490) {
r = 0.0;
g = (wavelength - 440) / (490 - 440);
b = 1.0;
} else if (wavelength >= 380 && wavelength < 440) {
r = -(wavelength - 440) / (440 - 380);
g = 0.0;
b = 1.0;
}
// Fading factor for extreme wavelengths
let fadingFactor = 1.0;
if (wavelength < uvThreshold) {
fadingFactor = 0.3 + 0.7 * (wavelength - 380) / (420 - 380);
} else if (wavelength > irThreshold) {
fadingFactor = 0.3 + 0.7 * (780 - wavelength) / (780 - 700);
}
// Apply fading factor to the RGB values
r *= fadingFactor;
g *= fadingFactor;
b *= fadingFactor;
// Convert RGB values to a range of 0-255
r = Math.pow(r, 0.8) * 255;
g = Math.pow(g, 0.8) * 255;
b = Math.pow(b, 0.8) * 255;
return color(r, g, b, 255 * 4 / totalWavelengths);
}
const materials = {
PMMA: { A: 1.49, B: 0.007 },
Polycarbonate: { A: 1.58, B: 0.006 },
Polystyrene: { A: 1.59, B: 0.008 },
Zeonex: { A: 1.53, B: 0.005 },
};
function getRefractiveIndex(wavelength) {
const material = "PMMA";
return materials[material].A + materials[material].B / Math.pow(wavelength / 1000, 2);
}