AI Wave Function Simulator - Physics Interference Visualization Explore wave interference physics!
60 views · Dec 12, 2025
Create an "AI Wave Function Simulator" - interactive physics visualization of wave interference. Two
📋 Embed Code
Copy this code to embed this sketch on your website:
<iframe src="https://p5js.ai/embed/193d0f00-384c-47f7-a922-003c58250db6" width="400" height="400" frameborder="0" allow="autoplay" title="AI Wave Function Simulator - Physics Interference Visualization Explore wave interference physics!"></iframe>
Adjust width and height as needed. Preview embed →
❓ About This Sketch 3 questions
What visual effects does the AI Wave Function Simulator create?
The simulator visually represents wave interference patterns resulting from two point sources, illustrating how waves interact with each other.
How can users interact with the AI Wave Function Simulator?
Users can drag the point sources to different positions and adjust the frequency of the waves using a slider, allowing them to explore various interference patterns.
What creative coding concepts does this sketch demonstrate?
This sketch demonstrates the principles of wave interference and real-time graphics rendering, showcasing how physics can be visualized through interactive coding.
💬 AI Chat History 4 messages
👤 You
Create an "AI Wave Function Simulator" - interactive physics visualization of wave interference. Two point sources emit circular waves that interfere constructively and destructively. Users can drag the sources to reposition them and adjust frequency/wavelength with sliders. Display interference pattern with color intensity showing wave amplitude. Include: play/pause button, reset button, and checkbox to show/hide wave nodes. Visual style: blue waves on dark background with white interference maxima.
🤖 AI
Here’s a complete replacement for your project that implements the “AI Wave Function Simulator” with two draggable sources, frequency/wavelength sliders, play/pause, reset, and a show-nodes checkbox.
---
```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI Wave Function Simulator</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="controls">
<h1>AI Wave Function Simulator</h1>
</div>
<!-- p5.js core library -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- main sketch -->
<script src="sketch.js"></script>
</body>
</html>
```
---
```css
/* style.css */
body {
margin: 0;
padding: 0;
overflow: hidden;
background: radial-gradient(circle at top, #101528 0, #050811 60%);
color: #f0f4ff;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
canvas {
display: block;
}
/* Floating UI panel */
#controls {
position: absolute;
top: 12px;
left: 12px;
z-index: 10;
padding: 10px 12px;
border-radius: 10px;
background: rgba(5, 8, 20, 0.82);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
#controls h1 {
margin: 0 0 8px;
font-size: 16px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #ffffff;
}
/* Rows for controls built with p5's createDiv/createSlider/etc */
.control-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: 12px;
}
.control-row:last-child {
margin-bottom: 0;
}
.label {
min-width: 74px;
}
.unit {
opacity: 0.7;
}
/* Sliders */
#controls input[type="range"] {
width: 140px;
}
/* Buttons */
#controls button {
background: #1b6fff;
color: #ffffff;
border: none;
border-radius: 999px;
padding: 4px 10px;
font-size: 12px;
cursor: pointer;
}
#controls button:hover {
background: #3c8dff;
}
#controls button:active {
background: #1457cc;
}
/* Checkbox */
#controls input[type="checkbox"] {
margin-right: 4px;
}
```
---
```js
// sketch.js
// --- Simulation resolution ---
let fieldG; // off-screen buffer for the wave field
const fieldScale = 2; // simulation is 1/fieldScale of canvas size
// --- Wave sources ---
let source1, source2;
const sourceRadius = 14;
// --- UI elements (p5 DOM) ---
let frequencySlider, wavelengthSlider;
let playPauseButton, resetButton, nodesCheckbox;
// --- Wave parameters ---
let frequency;
let wavelength;
const defaultFrequency = 1.0; // Hz (visual)
const defaultWavelength = 200; // pixels
// --- Simulation state ---
let isPlaying = true;
let showNodes = false;
let simTime = 0; // time in seconds
let lastUpdateMillis = 0; // for dt calculation
function setup() {
createCanvas(windowWidth, windowHeight);
pixelDensity(1);
createFieldGraphics();
initializeSources();
frequency = defaultFrequency;
wavelength = defaultWavelength;
setupUI();
simTime = 0;
lastUpdateMillis = millis();
}
function draw() {
updateTime();
updateParametersFromUI();
const omega = TWO_PI * frequency; // angular frequency
const k = TWO_PI / wavelength; // wave number
renderField(omega, k);
image(fieldG, 0, 0, width, height);
drawSources();
drawOverlayText();
}
// ------------------------
// Initialization & resizing
// ------------------------
function createFieldGraphics() {
const gWidth = Math.max(1, Math.floor(width / fieldScale));
const gHeight = Math.max(1, Math.floor(height / fieldScale));
fieldG = createGraphics(gWidth, gHeight);
fieldG.pixelDensity(1);
}
function initializeSources() {
const midY = height * 0.5;
const spacing = width * 0.18;
const centerX = width * 0.5;
source1 = {
x: centerX - spacing,
y: midY,
isDragging: false,
offsetX: 0,
offsetY: 0
};
source2 = {
x: centerX + spacing,
y: midY,
isDragging: false,
offsetX: 0,
offsetY: 0
};
}
function windowResized() {
const prevW = width;
const prevH = height;
resizeCanvas(windowWidth, windowHeight);
pixelDensity(1);
createFieldGraphics();
const scaleX = prevW > 0 ? width / prevW : 1;
const scaleY = prevH > 0 ? height / prevH : 1;
if (source1 && source2) {
source1.x *= scaleX;
source1.y *= scaleY;
source2.x *= scaleX;
source2.y *= scaleY;
}
}
// ------------------------
// Time & parameters
// ------------------------
function updateTime() {
const now = millis();
if (isPlaying) {
const dt = (now - lastUpdateMillis) / 1000.0;
simTime += dt;
}
lastUpdateMillis = now;
}
function updateParametersFromUI() {
if (frequencySlider) {
frequency = parseFloat(frequencySlider.value());
}
if (wavelengthSlider) {
wavelength = parseFloat(wavelengthSlider.value());
}
}
// ------------------------
// UI setup
// ------------------------
function setupUI() {
const ui = select('#controls');
if (!ui) return;
// Frequency row
const freqRow = createDiv();
freqRow.parent(ui);
freqRow.addClass('control-row');
const freqLabel = createSpan('Frequency');
freqLabel.parent(freqRow);
freqLabel.addClass('label');
// createSlider: https://p5js.org/reference/#/p5/createSlider
frequencySlider = createSlider(0.2, 3.0, defaultFrequency, 0.01);
frequencySlider.parent(freqRow);
const freqUnit = createSpan('Hz');
freqUnit.parent(freqRow);
freqUnit.addClass('unit');
// Wavelength row
const waveRow = createDiv();
waveRow.parent(ui);
waveRow.addClass('control-row');
const waveLabel = createSpan('Wavelength');
waveLabel.parent(waveRow);
waveLabel.addClass('label');
wavelengthSlider = createSlider(60, 400, defaultWavelength, 1);
wavelengthSlider.parent(waveRow);
const waveUnit = createSpan('px');
waveUnit.parent(waveRow);
waveUnit.addClass('unit');
// Buttons row
const buttonRow = createDiv();
buttonRow.parent(ui);
buttonRow.addClass('control-row');
// createButton: https://p5js.org/reference/#/p5/createButton
playPauseButton = createButton('Pause');
playPauseButton.parent(buttonRow);
playPauseButton.mousePressed(togglePlayPause);
resetButton = createButton('Reset');
resetButton.parent(buttonRow);
resetButton.mousePressed(resetSimulation);
// Nodes checkbox row
const nodesRow = createDiv();
nodesRow.parent(ui);
nodesRow.addClass('control-row');
// createCheckbox: https://p5js.org/reference/#/p5/createCheckbox
nodesCheckbox = createCheckbox('Show nodes', showNodes);
nodesCheckbox.parent(nodesRow);
nodesCheckbox.changed(() => {
showNodes = nodesCheckbox.checked();
});
}
function togglePlayPause() {
isPlaying = !isPlaying;
if (playPauseButton) {
playPauseButton.html(isPlaying ? 'Pause' : 'Play');
}
}
function resetSimulation() {
initializeSources();
if (frequencySlider) frequencySlider.value(defaultFrequency);
if (wavelengthSlider) wavelengthSlider.value(defaultWavelength);
frequency = defaultFrequency;
wavelength = defaultWavelength;
simTime = 0;
isPlaying = true;
if (playPauseButton) {
playPauseButton.html('Pause');
}
}
// ------------------------
// Rendering the wave field
// ------------------------
function renderField(omega, k) {
if (!fieldG) return;
const g = fieldG;
g.loadPixels();
const w = g.width;
const h = g.height;
const simW = width;
const simH = height;
const maxAmp = 2.0; // max |A| for two waves of unit amplitude
const nodeThreshold = 0.06; // where we consider amplitude to be "node-like"
for (let y = 0; y < h; y++) {
const vy = ((y + 0.5) / h) * simH;
for (let x = 0; x < w; x++) {
const vx = ((x + 0.5) / w) * simW;
// Distance to each source for circular wavefronts
const dx1 = vx - source1.x;
const dy1 = vy - source1.y;
const d1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
const dx2 = vx - source2.x;
const dy2 = vy - source2.y;
const d2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
// Wave phase: k r - ω t
const phase1 = k * d1 - omega * simTime;
const phase2 = k * d2 - omega * simTime;
const a1 = Math.sin(phase1);
const a2 = Math.sin(phase2);
const A = a1 + a2;
const ampAbs = Math.abs(A);
let r, gCol, bCol;
if (showNodes && ampAbs < nodeThreshold) {
// Highlight nodes (near-zero amplitude)
r = 255;
gCol = 200;
bCol = 0;
} else {
// Map |A| from 0..maxAmp to brightness 0..1
const m = Math.min(ampAbs / maxAmp, 1.0);
const brightness = Math.pow(m, 0.6); // gamma for contrast
// Dark blue -> white gradient
const baseR = 0;
const baseG = 10;
const baseB = 35;
r = baseR + (255 - baseR) * brightness;
gCol = baseG + (255 - baseG) * brightness;
bCol = baseB + (255 - baseB) * brightness;
}
const idx = 4 * (y * w + x);
g.pixels[idx + 0] = r;
g.pixels[idx + 1] = gCol;
g.pixels[idx + 2] = bCol;
g.pixels[idx + 3] = 255;
}
}
g.updatePixels();
}
// ------------------------
// Source visuals & HUD
// ------------------------
function drawSources() {
stroke(255);
strokeWeight(2);
fill(0, 180, 255, 180);
const d = sourceRadius * 2;
ellipse(source1.x, source1.y, d, d);
ellipse(source2.x, source2.y, d, d);
noStroke();
fill(255);
textAlign(CENTER, CENTER);
textSize(12);
text('A', source1.x, source1.y);
text('B', source2.x, source2.y);
}
function drawOverlayText() {
noStroke();
fill(255, 230);
textAlign(LEFT, BOTTOM);
textSize(12);
const freqStr = 'f = ' + nf(frequency, 1, 2) + ' Hz';
const lambdaStr = 'λ = ' + nf(wavelength, 3, 0) + ' px';
const stateStr = isPlaying ? 'Running' : 'Paused';
text(freqStr + ' ' + lambdaStr + ' ' + stateStr, 16, height - 16);
}
// ------------------------
// Interaction: dragging sources
// ------------------------
function isMouseInsideCanvas() {
return mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height;
}
function mousePressed() {
if (!isMouseInsideCanvas()) return;
if (!source1 || !source2) return;
const d1 = dist(mouseX, mouseY, source1.x, source1.y);
const d2 = dist(mouseX, mouseY, source2.x, source2.y);
if (d1 <= sourceRadius + 6) {
source1.isDragging = true;
source1.offsetX = mouseX - source1.x;
source1.offsetY = mouseY - source1.y;
} else if (d2 <= sourceRadius + 6) {
source2.isDragging = true;
source2.offsetX = mouseX - source2.x;
source2.offsetY = mouseY - source2.y;
}
}
function mouseDragged() {
if (!isMouseInsideCanvas()) return;
if (source1 && source1.isDragging) {
source1.x = constrain(mouseX - source1.offsetX, 0, width);
source1.y = constrain(mouseY - source1.offsetY, 0, height);
} else if (source2 && source2.isDragging) {
source2.x = constrain(mouseX - source2.offsetX, 0, width);
source2.y = constrain(mouseY - source2.offsetY, 0, height);
}
}
function mouseReleased() {
if (source1) source1.isDragging = false;
if (source2) source2.isDragging = false;
}
```
---
Usage notes:
- Drag the labeled circles “A” and “B” to move the point sources.
- Adjust the **Frequency** and **Wavelength** sliders (built with `createSlider`: <https://p5js.org/reference/#/p5/createSlider>) to change temporal and spatial characteristics.
- Click **Pause/Play** (`createButton`: <https://p5js.org/reference/#/p5/createButton>) to freeze or resume time evolution.
- Click **Reset** to restore default positions and parameters.
- Toggle **Show nodes** (`createCheckbox`: <https://p5js.org/reference/#/p5/createCheckbox>) to highlight near-zero-amplitude nodal lines.
If you’d like, I can next add an optional “theoretical overlay” (e.g., predicted nodal lines for equal-frequency sources) or performance tweaks (e.g., adaptive fieldScale) for heavier screens.
---
```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI Wave Function Simulator</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="controls">
<h1>AI Wave Function Simulator</h1>
</div>
<!-- p5.js core library -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- main sketch -->
<script src="sketch.js"></script>
</body>
</html>
```
---
```css
/* style.css */
body {
margin: 0;
padding: 0;
overflow: hidden;
background: radial-gradient(circle at top, #101528 0, #050811 60%);
color: #f0f4ff;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
canvas {
display: block;
}
/* Floating UI panel */
#controls {
position: absolute;
top: 12px;
left: 12px;
z-index: 10;
padding: 10px 12px;
border-radius: 10px;
background: rgba(5, 8, 20, 0.82);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
#controls h1 {
margin: 0 0 8px;
font-size: 16px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #ffffff;
}
/* Rows for controls built with p5's createDiv/createSlider/etc */
.control-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: 12px;
}
.control-row:last-child {
margin-bottom: 0;
}
.label {
min-width: 74px;
}
.unit {
opacity: 0.7;
}
/* Sliders */
#controls input[type="range"] {
width: 140px;
}
/* Buttons */
#controls button {
background: #1b6fff;
color: #ffffff;
border: none;
border-radius: 999px;
padding: 4px 10px;
font-size: 12px;
cursor: pointer;
}
#controls button:hover {
background: #3c8dff;
}
#controls button:active {
background: #1457cc;
}
/* Checkbox */
#controls input[type="checkbox"] {
margin-right: 4px;
}
```
---
```js
// sketch.js
// --- Simulation resolution ---
let fieldG; // off-screen buffer for the wave field
const fieldScale = 2; // simulation is 1/fieldScale of canvas size
// --- Wave sources ---
let source1, source2;
const sourceRadius = 14;
// --- UI elements (p5 DOM) ---
let frequencySlider, wavelengthSlider;
let playPauseButton, resetButton, nodesCheckbox;
// --- Wave parameters ---
let frequency;
let wavelength;
const defaultFrequency = 1.0; // Hz (visual)
const defaultWavelength = 200; // pixels
// --- Simulation state ---
let isPlaying = true;
let showNodes = false;
let simTime = 0; // time in seconds
let lastUpdateMillis = 0; // for dt calculation
function setup() {
createCanvas(windowWidth, windowHeight);
pixelDensity(1);
createFieldGraphics();
initializeSources();
frequency = defaultFrequency;
wavelength = defaultWavelength;
setupUI();
simTime = 0;
lastUpdateMillis = millis();
}
function draw() {
updateTime();
updateParametersFromUI();
const omega = TWO_PI * frequency; // angular frequency
const k = TWO_PI / wavelength; // wave number
renderField(omega, k);
image(fieldG, 0, 0, width, height);
drawSources();
drawOverlayText();
}
// ------------------------
// Initialization & resizing
// ------------------------
function createFieldGraphics() {
const gWidth = Math.max(1, Math.floor(width / fieldScale));
const gHeight = Math.max(1, Math.floor(height / fieldScale));
fieldG = createGraphics(gWidth, gHeight);
fieldG.pixelDensity(1);
}
function initializeSources() {
const midY = height * 0.5;
const spacing = width * 0.18;
const centerX = width * 0.5;
source1 = {
x: centerX - spacing,
y: midY,
isDragging: false,
offsetX: 0,
offsetY: 0
};
source2 = {
x: centerX + spacing,
y: midY,
isDragging: false,
offsetX: 0,
offsetY: 0
};
}
function windowResized() {
const prevW = width;
const prevH = height;
resizeCanvas(windowWidth, windowHeight);
pixelDensity(1);
createFieldGraphics();
const scaleX = prevW > 0 ? width / prevW : 1;
const scaleY = prevH > 0 ? height / prevH : 1;
if (source1 && source2) {
source1.x *= scaleX;
source1.y *= scaleY;
source2.x *= scaleX;
source2.y *= scaleY;
}
}
// ------------------------
// Time & parameters
// ------------------------
function updateTime() {
const now = millis();
if (isPlaying) {
const dt = (now - lastUpdateMillis) / 1000.0;
simTime += dt;
}
lastUpdateMillis = now;
}
function updateParametersFromUI() {
if (frequencySlider) {
frequency = parseFloat(frequencySlider.value());
}
if (wavelengthSlider) {
wavelength = parseFloat(wavelengthSlider.value());
}
}
// ------------------------
// UI setup
// ------------------------
function setupUI() {
const ui = select('#controls');
if (!ui) return;
// Frequency row
const freqRow = createDiv();
freqRow.parent(ui);
freqRow.addClass('control-row');
const freqLabel = createSpan('Frequency');
freqLabel.parent(freqRow);
freqLabel.addClass('label');
// createSlider: https://p5js.org/reference/#/p5/createSlider
frequencySlider = createSlider(0.2, 3.0, defaultFrequency, 0.01);
frequencySlider.parent(freqRow);
const freqUnit = createSpan('Hz');
freqUnit.parent(freqRow);
freqUnit.addClass('unit');
// Wavelength row
const waveRow = createDiv();
waveRow.parent(ui);
waveRow.addClass('control-row');
const waveLabel = createSpan('Wavelength');
waveLabel.parent(waveRow);
waveLabel.addClass('label');
wavelengthSlider = createSlider(60, 400, defaultWavelength, 1);
wavelengthSlider.parent(waveRow);
const waveUnit = createSpan('px');
waveUnit.parent(waveRow);
waveUnit.addClass('unit');
// Buttons row
const buttonRow = createDiv();
buttonRow.parent(ui);
buttonRow.addClass('control-row');
// createButton: https://p5js.org/reference/#/p5/createButton
playPauseButton = createButton('Pause');
playPauseButton.parent(buttonRow);
playPauseButton.mousePressed(togglePlayPause);
resetButton = createButton('Reset');
resetButton.parent(buttonRow);
resetButton.mousePressed(resetSimulation);
// Nodes checkbox row
const nodesRow = createDiv();
nodesRow.parent(ui);
nodesRow.addClass('control-row');
// createCheckbox: https://p5js.org/reference/#/p5/createCheckbox
nodesCheckbox = createCheckbox('Show nodes', showNodes);
nodesCheckbox.parent(nodesRow);
nodesCheckbox.changed(() => {
showNodes = nodesCheckbox.checked();
});
}
function togglePlayPause() {
isPlaying = !isPlaying;
if (playPauseButton) {
playPauseButton.html(isPlaying ? 'Pause' : 'Play');
}
}
function resetSimulation() {
initializeSources();
if (frequencySlider) frequencySlider.value(defaultFrequency);
if (wavelengthSlider) wavelengthSlider.value(defaultWavelength);
frequency = defaultFrequency;
wavelength = defaultWavelength;
simTime = 0;
isPlaying = true;
if (playPauseButton) {
playPauseButton.html('Pause');
}
}
// ------------------------
// Rendering the wave field
// ------------------------
function renderField(omega, k) {
if (!fieldG) return;
const g = fieldG;
g.loadPixels();
const w = g.width;
const h = g.height;
const simW = width;
const simH = height;
const maxAmp = 2.0; // max |A| for two waves of unit amplitude
const nodeThreshold = 0.06; // where we consider amplitude to be "node-like"
for (let y = 0; y < h; y++) {
const vy = ((y + 0.5) / h) * simH;
for (let x = 0; x < w; x++) {
const vx = ((x + 0.5) / w) * simW;
// Distance to each source for circular wavefronts
const dx1 = vx - source1.x;
const dy1 = vy - source1.y;
const d1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
const dx2 = vx - source2.x;
const dy2 = vy - source2.y;
const d2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
// Wave phase: k r - ω t
const phase1 = k * d1 - omega * simTime;
const phase2 = k * d2 - omega * simTime;
const a1 = Math.sin(phase1);
const a2 = Math.sin(phase2);
const A = a1 + a2;
const ampAbs = Math.abs(A);
let r, gCol, bCol;
if (showNodes && ampAbs < nodeThreshold) {
// Highlight nodes (near-zero amplitude)
r = 255;
gCol = 200;
bCol = 0;
} else {
// Map |A| from 0..maxAmp to brightness 0..1
const m = Math.min(ampAbs / maxAmp, 1.0);
const brightness = Math.pow(m, 0.6); // gamma for contrast
// Dark blue -> white gradient
const baseR = 0;
const baseG = 10;
const baseB = 35;
r = baseR + (255 - baseR) * brightness;
gCol = baseG + (255 - baseG) * brightness;
bCol = baseB + (255 - baseB) * brightness;
}
const idx = 4 * (y * w + x);
g.pixels[idx + 0] = r;
g.pixels[idx + 1] = gCol;
g.pixels[idx + 2] = bCol;
g.pixels[idx + 3] = 255;
}
}
g.updatePixels();
}
// ------------------------
// Source visuals & HUD
// ------------------------
function drawSources() {
stroke(255);
strokeWeight(2);
fill(0, 180, 255, 180);
const d = sourceRadius * 2;
ellipse(source1.x, source1.y, d, d);
ellipse(source2.x, source2.y, d, d);
noStroke();
fill(255);
textAlign(CENTER, CENTER);
textSize(12);
text('A', source1.x, source1.y);
text('B', source2.x, source2.y);
}
function drawOverlayText() {
noStroke();
fill(255, 230);
textAlign(LEFT, BOTTOM);
textSize(12);
const freqStr = 'f = ' + nf(frequency, 1, 2) + ' Hz';
const lambdaStr = 'λ = ' + nf(wavelength, 3, 0) + ' px';
const stateStr = isPlaying ? 'Running' : 'Paused';
text(freqStr + ' ' + lambdaStr + ' ' + stateStr, 16, height - 16);
}
// ------------------------
// Interaction: dragging sources
// ------------------------
function isMouseInsideCanvas() {
return mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height;
}
function mousePressed() {
if (!isMouseInsideCanvas()) return;
if (!source1 || !source2) return;
const d1 = dist(mouseX, mouseY, source1.x, source1.y);
const d2 = dist(mouseX, mouseY, source2.x, source2.y);
if (d1 <= sourceRadius + 6) {
source1.isDragging = true;
source1.offsetX = mouseX - source1.x;
source1.offsetY = mouseY - source1.y;
} else if (d2 <= sourceRadius + 6) {
source2.isDragging = true;
source2.offsetX = mouseX - source2.x;
source2.offsetY = mouseY - source2.y;
}
}
function mouseDragged() {
if (!isMouseInsideCanvas()) return;
if (source1 && source1.isDragging) {
source1.x = constrain(mouseX - source1.offsetX, 0, width);
source1.y = constrain(mouseY - source1.offsetY, 0, height);
} else if (source2 && source2.isDragging) {
source2.x = constrain(mouseX - source2.offsetX, 0, width);
source2.y = constrain(mouseY - source2.offsetY, 0, height);
}
}
function mouseReleased() {
if (source1) source1.isDragging = false;
if (source2) source2.isDragging = false;
}
```
---
Usage notes:
- Drag the labeled circles “A” and “B” to move the point sources.
- Adjust the **Frequency** and **Wavelength** sliders (built with `createSlider`: <https://p5js.org/reference/#/p5/createSlider>) to change temporal and spatial characteristics.
- Click **Pause/Play** (`createButton`: <https://p5js.org/reference/#/p5/createButton>) to freeze or resume time evolution.
- Click **Reset** to restore default positions and parameters.
- Toggle **Show nodes** (`createCheckbox`: <https://p5js.org/reference/#/p5/createCheckbox>) to highlight near-zero-amplitude nodal lines.
If you’d like, I can next add an optional “theoretical overlay” (e.g., predicted nodal lines for equal-frequency sources) or performance tweaks (e.g., adaptive fieldScale) for heavier screens.
👤 You
Generate wave interference simulation: Create two draggable point sources. For each pixel, calculate distance to both sources, compute sin waves with phase based on distance, sum the amplitudes for interference. Use blue color with brightness based on combined amplitude. Add frequency slider and play/pause button.
🤖 AI
Here’s a complete implementation matching your description: two draggable point sources, per-pixel interference using distances, blue brightness based on combined amplitude, plus a frequency slider and play/pause button.
---
```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wave Interference Simulation</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="controls">
<div id="title">Wave Interference</div>
</div>
<!-- p5.js core library -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- main sketch -->
<script src="sketch.js"></script>
</body>
</html>
```
---
```css
/* style.css */
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #000814;
color: #e0e8ff;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
canvas {
display: block;
}
/* Simple floating UI */
#controls {
position: absolute;
top: 10px;
left: 10px;
z-index: 10;
padding: 8px 10px;
border-radius: 8px;
background: rgba(0, 8, 20, 0.85);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
font-size: 12px;
}
#title {
font-weight: 600;
margin-bottom: 6px;
}
/* Rows for p5 controls */
.control-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
}
.control-row:last-child {
margin-bottom: 0;
}
.label {
min-width: 70px;
}
/* Sliders created with p5: https://p5js.org/reference/#/p5/createSlider */
#controls input[type="range"] {
width: 140px;
}
/* Buttons created with p5: https://p5js.org/reference/#/p5/createButton */
#controls button {
background: #1b6fff;
color: #ffffff;
border: none;
border-radius: 999px;
padding: 3px 10px;
font-size: 12px;
cursor: pointer;
}
#controls button:hover {
background: #3c8dff;
}
#controls button:active {
background: #1457cc;
}
```
---
```js
// sketch.js
// Off-screen field buffer (lower resolution for performance)
let fieldG;
const fieldScale = 2; // simulation resolution = canvas / fieldScale
// Point sources
let source1, source2;
const sourceRadius = 14;
// UI
let frequencySlider, frequencyLabelSpan, playPauseButton;
// Wave parameters
const defaultFrequency = 1.0; // Hz (visual)
const minFrequency = 0.2;
const maxFrequency = 3.0;
const wavelength = 150; // pixels (fixed)
let frequency = defaultFrequency;
// Time
let timeSec = 0;
let lastMillis = 0;
let isPlaying = true;
function setup() {
createCanvas(windowWidth, windowHeight);
pixelDensity(1); // make per-pixel math predictable
createFieldGraphics();
initSources();
setupUI();
timeSec = 0;
lastMillis = millis();
}
function draw() {
updateTime();
// Update frequency from slider
if (frequencySlider) {
frequency = parseFloat(frequencySlider.value());
if (frequencyLabelSpan) {
frequencyLabelSpan.html(nf(frequency, 1, 2) + ' Hz');
}
}
background(0);
renderField(); // compute interference pattern
image(fieldG, 0, 0, width, height); // scale buffer onto canvas
drawSources();
}
// -------------------------
// Setup helpers
// -------------------------
function createFieldGraphics() {
const gWidth = max(1, floor(width / fieldScale));
const gHeight = max(1, floor(height / fieldScale));
fieldG = createGraphics(gWidth, gHeight);
fieldG.pixelDensity(1);
}
function initSources() {
const midY = height * 0.5;
const spacing = width * 0.18;
const centerX = width * 0.5;
source1 = {
x: centerX - spacing,
y: midY,
isDragging: false,
offsetX: 0,
offsetY: 0
};
source2 = {
x: centerX + spacing,
y: midY,
isDragging: false,
offsetX: 0,
offsetY: 0
};
}
function setupUI() {
const panel = select('#controls');
if (!panel) return;
// Frequency row
const freqRow = createDiv();
freqRow.parent(panel);
freqRow.addClass('control-row');
const freqLabel = createSpan('Frequency');
freqLabel.parent(freqRow);
freqLabel.addClass('label');
// createSlider: https://p5js.org/reference/#/p5/createSlider
frequencySlider = createSlider(minFrequency, maxFrequency, defaultFrequency, 0.01);
frequencySlider.parent(freqRow);
frequencyLabelSpan = createSpan(nf(defaultFrequency, 1, 2) + ' Hz');
frequencyLabelSpan.parent(freqRow);
// Play / Pause button
const buttonRow = createDiv();
buttonRow.parent(panel);
buttonRow.addClass('control-row');
// createButton: https://p5js.org/reference/#/p5/createButton
playPauseButton = createButton('Pause');
playPauseButton.parent(buttonRow);
playPauseButton.mousePressed(togglePlayPause);
}
// -------------------------
// Time and animation control
// -------------------------
function updateTime() {
const now = millis();
if (isPlaying) {
const dt = (now - lastMillis) / 1000.0;
timeSec += dt;
}
lastMillis = now;
}
function togglePlayPause() {
isPlaying = !isPlaying;
if (playPauseButton) {
playPauseButton.html(isPlaying ? 'Pause' : 'Play');
}
}
// -------------------------
// Wave field rendering
// -------------------------
function renderField() {
if (!fieldG) return;
const g = fieldG;
g.loadPixels(); // https://p5js.org/reference/#/p5.Image/loadPixels
const gw = g.width;
const gh = g.height;
const simW = width;
const simH = height;
const omega = TWO_PI * frequency; // angular frequency
const k = TWO_PI / wavelength; // wave number
const maxAmp = 2.0; // max |A| for two unit waves
for (let y = 0; y < gh; y++) {
const vy = ((y + 0.5) / gh) * simH;
for (let x = 0; x < gw; x++) {
const vx = ((x + 0.5) / gw) * simW;
// Distance to source 1
const dx1 = vx - source1.x;
const dy1 = vy - source1.y;
const d1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
// Distance to source 2
const dx2 = vx - source2.x;
const dy2 = vy - source2.y;
const d2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
// Phase = k * distance - omega * t
const phase1 = k * d1 - omega * timeSec;
const phase2 = k * d2 - omega * timeSec;
// Wave amplitudes from each source
const a1 = Math.sin(phase1);
const a2 = Math.sin(phase2);
// Combined amplitude (interference)
const A = a1 + a2;
const ampAbs = Math.abs(A);
// Map |A| from 0..maxAmp to brightness 0..1
const b = constrain(ampAbs / maxAmp, 0, 1);
const blue = b * 255;
const idx = 4 * (y * gw + x);
g.pixels[idx + 0] = 0; // R
g.pixels[idx + 1] = 0; // G
g.pixels[idx + 2] = blue; // B
g.pixels[idx + 3] = 255; // A
}
}
g.updatePixels(); // https://p5js.org/reference/#/p5.Image/updatePixels
}
// -------------------------
// Source visuals
// -------------------------
function drawSources() {
stroke(255);
strokeWeight(2);
fill(80, 160, 255, 200);
const d = sourceRadius * 2;
ellipse(source1.x, source1.y, d, d);
ellipse(source2.x, source2.y, d, d);
noStroke();
fill(255);
textAlign(CENTER, CENTER);
textSize(12);
text('A', source1.x, source1.y);
text('B', source2.x, source2.y);
}
// -------------------------
// Interaction: dragging sources
// -------------------------
function mousePressed() {
if (!insideCanvas(mouseX, mouseY)) return;
const d1 = dist(mouseX, mouseY, source1.x, source1.y);
const d2 = dist(mouseX, mouseY, source2.x, source2.y);
if (d1 <= sourceRadius + 6) {
source1.isDragging = true;
source1.offsetX = mouseX - source1.x;
source1.offsetY = mouseY - source1.y;
} else if (d2 <= sourceRadius + 6) {
source2.isDragging = true;
source2.offsetX = mouseX - source2.x;
source2.offsetY = mouseY - source2.y;
}
}
function mouseDragged() {
if (!insideCanvas(mouseX, mouseY)) return;
if (source1.isDragging) {
source1.x = constrain(mouseX - source1.offsetX, 0, width);
source1.y = constrain(mouseY - source1.offsetY, 0, height);
} else if (source2.isDragging) {
source2.x = constrain(mouseX - source2.offsetX, 0, width);
source2.y = constrain(mouseY - source2.offsetY, 0, height);
}
}
function mouseReleased() {
source1.isDragging = false;
source2.isDragging = false;
}
function insideCanvas(x, y) {
return x >= 0 && x <= width && y >= 0 && y <= height;
}
// -------------------------
// Handle window resize
// -------------------------
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
pixelDensity(1);
createFieldGraphics();
}
```
This gives you:
- Two draggable point sources “A” and “B”
- For each pixel: distance to both sources → phase → `sin` waves → summed amplitude
- Blue intensity proportional to the combined amplitude (constructive interference = bright blue, destructive = dark)
- Frequency slider (Hz) and play/pause button using p5 DOM APIs
If you want next steps (e.g., adjustable wavelength or an option to show nodal lines), I can extend this version.
---
```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wave Interference Simulation</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="controls">
<div id="title">Wave Interference</div>
</div>
<!-- p5.js core library -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- main sketch -->
<script src="sketch.js"></script>
</body>
</html>
```
---
```css
/* style.css */
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #000814;
color: #e0e8ff;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
canvas {
display: block;
}
/* Simple floating UI */
#controls {
position: absolute;
top: 10px;
left: 10px;
z-index: 10;
padding: 8px 10px;
border-radius: 8px;
background: rgba(0, 8, 20, 0.85);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
font-size: 12px;
}
#title {
font-weight: 600;
margin-bottom: 6px;
}
/* Rows for p5 controls */
.control-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
}
.control-row:last-child {
margin-bottom: 0;
}
.label {
min-width: 70px;
}
/* Sliders created with p5: https://p5js.org/reference/#/p5/createSlider */
#controls input[type="range"] {
width: 140px;
}
/* Buttons created with p5: https://p5js.org/reference/#/p5/createButton */
#controls button {
background: #1b6fff;
color: #ffffff;
border: none;
border-radius: 999px;
padding: 3px 10px;
font-size: 12px;
cursor: pointer;
}
#controls button:hover {
background: #3c8dff;
}
#controls button:active {
background: #1457cc;
}
```
---
```js
// sketch.js
// Off-screen field buffer (lower resolution for performance)
let fieldG;
const fieldScale = 2; // simulation resolution = canvas / fieldScale
// Point sources
let source1, source2;
const sourceRadius = 14;
// UI
let frequencySlider, frequencyLabelSpan, playPauseButton;
// Wave parameters
const defaultFrequency = 1.0; // Hz (visual)
const minFrequency = 0.2;
const maxFrequency = 3.0;
const wavelength = 150; // pixels (fixed)
let frequency = defaultFrequency;
// Time
let timeSec = 0;
let lastMillis = 0;
let isPlaying = true;
function setup() {
createCanvas(windowWidth, windowHeight);
pixelDensity(1); // make per-pixel math predictable
createFieldGraphics();
initSources();
setupUI();
timeSec = 0;
lastMillis = millis();
}
function draw() {
updateTime();
// Update frequency from slider
if (frequencySlider) {
frequency = parseFloat(frequencySlider.value());
if (frequencyLabelSpan) {
frequencyLabelSpan.html(nf(frequency, 1, 2) + ' Hz');
}
}
background(0);
renderField(); // compute interference pattern
image(fieldG, 0, 0, width, height); // scale buffer onto canvas
drawSources();
}
// -------------------------
// Setup helpers
// -------------------------
function createFieldGraphics() {
const gWidth = max(1, floor(width / fieldScale));
const gHeight = max(1, floor(height / fieldScale));
fieldG = createGraphics(gWidth, gHeight);
fieldG.pixelDensity(1);
}
function initSources() {
const midY = height * 0.5;
const spacing = width * 0.18;
const centerX = width * 0.5;
source1 = {
x: centerX - spacing,
y: midY,
isDragging: false,
offsetX: 0,
offsetY: 0
};
source2 = {
x: centerX + spacing,
y: midY,
isDragging: false,
offsetX: 0,
offsetY: 0
};
}
function setupUI() {
const panel = select('#controls');
if (!panel) return;
// Frequency row
const freqRow = createDiv();
freqRow.parent(panel);
freqRow.addClass('control-row');
const freqLabel = createSpan('Frequency');
freqLabel.parent(freqRow);
freqLabel.addClass('label');
// createSlider: https://p5js.org/reference/#/p5/createSlider
frequencySlider = createSlider(minFrequency, maxFrequency, defaultFrequency, 0.01);
frequencySlider.parent(freqRow);
frequencyLabelSpan = createSpan(nf(defaultFrequency, 1, 2) + ' Hz');
frequencyLabelSpan.parent(freqRow);
// Play / Pause button
const buttonRow = createDiv();
buttonRow.parent(panel);
buttonRow.addClass('control-row');
// createButton: https://p5js.org/reference/#/p5/createButton
playPauseButton = createButton('Pause');
playPauseButton.parent(buttonRow);
playPauseButton.mousePressed(togglePlayPause);
}
// -------------------------
// Time and animation control
// -------------------------
function updateTime() {
const now = millis();
if (isPlaying) {
const dt = (now - lastMillis) / 1000.0;
timeSec += dt;
}
lastMillis = now;
}
function togglePlayPause() {
isPlaying = !isPlaying;
if (playPauseButton) {
playPauseButton.html(isPlaying ? 'Pause' : 'Play');
}
}
// -------------------------
// Wave field rendering
// -------------------------
function renderField() {
if (!fieldG) return;
const g = fieldG;
g.loadPixels(); // https://p5js.org/reference/#/p5.Image/loadPixels
const gw = g.width;
const gh = g.height;
const simW = width;
const simH = height;
const omega = TWO_PI * frequency; // angular frequency
const k = TWO_PI / wavelength; // wave number
const maxAmp = 2.0; // max |A| for two unit waves
for (let y = 0; y < gh; y++) {
const vy = ((y + 0.5) / gh) * simH;
for (let x = 0; x < gw; x++) {
const vx = ((x + 0.5) / gw) * simW;
// Distance to source 1
const dx1 = vx - source1.x;
const dy1 = vy - source1.y;
const d1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
// Distance to source 2
const dx2 = vx - source2.x;
const dy2 = vy - source2.y;
const d2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
// Phase = k * distance - omega * t
const phase1 = k * d1 - omega * timeSec;
const phase2 = k * d2 - omega * timeSec;
// Wave amplitudes from each source
const a1 = Math.sin(phase1);
const a2 = Math.sin(phase2);
// Combined amplitude (interference)
const A = a1 + a2;
const ampAbs = Math.abs(A);
// Map |A| from 0..maxAmp to brightness 0..1
const b = constrain(ampAbs / maxAmp, 0, 1);
const blue = b * 255;
const idx = 4 * (y * gw + x);
g.pixels[idx + 0] = 0; // R
g.pixels[idx + 1] = 0; // G
g.pixels[idx + 2] = blue; // B
g.pixels[idx + 3] = 255; // A
}
}
g.updatePixels(); // https://p5js.org/reference/#/p5.Image/updatePixels
}
// -------------------------
// Source visuals
// -------------------------
function drawSources() {
stroke(255);
strokeWeight(2);
fill(80, 160, 255, 200);
const d = sourceRadius * 2;
ellipse(source1.x, source1.y, d, d);
ellipse(source2.x, source2.y, d, d);
noStroke();
fill(255);
textAlign(CENTER, CENTER);
textSize(12);
text('A', source1.x, source1.y);
text('B', source2.x, source2.y);
}
// -------------------------
// Interaction: dragging sources
// -------------------------
function mousePressed() {
if (!insideCanvas(mouseX, mouseY)) return;
const d1 = dist(mouseX, mouseY, source1.x, source1.y);
const d2 = dist(mouseX, mouseY, source2.x, source2.y);
if (d1 <= sourceRadius + 6) {
source1.isDragging = true;
source1.offsetX = mouseX - source1.x;
source1.offsetY = mouseY - source1.y;
} else if (d2 <= sourceRadius + 6) {
source2.isDragging = true;
source2.offsetX = mouseX - source2.x;
source2.offsetY = mouseY - source2.y;
}
}
function mouseDragged() {
if (!insideCanvas(mouseX, mouseY)) return;
if (source1.isDragging) {
source1.x = constrain(mouseX - source1.offsetX, 0, width);
source1.y = constrain(mouseY - source1.offsetY, 0, height);
} else if (source2.isDragging) {
source2.x = constrain(mouseX - source2.offsetX, 0, width);
source2.y = constrain(mouseY - source2.offsetY, 0, height);
}
}
function mouseReleased() {
source1.isDragging = false;
source2.isDragging = false;
}
function insideCanvas(x, y) {
return x >= 0 && x <= width && y >= 0 && y <= height;
}
// -------------------------
// Handle window resize
// -------------------------
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
pixelDensity(1);
createFieldGraphics();
}
```
This gives you:
- Two draggable point sources “A” and “B”
- For each pixel: distance to both sources → phase → `sin` waves → summed amplitude
- Blue intensity proportional to the combined amplitude (constructive interference = bright blue, destructive = dark)
- Frequency slider (Hz) and play/pause button using p5 DOM APIs
If you want next steps (e.g., adjustable wavelength or an option to show nodal lines), I can extend this version.