Spaces:
Running
Running
| /** | |
| * main.js — Application entry point, animation loop, module coordination. | |
| * Loaded as a plain script after all other modules. | |
| */ | |
| (function () { | |
| 'use strict'; | |
| // Default simulation parameters | |
| var NX = 800; | |
| var NY = 600; | |
| var DX = 1.0; | |
| var DT = 0.4; | |
| var C = 1.0; | |
| var DAMPING_WIDTH = 50; | |
| var DEFAULT_WAVELENGTH = 20; | |
| var DEFAULT_SLIT_WIDTH = 40; // in grid cells | |
| var DEFAULT_SLIT_SEPARATION = 80; // in grid cells | |
| var BARRIER_COL = Math.floor(600 * 0.35); // 210 | |
| var DETECTOR_COL = Math.floor(600 * 0.55); // 330 — initial position | |
| var STEPS_PER_FRAME = 3; | |
| var HEIGHT_SCALE = 5.0; | |
| // Module instances | |
| var solver = null; | |
| var solver2 = null; // Second solver for orthogonal polarization (slit 2 only) | |
| var sceneManager = null; | |
| var waveSurface = null; | |
| var intensityPanel = null; | |
| var controlPanel = null; | |
| var useOrthogonalPol = false; | |
| var usePolarizer45 = false; | |
| var polarizerMesh = null; | |
| var POLARIZER_COL = 0; // Will be set between barrier and detector | |
| var slitFilterGroup = null; // Yellow/green filter planes at slits | |
| var currentAmplitude = 2.0; // Track current amplitude for element heights | |
| var colorGradient = null; | |
| var playing = false; | |
| /** | |
| * Build a BarrierConfig from the current control panel params. | |
| * @param {SimParams} params | |
| * @returns {BarrierConfig} | |
| */ | |
| function getElementHeight() { | |
| return currentAmplitude * HEIGHT_SCALE * 1.5 + 5; | |
| } | |
| function buildBarrierConfig(params) { | |
| var config = { | |
| mode: params.mode, | |
| barrierCol: BARRIER_COL, | |
| slitWidth: Math.round(params.slitWidth), | |
| slitSeparation: Math.round(params.slitSeparation), | |
| ny: NY | |
| }; | |
| // If in double-slit mode and one slit is closed, switch to single-slit | |
| // positioned at the open slit's location | |
| if (params.mode === 'double') { | |
| var topOpen = params.topSlitOpen !== false; | |
| var bottomOpen = params.bottomSlitOpen !== false; | |
| if (!topOpen && !bottomOpen) { | |
| // Both closed: full barrier (use single slit with width 0) | |
| config.mode = 'single'; | |
| config.slitWidth = 0; | |
| } else if (!topOpen) { | |
| // Only bottom slit open | |
| config.mode = 'single'; | |
| var centerY = Math.floor(NY / 2); | |
| var halfSep = Math.floor(config.slitSeparation / 2); | |
| config._slitCenter = centerY + halfSep; | |
| } else if (!bottomOpen) { | |
| // Only top slit open | |
| config.mode = 'single'; | |
| var centerY = Math.floor(NY / 2); | |
| var halfSep = Math.floor(config.slitSeparation / 2); | |
| config._slitCenter = centerY - halfSep; | |
| } | |
| } | |
| return config; | |
| } | |
| /** | |
| * Build a barrier config with only slit 1 open (for orthogonal polarization). | |
| */ | |
| function buildSlit1OnlyConfig(params) { | |
| var sw = Math.round(params.slitWidth); | |
| var sep = Math.round(params.slitSeparation); | |
| var centerY = Math.floor(NY / 2); | |
| var halfSep = Math.floor(sep / 2); | |
| // Slit 1 is at centerY - halfSep. Use single-slit mode centered there. | |
| return { | |
| mode: 'single', | |
| barrierCol: BARRIER_COL, | |
| slitWidth: sw, | |
| slitSeparation: 0, | |
| ny: NY, | |
| _slitCenter: centerY - halfSep | |
| }; | |
| } | |
| /** | |
| * Build a barrier config with only slit 2 open (for orthogonal polarization). | |
| */ | |
| function buildSlit2OnlyConfig(params) { | |
| var sw = Math.round(params.slitWidth); | |
| var sep = Math.round(params.slitSeparation); | |
| var centerY = Math.floor(NY / 2); | |
| var halfSep = Math.floor(sep / 2); | |
| return { | |
| mode: 'single', | |
| barrierCol: BARRIER_COL, | |
| slitWidth: sw, | |
| slitSeparation: 0, | |
| ny: NY, | |
| _slitCenter: centerY + halfSep | |
| }; | |
| } | |
| // Barrier meshes group — holds individual wall segments | |
| var barrierGroup = null; | |
| /** | |
| * Create or update the 3D barrier in the scene. | |
| * Renders individual red wall segments with gaps for slit openings, | |
| * so the user can clearly see where the slits are. | |
| * @param {BarrierConfig} config | |
| */ | |
| function updateBarrierMesh(config) { | |
| // Remove old barrier group | |
| if (barrierGroup && sceneManager && sceneManager.scene) { | |
| sceneManager.scene.remove(barrierGroup); | |
| barrierGroup.traverse(function (child) { | |
| if (child.geometry) child.geometry.dispose(); | |
| if (child.material) child.material.dispose(); | |
| }); | |
| } | |
| barrierGroup = new THREE.Group(); | |
| var barrierThickness = 2; | |
| var barrierHeight = getElementHeight(); | |
| var surfaceWidth = (NX - 1) * DX; | |
| var surfaceDepth = (NY - 1) * DX; | |
| var barrierX = -surfaceWidth / 2 + config.barrierCol * DX; | |
| var material = new THREE.MeshPhongMaterial({ | |
| color: 0xcc2222, | |
| specular: 0x441111, | |
| shininess: 40 | |
| }); | |
| // Compute wall segments (barrier regions) from the slit config | |
| var centerY = Math.floor(config.ny / 2); | |
| var halfSlit = Math.floor(config.slitWidth / 2); | |
| var segments = []; // each: { startZ, endZ } in grid coords | |
| if (config.mode === 'single') { | |
| var slitStart = centerY - halfSlit; | |
| var slitEnd = slitStart + config.slitWidth; | |
| // Wall below slit | |
| if (slitStart > 0) segments.push({ start: 0, end: slitStart }); | |
| // Wall above slit | |
| if (slitEnd < config.ny) segments.push({ start: slitEnd, end: config.ny }); | |
| } else { | |
| var halfSep = Math.floor(config.slitSeparation / 2); | |
| var slit1Start = (centerY - halfSep) - halfSlit; | |
| var slit1End = slit1Start + config.slitWidth; | |
| var slit2Start = (centerY + halfSep) - halfSlit; | |
| var slit2End = slit2Start + config.slitWidth; | |
| var topOpen = config.topSlitOpen !== false; | |
| var bottomOpen = config.bottomSlitOpen !== false; | |
| if (!topOpen && !bottomOpen) { | |
| // Both closed: full wall | |
| segments.push({ start: 0, end: config.ny }); | |
| } else if (!topOpen) { | |
| // Top slit closed: wall from 0 to slit2Start, then slit2End to ny | |
| if (slit2Start > 0) segments.push({ start: 0, end: slit2Start }); | |
| if (slit2End < config.ny) segments.push({ start: slit2End, end: config.ny }); | |
| } else if (!bottomOpen) { | |
| // Bottom slit closed: wall from 0 to slit1Start, then slit1End to ny | |
| if (slit1Start > 0) segments.push({ start: 0, end: slit1Start }); | |
| if (slit1End < config.ny) segments.push({ start: slit1End, end: config.ny }); | |
| } else { | |
| // Both open: normal double slit | |
| if (slit1Start > 0) segments.push({ start: 0, end: slit1Start }); | |
| if (slit1End < slit2Start) segments.push({ start: slit1End, end: slit2Start }); | |
| if (slit2End < config.ny) segments.push({ start: slit2End, end: config.ny }); | |
| } | |
| } | |
| // Create a box for each wall segment | |
| for (var i = 0; i < segments.length; i++) { | |
| var seg = segments[i]; | |
| var segLength = (seg.end - seg.start) * DX; | |
| var segCenter = ((seg.start + seg.end) / 2) * DX - surfaceDepth / 2; | |
| var geo = new THREE.BoxGeometry(barrierThickness, barrierHeight, segLength); | |
| var mesh = new THREE.Mesh(geo, material); | |
| mesh.position.set(barrierX, barrierHeight / 2, segCenter); | |
| barrierGroup.add(mesh); | |
| } | |
| if (sceneManager && sceneManager.scene) { | |
| sceneManager.scene.add(barrierGroup); | |
| } | |
| } | |
| /** | |
| * Create or update the 45° polarizer plane in the 3D scene. | |
| * Positioned halfway between barrier and detector. | |
| * @param {boolean} visible | |
| */ | |
| function updatePolarizerMesh(visible) { | |
| if (polarizerMesh && sceneManager && sceneManager.scene) { | |
| sceneManager.scene.remove(polarizerMesh); | |
| if (polarizerMesh.geometry) polarizerMesh.geometry.dispose(); | |
| if (polarizerMesh.material) polarizerMesh.material.dispose(); | |
| polarizerMesh = null; | |
| } | |
| if (!visible) return; | |
| POLARIZER_COL = Math.floor((BARRIER_COL + DETECTOR_COL) / 2); | |
| var surfaceWidth = (NX - 1) * DX; | |
| var surfaceDepth = (NY - 1) * DX; | |
| var worldX = -surfaceWidth / 2 + POLARIZER_COL * DX; | |
| var screenHeight = getElementHeight(); | |
| var geometry = new THREE.PlaneGeometry(surfaceDepth, screenHeight); | |
| var material = new THREE.MeshBasicMaterial({ | |
| color: 0x4488ff, | |
| transparent: true, | |
| opacity: 0.2, | |
| side: THREE.DoubleSide, | |
| depthWrite: false | |
| }); | |
| polarizerMesh = new THREE.Mesh(geometry, material); | |
| polarizerMesh.rotation.y = Math.PI / 2; | |
| polarizerMesh.position.set(worldX, screenHeight / 2 - 5, 0); | |
| if (sceneManager && sceneManager.scene) { | |
| sceneManager.scene.add(polarizerMesh); | |
| } | |
| } | |
| /** | |
| * Create or remove small colored filter planes at each slit opening. | |
| * Yellow for slit 1, green for slit 2. Only shown in orthogonal polarization mode. | |
| * @param {boolean} visible | |
| * @param {object} params - current sim params | |
| */ | |
| function updateSlitFilters(visible, params) { | |
| // Remove old | |
| if (slitFilterGroup && sceneManager && sceneManager.scene) { | |
| sceneManager.scene.remove(slitFilterGroup); | |
| slitFilterGroup.traverse(function (child) { | |
| if (child.geometry) child.geometry.dispose(); | |
| if (child.material) child.material.dispose(); | |
| }); | |
| slitFilterGroup = null; | |
| } | |
| if (!visible || !params) return; | |
| slitFilterGroup = new THREE.Group(); | |
| var sw = Math.round(params.slitWidth); | |
| var sep = Math.round(params.slitSeparation); | |
| var centerY = Math.floor(NY / 2); | |
| var halfSep = Math.floor(sep / 2); | |
| var halfSlit = Math.floor(sw / 2); | |
| var surfaceWidth = (NX - 1) * DX; | |
| var surfaceDepth = (NY - 1) * DX; | |
| var barrierX = -surfaceWidth / 2 + BARRIER_COL * DX; | |
| var filterOffset = 3; // slightly in front of barrier (toward source) | |
| var filterHeight = getElementHeight(); | |
| // Slit 1 center | |
| var slit1CenterY = centerY - halfSep; | |
| var slit1WorldZ = (slit1CenterY * DX) - surfaceDepth / 2; | |
| var filterDepth = sw * DX; | |
| // Yellow filter for slit 1 | |
| var geo1 = new THREE.PlaneGeometry(filterDepth, filterHeight); | |
| var mat1 = new THREE.MeshBasicMaterial({ | |
| color: 0xffcc00, transparent: true, opacity: 0.35, | |
| side: THREE.DoubleSide, depthWrite: false | |
| }); | |
| var filter1 = new THREE.Mesh(geo1, mat1); | |
| filter1.rotation.y = Math.PI / 2; | |
| filter1.position.set(barrierX - filterOffset, filterHeight / 2 - 3, slit1WorldZ); | |
| slitFilterGroup.add(filter1); | |
| // Slit 2 center | |
| var slit2CenterY = centerY + halfSep; | |
| var slit2WorldZ = (slit2CenterY * DX) - surfaceDepth / 2; | |
| // Green filter for slit 2 | |
| var geo2 = new THREE.PlaneGeometry(filterDepth, filterHeight); | |
| var mat2 = new THREE.MeshBasicMaterial({ | |
| color: 0x00cc44, transparent: true, opacity: 0.35, | |
| side: THREE.DoubleSide, depthWrite: false | |
| }); | |
| var filter2 = new THREE.Mesh(geo2, mat2); | |
| filter2.rotation.y = Math.PI / 2; | |
| filter2.position.set(barrierX - filterOffset, filterHeight / 2 - 3, slit2WorldZ); | |
| slitFilterGroup.add(filter2); | |
| if (sceneManager && sceneManager.scene) { | |
| sceneManager.scene.add(slitFilterGroup); | |
| } | |
| } | |
| /** | |
| * Initialise all modules, set default parameters, and start the animation loop. | |
| */ | |
| function init() { | |
| // 1. Get DOM containers | |
| var sceneContainer = document.getElementById('scene-container'); | |
| var intensityContainer = document.getElementById('intensity-container'); | |
| var controlContainer = document.getElementById('control-container'); | |
| // 2. Create FDTD solver | |
| solver = new FDTDSolver({ | |
| nx: NX, | |
| ny: NY, | |
| dx: DX, | |
| dt: DT, | |
| c: C, | |
| dampingWidth: DAMPING_WIDTH | |
| }); | |
| solver.wavelength = DEFAULT_WAVELENGTH; | |
| solver.sourceAmplitude = 2.0; | |
| solver.sourceWidth = 600; | |
| // 2b. Create second solver for orthogonal polarization mode | |
| solver2 = new FDTDSolver({ | |
| nx: NX, ny: NY, dx: DX, dt: DT, c: C, dampingWidth: DAMPING_WIDTH | |
| }); | |
| solver2.wavelength = DEFAULT_WAVELENGTH; | |
| solver2.sourceAmplitude = 2.0; | |
| solver2.sourceWidth = 600; | |
| // 3. Create and init SceneManager | |
| sceneManager = new SceneManager(); | |
| sceneManager.init(sceneContainer, { | |
| nx: NX, | |
| ny: NY, | |
| detectorCol: DETECTOR_COL | |
| }); | |
| // 4. Create and init WaveSurface, add mesh to scene | |
| waveSurface = new WaveSurface(); | |
| var surfaceMesh = waveSurface.init(NX, NY, DX); | |
| if (sceneManager.scene && surfaceMesh) { | |
| sceneManager.scene.add(surfaceMesh); | |
| } | |
| // 5. Create color gradient | |
| colorGradient = new ColorGradient(); | |
| // 6. Create and init IntensityPanel | |
| intensityPanel = new IntensityPanel(); | |
| intensityPanel.init(intensityContainer, NY); | |
| // 7. Create and init ControlPanel | |
| controlPanel = new ControlPanel(); | |
| var controlInner = document.getElementById('control-inner') || controlContainer; | |
| controlPanel.init(controlInner); | |
| // 8. Build initial barrier — default is double slit, light, orthogonal | |
| var defaultParams = { | |
| mode: 'double', | |
| slitWidth: DEFAULT_SLIT_WIDTH, | |
| slitSeparation: DEFAULT_SLIT_SEPARATION, | |
| wavelength: DEFAULT_WAVELENGTH, | |
| playing: false | |
| }; | |
| // Orthogonal polarization: set up two solvers with one slit each | |
| useOrthogonalPol = true; | |
| solver.setBarrier(buildSlit1OnlyConfig(defaultParams)); | |
| solver2.setBarrier(buildSlit2OnlyConfig(defaultParams)); | |
| // 9. Create 3D barrier mesh | |
| var initVisualConfig = { | |
| mode: 'double', barrierCol: BARRIER_COL, | |
| slitWidth: DEFAULT_SLIT_WIDTH, slitSeparation: DEFAULT_SLIT_SEPARATION, | |
| ny: NY, topSlitOpen: true, bottomSlitOpen: true | |
| }; | |
| updateBarrierMesh(initVisualConfig); | |
| // 9b. Show slit filter planes for orthogonal mode | |
| updateSlitFilters(true, defaultParams); | |
| // Track previous params to detect actual parameter changes vs just play/pause | |
| var prevSimParams = { | |
| mode: 'double', slitWidth: DEFAULT_SLIT_WIDTH, slitSeparation: DEFAULT_SLIT_SEPARATION, | |
| wavelength: DEFAULT_WAVELENGTH, amplitude: 2.0, sourceWidth: 600, detectorPos: DETECTOR_COL, | |
| waveNature: 'light', polarization: 'orthogonal' | |
| }; | |
| // 10. Wire controlPanel.onChange | |
| controlPanel.onChange = function (params) { | |
| // Update solver wavelength and amplitude | |
| solver.wavelength = params.wavelength; | |
| solver.sourceAmplitude = params.amplitude; | |
| solver.sourceWidth = params.sourceWidth; | |
| solver2.wavelength = params.wavelength; | |
| solver2.sourceAmplitude = params.amplitude; | |
| solver2.sourceWidth = params.sourceWidth; | |
| // Track current amplitude for element heights | |
| currentAmplitude = params.amplitude; | |
| // Determine if orthogonal polarization mode | |
| useOrthogonalPol = (params.waveNature === 'light' && params.mode === 'double' && params.polarization === 'orthogonal'); | |
| usePolarizer45 = useOrthogonalPol && params.polarizer45; | |
| // Update 45° polarizer plane visibility | |
| updatePolarizerMesh(usePolarizer45); | |
| // Update slit filter planes (yellow/green) for orthogonal mode | |
| updateSlitFilters(useOrthogonalPol, params); | |
| // Update detector screen height | |
| if (sceneManager) { | |
| sceneManager.updateDetectorHeight(getElementHeight()); | |
| } | |
| // Update detector position | |
| if (params.detectorPos != null) { | |
| DETECTOR_COL = Math.max(1, Math.min(NX - 1, params.detectorPos)); | |
| if (sceneManager && sceneManager.detectorMarker) { | |
| var surfaceWidth = (NX - 1) * DX; | |
| var worldX = -surfaceWidth / 2 + DETECTOR_COL * DX; | |
| sceneManager.detectorMarker.position.x = worldX; | |
| } | |
| } | |
| // Check if polarization or wave nature changed (needs full solver reset) | |
| var polChanged = ( | |
| params.waveNature !== prevSimParams.waveNature || | |
| params.polarization !== prevSimParams.polarization || | |
| params.mode !== prevSimParams.mode | |
| ); | |
| // Check if simulation parameters changed (not just play/pause) | |
| var simChanged = ( | |
| polChanged || | |
| params.slitWidth !== prevSimParams.slitWidth || | |
| params.slitSeparation !== prevSimParams.slitSeparation || | |
| params.wavelength !== prevSimParams.wavelength || | |
| params.sourceWidth !== prevSimParams.sourceWidth | |
| ); | |
| // Set up barriers based on polarization mode | |
| if (useOrthogonalPol) { | |
| // Two independent solvers, each with one slit | |
| var topOpen = params.topSlitOpen !== false; | |
| var bottomOpen = params.bottomSlitOpen !== false; | |
| if (topOpen) { | |
| solver.setBarrier(buildSlit1OnlyConfig(params)); | |
| } else { | |
| // Close slit 1: full barrier | |
| solver.setBarrier({ mode: 'single', barrierCol: BARRIER_COL, slitWidth: 0, slitSeparation: 0, ny: NY }); | |
| } | |
| if (bottomOpen) { | |
| solver2.setBarrier(buildSlit2OnlyConfig(params)); | |
| } else { | |
| solver2.setBarrier({ mode: 'single', barrierCol: BARRIER_COL, slitWidth: 0, slitSeparation: 0, ny: NY }); | |
| } | |
| } else { | |
| // Single solver with both slits | |
| var newBarrierConfig = buildBarrierConfig(params); | |
| solver.setBarrier(newBarrierConfig); | |
| } | |
| if (simChanged) { | |
| // Reset solvers when polarization/mode changes so wave field restarts clean | |
| if (polChanged) { | |
| solver.reset(); | |
| solver2.reset(); | |
| } | |
| intensityPanel.reset(NY); | |
| prevSimParams = Object.assign({}, params); | |
| } | |
| // Update barrier mesh — always show double-slit visual with open/closed state | |
| var visualBarrierConfig = { | |
| mode: params.mode, | |
| barrierCol: BARRIER_COL, | |
| slitWidth: Math.round(params.slitWidth), | |
| slitSeparation: Math.round(params.slitSeparation), | |
| ny: NY, | |
| topSlitOpen: params.topSlitOpen !== false, | |
| bottomSlitOpen: params.bottomSlitOpen !== false | |
| }; | |
| updateBarrierMesh(visualBarrierConfig); | |
| // Update playing state | |
| playing = params.playing; | |
| solver.paused = !playing; | |
| solver2.paused = !playing; | |
| }; | |
| // 11. Wire controlPanel.onReset | |
| controlPanel.onReset = function () { | |
| solver.reset(); | |
| solver2.reset(); | |
| intensityPanel.reset(NY); | |
| if (sceneManager) { | |
| sceneManager.resetCamera(); | |
| } | |
| playing = false; | |
| solver.paused = true; | |
| controlPanel.params.playing = false; | |
| }; | |
| // 12. Wire window resize handler (RAF-throttled) | |
| var resizePending = false; | |
| window.addEventListener('resize', function () { | |
| if (resizePending) return; | |
| resizePending = true; | |
| requestAnimationFrame(function () { | |
| resizePending = false; | |
| var w = Math.max(sceneContainer.clientWidth, 1); | |
| var h = Math.max(sceneContainer.clientHeight, 1); | |
| if (sceneManager) { | |
| sceneManager.resize(w, h); | |
| } | |
| if (intensityPanel) { | |
| intensityPanel.resize(); | |
| } | |
| }); | |
| }); | |
| // 13. Wire view buttons | |
| var btnTopView = document.getElementById('btn-top-view'); | |
| var btnAngleView = document.getElementById('btn-angle-view'); | |
| var btn135View = document.getElementById('btn-135-view'); | |
| if (btnTopView && btnAngleView && btn135View) { | |
| var allBtns = [btnTopView, btnAngleView, btn135View]; | |
| function setActiveBtn(active) { | |
| allBtns.forEach(function(b) { | |
| b.style.background = 'rgba(255,255,255,0.05)'; | |
| b.style.color = '#8899bb'; | |
| }); | |
| active.style.background = 'rgba(100,140,255,0.2)'; | |
| active.style.color = '#c0ccff'; | |
| } | |
| btnTopView.addEventListener('click', function () { | |
| sceneManager.setBottomView('top'); | |
| setActiveBtn(btnTopView); | |
| }); | |
| btnAngleView.addEventListener('click', function () { | |
| sceneManager.setBottomView('angle'); | |
| setActiveBtn(btnAngleView); | |
| }); | |
| btn135View.addEventListener('click', function () { | |
| sceneManager.setBottomView('135'); | |
| setActiveBtn(btn135View); | |
| }); | |
| } | |
| // 14. Start animation loop | |
| animate(); | |
| } | |
| /** | |
| * requestAnimationFrame loop — steps the solver, updates visuals, renders. | |
| */ | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| if (!solver || !waveSurface || !sceneManager) return; | |
| // Step the solver(s) if playing | |
| if (playing) { | |
| solver.step(STEPS_PER_FRAME); | |
| if (useOrthogonalPol) { | |
| solver2.step(STEPS_PER_FRAME); | |
| } | |
| } | |
| // Get current amplitude | |
| var amplitude; | |
| if (useOrthogonalPol) { | |
| // Orthogonal polarization: sum intensities (not amplitudes) | |
| // For display, show sqrt(a1² + a2²) with sign of the larger component | |
| var a1 = solver.getAmplitude(); | |
| var a2 = solver2.getAmplitude(); | |
| var combined = new Float32Array(a1.length); | |
| for (var i = 0; i < a1.length; i++) { | |
| combined[i] = a1[i] + a2[i]; // For visual display, sum amplitudes | |
| } | |
| amplitude = combined; | |
| } else { | |
| amplitude = solver.getAmplitude(); | |
| } | |
| // Update wave surface visuals | |
| waveSurface.updateHeights(amplitude, HEIGHT_SCALE); | |
| if (useOrthogonalPol) { | |
| // Custom coloring: yellow for slit 1, green for slit 2 | |
| waveSurface.updateColorsOrthogonal(solver.getAmplitude(), solver2.getAmplitude()); | |
| } else { | |
| waveSurface.updateColors(amplitude, colorGradient); | |
| } | |
| // Sample intensity if playing | |
| if (playing) { | |
| if (useOrthogonalPol) { | |
| var a1 = solver.getAmplitude(); | |
| var a2 = solver2.getAmplitude(); | |
| var alpha = 0.003; | |
| for (var y = 0; y < NY; y++) { | |
| var v1 = a1[y * NX + DETECTOR_COL]; | |
| var v2 = a2[y * NX + DETECTOR_COL]; | |
| var intensity; | |
| if (usePolarizer45) { | |
| var projected = (v1 + v2) / Math.SQRT2; | |
| intensity = projected * projected; | |
| intensityPanel.lastAmplitude[y] = projected; | |
| } else { | |
| intensity = v1 * v1 + v2 * v2; | |
| intensityPanel.lastAmplitude[y] = v1 + v2; | |
| } | |
| intensityPanel.accumulatedIntensity[y] = intensityPanel.accumulatedIntensity[y] * (1 - alpha) + intensity * alpha; | |
| } | |
| intensityPanel.sampleCount++; | |
| } else { | |
| intensityPanel.sample(amplitude, DETECTOR_COL, NX); | |
| } | |
| } | |
| // Draw intensity panel | |
| intensityPanel.draw(); | |
| // Render 3D scene | |
| sceneManager.render(); | |
| } | |
| // When loaded dynamically after the ES module bootstrap, DOMContentLoaded | |
| // has already fired. Check readyState and call init immediately if ready. | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', init); | |
| } else { | |
| init(); | |
| } | |
| })(); | |