Spaces:
Running
Running
File size: 6,351 Bytes
835dae2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
// Interactive Travel-Time Curve
const ttCanvas = document.getElementById('travelTimeCanvas');
const ttCtx = ttCanvas.getContext('2d');
let receiverX = 100;
let isDragging = false;
const Vp = 200; // P-wave velocity m/s
const Vs = 120; // S-wave velocity m/s
const Vr = 100; // Rayleigh-wave velocity m/s
function drawTravelTime() {
const w = ttCanvas.width;
const h = ttCanvas.height;
ttCtx.clearRect(0, 0, w, h);
// Part 1: Survey Diagram (Top half)
const groundY = h * 0.2;
ttCtx.fillStyle = '#8b4513';
ttCtx.fillRect(0, groundY, w, h*0.3);
ttCtx.fillStyle = '#add8e6';
ttCtx.fillRect(0, 0, w, groundY);
// Source
ttCtx.fillStyle = 'red';
ttCtx.beginPath();
ttCtx.arc(50, groundY, 10, 0, Math.PI * 2);
ttCtx.fill();
ttCtx.fillText('Source', 40, groundY - 15);
// Receiver
ttCtx.fillStyle = 'blue';
ttCtx.fillRect(receiverX - 5, groundY - 20, 10, 20);
ttCtx.fillText('Receiver', receiverX - 25, groundY - 25);
// Part 2: Travel-Time Graph (Bottom half)
const graphOriginX = 50;
const graphOriginY = h * 0.5;
const graphHeight = h * 0.45;
const graphWidth = w - 60;
// Axes
ttCtx.strokeStyle = 'black';
ttCtx.beginPath();
ttCtx.moveTo(graphOriginX, graphOriginY);
ttCtx.lineTo(graphOriginX, graphOriginY + graphHeight);
ttCtx.lineTo(graphOriginX + graphWidth, graphOriginY + graphHeight);
ttCtx.stroke();
ttCtx.fillText('Time (T)', graphOriginX - 40, graphOriginY + graphHeight / 2);
ttCtx.fillText('Distance (X)', graphOriginX + graphWidth / 2, graphOriginY + graphHeight + 20);
// Plot lines
const plotX = receiverX - 50;
const maxDist = w - 100;
function plotWave(v, color) {
ttCtx.strokeStyle = color;
ttCtx.lineWidth = 2;
ttCtx.beginPath();
ttCtx.moveTo(graphOriginX, graphOriginY + graphHeight);
// Slope = 1/V. T = X/V. Y_pixel = T * scale_Y. X_pixel = X
// We invert T axis, so T=0 is at the top
let endT = maxDist / v;
let scaleY = graphHeight / (maxDist/Vr); // Scale based on slowest wave
let endY = graphOriginY + graphHeight - (endT * scaleY);
let endX = graphOriginX + maxDist;
// The line in the graph is T vs X, so Y axis is T.
// Y = origin + H - (T*scale)
// T = X/V
// Y = origin + H - (X/V * scaleY)
ttCtx.moveTo(graphOriginX, graphOriginY + graphHeight);
ttCtx.lineTo(graphOriginX+maxDist, graphOriginY+graphHeight - (maxDist/v * scaleY) );
ttCtx.stroke();
// Plot point for current receiver
let currentT = plotX / v;
let pointY = graphOriginY + graphHeight - (currentT * scaleY);
ttCtx.fillStyle = color;
ttCtx.beginPath();
ttCtx.arc(graphOriginX + plotX, pointY, 5, 0, Math.PI * 2);
ttCtx.fill();
}
plotWave(Vp, '#d90429');
plotWave(Vs, '#0077b6');
plotWave(Vr, '#2d6a4f');
ttCtx.lineWidth = 1;
}
ttCanvas.addEventListener('mousedown', (e) => {
isDragging = true;
});
ttCanvas.addEventListener('mouseup', () => {
isDragging = false;
});
ttCanvas.addEventListener('mouseleave', () => {
isDragging = false;
});
ttCanvas.addEventListener('mousemove', (e) => {
if (isDragging) {
const rect = ttCanvas.getBoundingClientRect();
let x = e.clientX - rect.left;
if (x > 50 && x < ttCanvas.width - 50) {
receiverX = x;
drawTravelTime();
}
}
});
// Interactive Refraction
const refCanvas = document.getElementById('refractionCanvas');
const refCtx = refCanvas.getContext('2d');
const slider = document.getElementById('v2-slider');
const v2ValueSpan = document.getElementById('v2-value');
const V1 = 1000;
function drawRefraction() {
const w = refCanvas.width;
const h = refCanvas.height;
refCtx.clearRect(0, 0, w, h);
const V2 = slider.value;
v2ValueSpan.textContent = V2;
const interfaceY = h / 2;
// Layers
refCtx.fillStyle = '#f0e68c'; // Layer 1
refCtx.fillRect(0, 0, w, interfaceY);
refCtx.fillStyle = '#d2b48c'; // Layer 2
refCtx.fillRect(0, interfaceY, w, h);
refCtx.fillText(`V₁ = ${V1} m/s`, 10, 20);
refCtx.fillText(`V₂ = ${V2} m/s`, 10, interfaceY + 20);
// Incident Ray
const source = { x: 100, y: 0 };
const incidentPoint = { x: 250, y: interfaceY };
const angle1 = Math.atan((incidentPoint.x - source.x) / incidentPoint.y);
refCtx.strokeStyle = 'black';
refCtx.lineWidth = 2;
refCtx.beginPath();
refCtx.moveTo(source.x, source.y);
refCtx.lineTo(incidentPoint.x, incidentPoint.y);
refCtx.stroke();
// Refracted Ray (Snell's Law: sin(theta1)/V1 = sin(theta2)/V2)
const sin_theta2 = (V2 / V1) * Math.sin(angle1);
if (sin_theta2 < 1) { // No critical refraction yet
const angle2 = Math.asin(sin_theta2);
const endX = incidentPoint.x + (h / 2) * Math.tan(angle2);
const endY = h;
refCtx.beginPath();
refCtx.moveTo(incidentPoint.x, incidentPoint.y);
refCtx.lineTo(endX, endY);
refCtx.stroke();
// Draw angles
refCtx.strokeStyle = 'rgba(0,0,0,0.5)';
refCtx.lineWidth = 1;
refCtx.beginPath();
refCtx.moveTo(incidentPoint.x, incidentPoint.y-30);
refCtx.lineTo(incidentPoint.x, incidentPoint.y+30);
refCtx.stroke();
refCtx.beginPath();
refCtx.arc(incidentPoint.x, incidentPoint.y, 20, Math.PI/2 - angle1, Math.PI/2);
refCtx.stroke();
refCtx.fillText('θ₁', incidentPoint.x - 25, incidentPoint.y-5);
refCtx.beginPath();
refCtx.arc(incidentPoint.x, incidentPoint.y, 20, Math.PI/2, Math.PI/2 + angle2);
refCtx.stroke();
refCtx.fillText('θ₂', incidentPoint.x - 25, incidentPoint.y+20);
} else { // Critical refraction
refCtx.beginPath();
refCtx.moveTo(incidentPoint.x, incidentPoint.y);
refCtx.lineTo(w, incidentPoint.y);
refCtx.stroke();
refCtx.fillStyle = 'red';
refCtx.fillText('Critical Refraction!', incidentPoint.x + 10, incidentPoint.y-10);
}
}
slider.addEventListener('input', drawRefraction);
// Initial draws
window.onload = () => {
drawTravelTime();
drawRefraction();
};
|