This is an article about physical computer models, i.e., simulation of physical experiments, processes or idealized model situations encountered in the physical tasks.
Table of Contents
Introduction
To understand physics processes and phenomenons, different experiments and demonstrations are often used. Software simulation of processes allow you to see an event or experiment outside laboratory. In its narrowest sense, a physics computer simulation is a recreation of a real-world behavior of a physical phenomenon. With the help of such modeling, it is possible to change many parameters, observe how this affects the experiment and understand the essence of the phenomenon.
This article describes the creation of JavaScript, HTML based models from the world of physics. It allows to run them on any platform via web browsers. Three subjects are covered here:
- “Addition of forces by an angle” from the “Dynamics” chapter of physics
- “Archimedes’ Principle” from the chapter “Pressure of liquids”
- “Mathematical pendulum” from the chapter ”Mechanical oscillations”
Physical Topics
Addition of Forces by an Angle
A force is a push or pull upon an object resulting from the object's interaction with another object. In the International System of Units (SI) unit of force is the newton (N). It is a vector quantity. We can add or subtract forces. Resultant force is the combination of two or more forces, it produces the same acceleration as all those forces.
In the simple case in which an object is subject to two forces that act in the same direction, the resultant has the magnitude equal to the sum of the two magnitudes \(F= F_1 + F_2\).
Graphically, the resultant force of two forces applied by an angle can be found by using the parallelogram rule:
The resultant force is: \(F= \sqrt{F_1^2 + F_2^2 + 2F_1F_2 \cos \alpha }\)
If the force \(F_1 \) equals to the force \(F_2 \) , then they can be found by the equation: \(F_1 = F_2 = \frac{F}{2 \cos( \alpha/2 ) } \)
Archimedes' Law
Archimedes' principle states that the buoyant force on a submerged object is equal to the weight of the fluid that is displaced by the object. The force, which acts downward on an object, is the weight of an object. If the buoyant force is more than the weight, the object rises, if it is less, the object sinks. If the buoyant force is equal to the weight, then an object stays put.
The buoyant force can be expressed as \(F_b = \rho G V \), where \(\rho\) is the density of fluid \( (kg/m^3) \), g is the acceleration of gravity \( (9.81m/s^2) \), V is the volume of body \( (m^3) \). Volume of body is found by the equation \(V = m/\rho \), where \(\rho\) is the density of body, m is the weight of body.
Mathematical Pendulum
A mathematical pendulum is a material point (a small body) that is suspended on a thin weightless inextensible string or on a weightless rod. The period of the motion for a pendulum is how long it takes to swing back-and-forth and measured in seconds.
The equation for the period of a simple pendulum starting at an angle \(\alpha\) is \(T= 2\Pi\sqrt{L/g }\), where L is the length of the rod, g is the acceleration of gravity \( (9.81m/s^2) \).
The frequency of a pendulum is how many back-and-forth swings there are in a second, measured in hertz. The equation for the frequency is \( f = 1/T \).
HTML, CSS Code
The project consists of several HTML pages with JavaScript coding. The main page is “index.html” in the root of project. It contains the main menu in the left side of the page. The menu is made out of DIV
elements. In the left side is located IFRAME
element. Here, the users can manipulate by parameters of each topic of physics. Drawing and manipulation of simulations' objects for physical experiments occurs on the object canvas in the center of the main page.
The folder “BoardArea” contains HTML pages to load into the Iframe
element on the main page. These HTML pages contain input controls as parameters for models. In the folder “css” are located Cascading Style Sheets. The file “controls.css" applies styles regarding input elements, buttons, labels. “main.css" contains styles for creation structure of the “index.html” page, “menu.css" contains styles for building menu on the main page and “variables.css” file contains global CSS variables of the project. All JS code scripts are located in the folder "./js”.
JavaScript Code Design
The project code is divided into several objects, according to required purposes. In this JS design has been used some ideas of the article by the author Sergey A Kryukov.
General Methods
Some common methods for entire application are located in the objects: application
, which is responsible for properties and methods in common, and the object contextLayout
to apply functions and variables concerning canvas. The code is located in the file "./js/appMethods.js".
The fragment from application
:
const application = {
arcPath: (2 * Math.PI),
degrToRad: (degrees) => (Math.PI / 180) * degrees,
kgToNewton: (kg) => kg * 9.81,
timeout : ms => new Promise(resolve => setTimeout(resolve, ms)),
}
This is the code from the object contextLayout
:
const contextLayout = {
clearCanvas: function (canvas) {
let context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);
},
}
Canvas Drawn Items of Stage
Time to time physics experiments require to manipulate with different secondary elements like plummets, balls, hooks. They help with experiments and demonstrations. There is special object for this purpose StageItem
, the code is in "./js/Tools/stageElements.js". StageItem
is a Canvas
drawn object from which can derive additional items.
function StageItem(options = {}) {
Object.assign(this, {
x: 0,
y: 0,
width: 40,
height: 40,
fill: "#7aa7ca",
strokeColor: "black",
weight: 1,
itemText: "item",
lineWidth: 2,
canvas: undefined,
}, options);
}
}
StageItem
has the following properties: x
, y
are the coordinates of left upper corner, width
, height
are the width and height of an item, fill
is the background color, strokeColor
is the stroke color, weight
is the weight (kg, g), weight
is the text drawn on item, lineWidth
is the stroke width of drawn object, canvas
is the canvas to draw on.
From StageItem
, derive two additional objects: Brick
to draw plummet on canvas, Ball
to draw plummet ball on canvas.
function Brick(options) {
StageItem.call(this, options);
Object.assign(this, {
roundRadius: 5,
},
options);
}
Brick.prototype = Object.create(StageItem.prototype);
Brick.prototype.constructor = Brick;
Brick.prototype.draw = function () {
}
function Ball(options) {
StageItem.call(this, options);
Object.assign(this, options);
}
Ball.prototype = Object.create(StageItem.prototype);
Ball.prototype.constructor = Ball;
Ball.prototype.draw = function () {
}
These two classes have prototype method draw. Drawing of brick on canvas is shown below:
let myCanvas = document.getElementById("Canvas");
let brick = new Brick({
x: 450,
y: 130,
width: 40,
height: 40,
fill: "LightGray",
strokeColor: "black",
itemText: "Brick",
lineWidth: 2,
canvas: myCanvas,
});
brick.draw();
Dragging on Canvas
To interact with different drawn objects on stage, i.e., to move them, to suspend them is used JS object dragRendering
from the separate file "./js/dragging.js". The code allows catching an object that can follow the user's mouse. The object dragRendering
accepts parameter canvas
to interact with and an array of draggable items dragElements
.
const dragRendering = {
canvas: undefined,
dragElements: [{
id: undefined,
isDraggable : true,
isDragging : false,
elem: undefined
}],
addElements: function (elements) {
this.dragElements = elements.slice();
this.dragElements.forEach(element => { element.isDraggable = true; });
this.drawElements();
},
}
The array dragElements
accepts parameters: id
is unique ID of each draggable item, isDraggable
can an item be dragged, isDragging
is flag to check that item is currenly being dragged, elem
is object to move (see Canvas drawn items of stage section above). To populate the array dragElements
, there is specially implemented function addElements
.
Now look at the three important functions: canvasMouseDown
is raised at an element when mouse pointer is inside the element, canvasMouseMove
is fired at an element when mouse pointer captured the element for dragging, canvasMouseUp
is raised at an element when the user releases the element and stops its movement on canvas.
canvasMouseDown: function (e) { }
canvasMouseMove: function (e) { }
canvasMouseUp: function (e) { }
The user can call custom functions refresh
during dragging without redefining canvasMouseMove
, startedDragging
during mouse down event without redefining canvasMouseDown
, stoppedDragging
during mouse up event without rewriting canvasMousUp
.
stoppedDragging: function (elem) { },
startedDragging: function (elem) { },
refresh: function () { },
In general, how to apply dragging on canvas is shown below:
let myCanvas = document.getElementById("Canvas");
myCanvas.onmousedown = function (e) {
dragRendering.canvasMouseDown(e);
};
myCanvas.onmouseup = function (e) {
dragRendering.canvasMouseUp(e);
};
myCanvas.onmousemove = function (e) {
dragRendering.canvasMouseMove(e);
};
let elementSize = 30;
dragRendering.dragElements = [];
dragRendering.refresh = function () { console.log('dragging'); };
dragRendering.stoppedDragging = function (elem) { console.log('stopped dragging'); };
dragRendering.startedDragging = function (elem) { console.log('started dragging'); };
const bricks = [
{
id: "X", elem: new Brick({
x: 10,
y: 10,
height: elementSize,
width: elementSize,
canvas: myCanvas,
itemText: "X"
})
},
{
id: "y", elem: new Ball({
x: 50,
y: 50,
height: elementSize,
width: elementSize,
canvas: myCanvas,
itemText: "Y"
})
},
];
dragRendering.addElements(bricks);
Communication between Main Window and Physical Topics
Each topic to simulate physical process corresponds to separate const
object in the separate JS files. These objects are forcesbyAngleDemo
, archimedesPrincipleDemo
, pendulumDemo
and appliancesDemo
. The objects are located in the folder "./js/topicsRender/". Each object accepts parameters like canvas, context or has methods and functions to perform implementation like drawing on canvas, receiving data from the IFrame
element.
In turn the file "./js/main.js" allows to build HTML menu corresponding to the physical topics and redraw the canvas area depending on the selected menu item. For this purpose, it has the special object applicationRendering
.
const applicationRendering = {
topics: { forcesAdditionByAngleId: 1, ArchimedesPrincipleId: 2 ,
pendulumDemoId : 3, AppliancesDemoId: 4},
currentTopic: undefined,
canvas: application.canvas,
initialInit: function () {
contextLayout.stretchCanvas(this.canvas, "divCanvas");
dragRendering.canvas = forcesbyAngleDemo.canvas =
archimedesPrincipleDemo.canvas = pendulumDemo.canvas =
appliancesDemo.canvas = this.canvas;
forcesbyAngleDemo.ctx = archimedesPrincipleDemo.ctx =
pendulumDemo.ctx = appliancesDemo.ctx = this.context;
this.renderMenu();
},
renderMenu: function () {
elementDiv.onclick = function () {
switch (applicationRendering.currentTopic) {
case applicationRendering.topics.forcesAdditionByAngleId:
forcesbyAngleDemo.init();
break;
case applicationRendering.topics.ArchimedesPrincipleId:
archimedesPrincipleDemo.init();
break;
case applicationRendering.topics.pendulumDemoId:
pendulumDemo.init();
break;
case applicationRendering.topics.AppliancesDemoId:
appliancesDemo.init();
break;
default:
}
}
}
}
};
Communication between Main Window and Iframe
During physical experiments, it is required to perform calculations based on different formulas. To each physical topics object corresponds separate HTML page with input elements which allows to change and pass variables into the required formula. These HTML pages can be loaded into inline IFrame
depending on the selected physical topic.
The HTML page "./BoardArea/FluidMechanics/ArchimedesPrinciple.html" with input elements and parameters for the topic "Archimedes' Principle".
In order to pass data to the main window from the IFrame
is used method parent.postMessage
. To receive data, the main window must have an event handler window.addEventListener('message')
. The constant object frameRender
from the separate file "framesRendering.js" catches change
event for each input element on HTML page with parameters and sends message to the parent main window. The main window receives this message in JSON format and passes further to physical topic.
const frameRender = {
passControlsMessage: function () { },
bindEvents: function () {
let passMsg = this.passControlsMessage.bind(this);
let divElement = document.querySelector('body');
let inputElements = divElement.querySelectorAll('input');
for (let elem of inputElements) {
elem.oninput = function () {
setRangeLabelValue();
passMsg();
}
}
}
window.onload = function () {
frameRender.bindEvents();
frameRender.passControlsMessage();
};
Passing data from the page "ArchimedesPrinciple.html" inside the IFrame
to the parent window:
frameRender.passControlsMessage = function () {
let bodyDensity = document.getElementById("bodyDensityTextBox").value;
let pass_data = {
'bodyDensity': bodyDensity
};
parent.postMessage(JSON.stringify(pass_data), "*");
}
Receiving data in the main window from IFrame
:
window.addEventListener(
"message",
function (e) {
var key = e.message ? "message" : "data";
var data = e[key];
applicationRendering.topicVariables = JSON.parse(data);
applicationRendering.receiveData();
},
false
);
Conversely, to send message to the IFrame
, frameEl.postMessage
method from the parent window is used. To receive message inside the frame, window.addEventListener('message')
is applied.
JavaScript Drawn Physical Appliances
Contraptions for laboratory experiments are special devices that allows you to collect, analyze, calculate and process information obtained from certain phenomena and effects. The project has several such canvas drawn items and they are placed in separate files in the folder "./js/Tools".
Arrow Dynamometer
A dynamometer is a device for measuring force, moment of force (torque), or power. There are several types of dynamometers: mechanical dynamometers (arrow and spring) as well as electrical.
To create an arrow dynamometer are used two canvas drawn objects Appliance
and Dynamometer
. These objects are placed in the files "./js/Tools/appliance.js" and "./js/Tools/dynamometer.js". Appliance
object simulates face of gauge control with arrow. It has two methods of prototype. The method draw
draws face like a set of filled circles with shadows. The second method of prototype drawPointer
draws arrow pointer with required width and angle.
function Appliance(options = {}) {
Object.assign(this, {
centerX: 0,
centerY: 0,
radius: 50,
angle: 0,
ringColor: "#5f5f5f",
innerRingColor: "#e9e9e9",
backRingColor: "#7e7e7e",
pointerColor: "#d95358",
canvas: undefined
}, options);
Appliance.prototype = {
draw : function () {
}
drawPointer : function (angle = 0, arcWidth = 10) {
}
}
The object Appliance
has the following properties : centerX
, centerY
are coordinates of center, radius
is the radius of gauge, angle
applies rotation angle around appliance's center point, ringColor
is the color of outer ring, innerRingColor
is the color of the second outer ring, backRingColor
is the color of background ring, pointerColor
is the color of arrow's pointer, canvas
is the canvas to draw on.
The example of how to draw appliance on canvas is shown below:
let myCanvas = document.getElementById("Canvas");
let appliance = new Appliance({
centerX: 600,
centerY: 300,
radius: 100,
canvas: myCanvas
});
appliance.draw();
appliance.drawPointer(45);
The object Dynamometer
inherits from the class Appliance
. it has three methods of prototype. To draw this dynamometer on canvas, there is the method draw
. The second method is setStaticValue
, which allows drawing arrow pointer for applied value. The method setValue
allows drawing arrow pointer for applied value with animation. The method setValue
is asynchronous and returns promise.
Dynamometer
has additional properties: maxValue
is the maximum value of dynamometer, value
is the current value of dynamometer, rotateStep
speed of rotation of the arrow (i.e., change of value by time period)
function Dynamometer(options) {
Appliance.call(this, options);
Object.assign(this, {
maxValue: 10,
value: 0,
rotateStep : undefined
}, options);
}
Dynamometer.prototype = {
draw: function () {
},
setStaticValue: function (valuePointer = 0) {
},
setValue: async function (valuePointer = 0) {
}
The example of how to draw dynamometer
on canvas is shown below:
let myCanvas = document.getElementById("Canvas");
let dynamometer = new Dynamometer({
centerX: 250,
centerY: 300,
radius: 100,
ringColor: "red",
innerRingColor: "yellow",
canvas: myCanvas,
rotateStep : 0.1,
angle: 45,
maxValue: 20
});
dynamometer.draw();
dynamometer.setValue(-12).then(function () {
alert('Value was set');
})
Spring
Spring object is a canvas drawn sine wave. Each turn of spring is a sinusoid in the range from 0 to 360 degrees. The required number of turns can simulate turns of spring. How to draw simulation of spring is shown below:
let ctx = document.getElementById("Canvas").getContext("2d");
let startX = 200;
let startY = 300;
let radius = 50;
ctx.save();
ctx.lineCap = "round";
let totalLength = 300;
let swings = 5;
let swingLength = totalLength / swings;
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
let currentSwing = 0;
while (currentSwing < swings) {
let beginX = startX + swingLength * currentSwing;
let endX = beginX + swingLength;
let waveStepX = (endX - beginX) / 360;
for (let x = 0; x <= endX - beginX; x += waveStepX) {
y = startY - (Math.sin((1 / waveStepX) * (x * Math.PI / 180))) * radius;
ctx.lineTo(beginX + x, y);
}
currentSwing++;
}
ctx.stroke();
ctx.restore();
For creation of spring, special object Spring
with required properties is used. To draw the object, there is a special method draw
of prototype.
function Spring(options = {}) {
Object.assign(this, {
canvas: undefined,
swingFrontColor: "lightGray",
swingBackColor: "gray",
startX: 0,
startY: 0,
length: 100,
radius: 50,
swings: 5,
angle: 0,
swingWidth: 3
}, options);
}
Spring.prototype.draw = function () {
}
The properties of Spring
are: canvas
is canvas to draw on, startX
, startY
are the beginning coordinates of sinus wave, swingFrontColor
is the color of front turn, <color>swingBackColor
is the color of back turn, length
is the total length of spring, radius
is the radius of sinus wave, swings
is the count of turns, angle
applies rotation angle around upper left corner, swingWidth
is the width of each swing in pixels.
Ruler
The object Ruler
draws metric scale on canvas to measure length. The user is allowed to apply maximum value of this object. Ruler
is split by strokes proportionally to maximum value.
function Ruler(options = {}) {
Object.assign(this, {
canvas: undefined,
startX: 0,
startY: 0,
length: 200,
height: 50,
maxValue: 10,
backColor: "orange",
strokeColor: "black",
showBackground: true,
showBottom: true,
angle: 0,
}, options);
}
Ruler.prototype.draw = function () {
}
Ruler
has the properties: canvas
is canvas to draw on, startX
, startY
are the beginning coordinates of left top corner, length
, height
are the length and height of ruler, maxValue
is the maximum value , backColor
is the background color, strokeColor
is the line color, showBackground
allows showing or hiding of background, showBottom
allows showing or hiding bottom strokes of ruler, angle
applies rotation angle around upper left corner.
Drawing of ruler
on canvas
is shown below:
let myCanvas = document.getElementById("Canvas");
let ruler = new Ruler({
startX: 200,
startY: 50,
canvas: myCanvas,
length: 500,
height: 60,
strokeColor: "black",
maxValue: 500
});
ruler.draw();
Spring Dynamometer
In a spring dynamometer, force is transferred to the spring, which, depending on the direction of force, is compressed or stretched. The amount of spring deformation is proportional to force.
The prototype of SpringDynamometer
has three functions: to draw this dynamometer on canvas, there is the method draw
, which draws entire dynamometer on canvas, setStaticValue
, which sets drawn indicator for applied value, setValue
, which animates drawn indicator for applied value. The method setValue
is asynchronous and returns promise.
function SpringDynamometer(options = {}) {
Object.assign(this, {
canvas: undefined,
startX: 0,
startY: 0,
length: 200,
height: 50,
maxValue: 10,
backColor: "orange",
strokeColor: "black",
angle: 0,
animatedHolder: false,
value: 0,
animationStep: undefined
}, options);
SpringDynamometer.prototype = {
draw: function () {
}
setStaticValue: function (valuePointer = 0) {
}
setValue: async function (valuePointer = 0) {
}
The properties of SpringDynamometer
are: canvas
is the canvas to draw on, startX
, startY
are the beginning coordinates of left top corner, length
, height
are the length and height of spring dynamometer, maxValue
is the maximum value ,backColor
is the background color, strokeColor
is the line color, angle
applies rotation angle around upper left corner, animatedHolder
determines whether or not holder with hook is animated , value
is the current value of dynamometer, animationStep
animation speed of rotation of the holder (i.e., change of value by time period).
The example how to apply properties of spring dynamometer and to draw it on canvas is shown below:
let myCanvas = document.getElementById("Canvas");
let springDynamometer = new SpringDynamometer({
startX: 100,
startY: 50,
canvas: myCanvas,
angle: 90,
length: 400,
height: 60,
strokeColor: "black",
value: 0,
animatedHolder: true,
maxValue: 50
});
springDynamometer.draw();
springDynamometer.setValue(30).then(function () {
springDynamometer.setValue(0);
});
JavaScript Physical Models
"Addition of Forces by an Angle" Model
The object forcesbyAngleDemo
from the file "forcesbyAngleRender.js" is responsible for creation of model to simulate addition of forces. It applies two dynamometers, which can be placed by some angle and three bricks, which can be dragged on the stage and suspended under the dynamometers onto the thread. Through changing parameters of bricks’ weight and angle between dynamometers, the user can calculate total force applied to the dynamometers.
This is a fragment from forcesbyAngleDemo
:
const forcesbyAngleDemo = {
dynamometers: undefined,
canvas: undefined,
ctx: undefined,
init: function () {
this.applySettings();
this.dynamometers = forcesbyAngleDemo.initRender();
dragRendering.refresh =
function () { forcesbyAngleDemo.refreshDrawDrag(); };
dragRendering.stoppedDragging =
function (elem) { forcesbyAngleDemo.stoppedDrawDrag(elem); };
dragRendering.startedDragging =
function (elem) { forcesbyAngleDemo.startedDrawDrag(elem); };
},
initRender: function () {
this.drawBox();
this.drawRope();
dynamLeft.draw();
dynamRight.draw();
},
startedDrawDrag: function (dragElem) {
let el = dragRendering.findElem(dragElem.id).elem;
},
stoppedDrawDrag: function (dropElem) {
this.setDynamValues();
this.passFrameValue();
},
setDynamValues: function () {
this.dynamometers.dynamLeft.setValue(this.getForce()).then(() => {
this.setDraggingFlags();
this.passFrameValue();
})
},
}
"Archimedes' Law" Model
The constant archimedesPrincipleDemo
applies one spring dynamometer and the brick, which can be dragged and suspended under the dynamometer to the thread. The dynamometer can be moved towards the box with liquid to show buoyant force applied to it.
This is a fragment from archimedesPrincipleDemo
:
const archimedesPrincipleDemo = {
dynamometer: undefined,
canvas: undefined,
ctx: undefined,
cancelTimer: false,
init: function () {
this.applySettings();
this.dynamometers = archimedesPrincipleDemo.initRender();
dragRendering.refresh =
function () { archimedesPrincipleDemo.refreshDrawDrag(); };
dragRendering.stoppedDragging =
function (elem) { archimedesPrincipleDemo.stoppedDrawDrag(elem); };
dragRendering.startedDragging =
function (elem) { archimedesPrincipleDemo.startedDrawDrag(elem); };
},
refreshDrawDrag: function () {
this.drawBox();
this.drawLiquidBoxBig();
this.dynamometers.springDynamometer.draw();
},
stoppedDrawDrag: function (dropElem) {
this.dynamometers.springDynamometer.setValue
(gravityForce).then(function () {
archimedesPrincipleDemo.animatePosition().then(function () {
})
})
this.passFrameValue(volume, buyonantForce,
application.roundTwoDigits(this.getForce()));
}
}
"Mathematical Pendulum" Model
The constant object pendulumDemo
draws on canvas two animated pendulums and applies parameters like length of pendulum and its start angle of movement. The setTimeout
function has been used for creation of periodic animation which is accurate to real timer.
const pendulumDemo = {
canvas: undefined,
ctx: undefined,
timer: undefined,
settings: {
pendulumLength: 0,
pendulumLength2: 0,
pendulumCoord: { x: 0, y: 0 },
},
animatePendulum: function (startAngle, timeInterval, startAngle2, timeInterval2) {
let interval = 20;
var expected = Date.now() + interval;
this.timer = setTimeout(step, interval);
let canvas = this.canvas;
function step() {
var dt = Date.now() - expected;
contextLayout.clearCanvas(pendulumDemo.canvas);
expected += interval;
pendulumDemo.drawPendulum
(currentPosition.x, currentPosition.y, 'red');
pendulumDemo.drawPendulum
(currentPosition2.x, currentPosition2.y, 'blue');
pendulumDemo.timer = setTimeout(step, Math.max(0, interval - dt));
}
}
}
Online Demo
The online demo is available here.
History
- 28th September, 2020: Initial version