<tr><td align="right"><p>Acceleration Voltage</p></td><td> </td>
<td align="middle"><input type="range" min="2.4" max="4.2" step="0.01" onchange="voltageSlider(this.value)"/></td></tr>
<tr><td align="right"><p>Ion Optics</p></td><td> </td>
<td align="middle"><input type="range" min="-0.9" max="2.1" step="0.01" onchange="opticsSlider(this.value)"/></td></tr>
<tr><td align="right"><p>Magnetic Sector</p></td><td> </td>
<td align="middle"><input type="range" min="-200" max="-50" step="0.01" onchange="magnetSlider(this.value)"/></td></tr>
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
function Particle(point, velocity, acceleration) {
this.position = point || new Vector(0, 0);
this.velocity = velocity || new Vector(0, 0);
this.acceleration = acceleration || new Vector(0, 0);
// Magnetic Field Calculations
Particle.prototype.submitToFields = function (fields) {
var totalAccelerationX = 0;
var totalAccelerationY = 0;
for (var i = 0; i < fields.length; i++) {
var vectorX = field.position.x - this.position.x;
var vectorY = field.position.y - this.position.y;
var force = field.mass / Math.pow(vectorX * vectorX + vectorY * vectorY, 1.5);
totalAccelerationX += vectorX * force;
totalAccelerationY += vectorY * force;
this.acceleration = new Vector(totalAccelerationX, totalAccelerationY);
Particle.prototype.move = function () {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
function Field(point, mass) {
Field.prototype.setMass = function(mass) {
this.drawColor = mass < 0 ? "#f00" : "#0f0";
function Vector(x, y) {this.x = x || 0; this.y = y || 0;}
Vector.prototype.add = function(vector) {
Vector.prototype.getMagnitude = function () {
return Math.sqrt(this.x * this.x + this.y * this.y)}
Vector.prototype.getAngle = function () {
return Math.atan2(this.y, this.x)}
Vector.fromAngle = function (angle, magnitude) {
return new Vector(magnitude * Math.cos(angle), magnitude * Math.sin(angle))}
function Emitter(point, velocity, spread) {
this.velocity = velocity;
this.spread = spread || Math.PI / 360;
this.drawColor = "#202020";
Emitter.prototype.emitParticle = function() {
var angle = this.velocity.getAngle() + this.spread - (Math.random() * this.spread * 3.5);
var initial = this.velocity.getMagnitude();
var magnitude = initial + (Math.random() * initial * 0.08);
var position = new Vector(this.position.x, this.position.y);
var velocity = Vector.fromAngle(angle, magnitude);
return new Particle(position,velocity);
function addNewParticles() {
if (particles.length > maxParticles) return;
for (var i = 0; i < emitters.length; i++) {
for (var j = 0; j < emissionRate; j++) {
particles.push(emitters[i].emitParticle());
function plotParticles(boundsX, boundsY) {
var currentParticles = [];
for (var i = 0; i < particles.length; i++) {
var particle = particles[i];
var pos = particle.position;
if (pos.x < 0 || pos.x > boundsX || pos.y < 0 || pos.y > boundsY) continue;
if (pos.x <= startX+290 && pos.x >= startX+281 && pos.y >= startY+55 && pos.y <= startY+55+7) {
particle.submitToFields(fields);
currentParticles.push(particle);
particles = currentParticles;
function drawParticles() {
ctx.fillStyle = "rgba(10, 70, 250, 0.25)";
for (var i = 0; i < particles.length; i++) {
var position = particles[i].position;
ctx.fillRect(position.x, position.y, particleSize, particleSize);
function drawCircle(object) {
ctx.fillStyle = object.drawColor;
ctx.arc(object.position.x, object.position.y, objectSize, 0, Math.PI * 3);
function drawDetector() {
ctx.rotate(deg * Math.PI / 180);
ctx.rect(startX+detectorX, startY-detectorY-2.5, 15, 5.5);
ctx.fillStyle = "rgba(0, 0, 0, 0.9)"
ctx.rotate(0 * Math.PI / 180);
ctx.rect(startX+detectorX-7, startY-detectorY-5, 22, 3);
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"
ctx.rotate(0 * Math.PI / 180);
ctx.rect(startX+detectorX-7, startY-detectorY+3, 22, 3);
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"
var fromX = startX+detectorX+4;
var fromY = startY+detectorY-70.5;
ctx.moveTo(fromX, fromY);
ctx.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, toX, toY);
function drawComputer() {
var computerX = startX+123;
var computerY = startY+80;
var fromX = startX+detectorX+2;
var fromY = startY+detectorY-64;
ctx.rect(computerX-80, computerY, 50, 40);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
ctx.rect(computerX-65, computerY+45, 20, 2);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
ctx.rect(computerX-20, computerY, 20, 47);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
// Computer Button Bottom
ctx.rect(computerX-10.5, computerY + 34, 2, 4);
ctx.strokeStyle = powerColor;
ctx.rect(computerX-13.2, computerY + 7, 7, 0.5);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
// Computer Button Middle
ctx.rect(computerX-14, computerY + 12, 8.5, 0.5);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
ctx.font = '7pt Verdana';
ctx.textAlign = 'center';
ctx.fillText("ions/sec", computerX-55, computerY+32);
function updateComputer() {
var computerX = startX+123;
var computerY = startY+80;
if (maxDisplay == 0) {count = counterIons}
else if (maxDisplay == 1) {count = maxIon}
ctx.font = '10pt Verdana';
ctx.textAlign = 'center';
ctx.fillText(count.toString(), computerX-55, computerY+20);
// Average Ions (over 3 seconds)
counterIons = Math.round((counterA + counterB + counterC) / 3);
setTimeout(function(){timedCount()}, 1000);
if (counterIons > maxIon) {maxIon = counterIons}
// Draw Mass Spectrometer
ctx.rect(startX-10, startY-1, 24, 9);
ctx.fillStyle = "rgba(0, 0, 0, 0.4)"
var sectorX1 = startX + 150;
var sectorY1 = startY + 175;
var sectorY2 = sectorY1 - 20;
var startAngle = 1.5 * Math.PI;
var endAngle = 1.67 * Math.PI;
var counterClockwise = false;
ctx.arc(sectorX1, sectorY1, radius, startAngle, endAngle, counterClockwise);
ctx.strokeStyle = "rgba(20, 20, 20, 0.7)"
ctx.lineWidth = sectorLine;
ctx.arc(sectorX2, sectorY2, radius-(0.3 * radius), startAngle, endAngle, counterClockwise);
ctx.strokeStyle = "rgba(20, 20, 20, 0.7)"
ctx.lineWidth = sectorLine;
ctx.rect(startX+42.5, startY+14, 80, 6.0);
ctx.fillStyle = "rgba(10, 10, 10, 0.35)"
ctx.strokeStyle = "rgba(0, 0, 0, 0.4)"
ctx.rect(startX+42.5, startY-13, 80, 6.0);
ctx.fillStyle = "rgba(10, 10, 10, 0.35)"
ctx.strokeStyle = "rgba(0, 0, 0, 0.4)"
// Magnetic Sector Slider
function magnetSlider(force) {
function opticsSlider(force) {
//if (force < 0.5 && force > 0) {force = 0.1}
emitters[0].spread = (Math.PI / 360) * force * force
emitters[1].spread = (Math.PI / 360) * force * force
// Acceleration Voltage Slider
function voltageSlider(force) {
emitters[0].velocity.x = force - 0.1
emitters[1].velocity.x = force
var midX = canvas.width / 2;
var midY = canvas.height / 2;
var emitters = [new Emitter(new Vector(midX - 150, startY + 2), Vector.fromAngle(-0.0, 3.3)),
new Emitter(new Vector(midX - 150, startY + 2), Vector.fromAngle(-0.0, 3.4))];
// Fields //50 27 50 45 130 -10
var fields = [new Field(new Vector(midX + 34, startY + 45), -fieldValue)];
ctx.clearRect(0, 0, canvas.width, canvas.height);
plotParticles(canvas.width, canvas.height/2);
//fields.forEach(drawCircle);
//emitters.forEach(drawCircle);
window.requestAnimationFrame(loop);
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {x: evt.clientX - rect.left,
y: evt.clientY - rect.top};
canvas.addEventListener('mousemove', function(evt) {
mousePos = getMousePos(canvas, evt)
if (mousePos.x >= startX+43 && mousePos.x <= startX+43+50 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+40) {
// Computer Screen Position
var screen = {left: startX+43, top: startY+80, right: startX+43+50, bottom: startY+80+40};
maxParticlesLast = maxParticles,
emissionRateLast = emissionRate,
if (togglePower == 0) {powerColor = "rgba(20, 230, 20, 0.9)"}
else if(togglePower == 1) {powerColor = "rgba(230, 20, 20, 0.9)"}
canvas.addEventListener('click', function(){
if (mousePos.x >= startX+43 && mousePos.x <= startX+43+50 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+40 &&
powerColor = "rgba(20, 230, 20, 0.9)"}
else if (mousePos.x >= startX+43 && mousePos.x <= startX+43+50 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+40 &&
powerColor = "rgba(20, 230, 20, 0.9)"
else if (mousePos.x >= startX+123-20 && mousePos.x <= startX+123-20+20 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+47 &&
maxParticlesLast = maxParticles
emissionRateLast = emissionRate
powerColor = "rgba(230, 20, 20, 0.9)"
else if (mousePos.x >= startX+123-20 && mousePos.x <= startX+123-20+20 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+47 &&
maxParticles = maxParticlesLast
emissionRate = emissionRateLast
powerColor = "rgba(20, 230, 20, 0.9)"
ctx.font = '10pt Verdana';
ctx.textAlign = 'center';
ctx.fillText('Source', 30, textY);
ctx.rect(computerX-80, computerY, 50, 40);
ctx.fillStyle = "rgba(62, 219, 67, 0.7";