myPhysicsLab Documentation

Building myPhysicsLab Software

myPhysicsLab provides classes to build real-time interactive animated physics simulations. This page has information about building the myPhysicsLab software, running tests, internationalization and general programming issues.

Contents of this page:

Additional information:

License and Source Code

myPhysicsLab is provided as open source software under the Apache 2.0 License. See the accompanying file named LICENSE.

Source code is available at http://www.github.com/myphysicslab/myphysicslab

The myPhysicsLab project was started in 2001 by Erik Neumann erikn@myphysicslab.com. It was originally written in Java, improved and enhanced over the years and converted to JavaScript from 2013 to 2016. In 2023 the code was converted to TypeScript.

Building

Build Instructions

It is possible to customize a myPhysicsLab simulation without building from source code, see Customizing myPhysicsLab Simulations.

To build from source code:

  1. Download the myPhysicsLab source code from https://github.com/myphysicslab/myphysicslab. You can download a zip file from that github page, or use

    git clone https://github.com/myphysicslab/myphysicslab.git
    
  2. Install the required tools:

  3. Execute tsc at the command line. This will compile all the typescript .ts files to become JavaScript .js files in the build directory. (This will also create the build directory if it doesn’t yet exist.)

  4. Execute make at the command line. This creates .html files and bundled .js files in the build directory for all applications and tests in all language versions. Execute make help to see available options.

  5. Open the file /build/index-en.html with a browser. This has links to all the example files that were built.

See References below for more information on the required tools.

Inside the Build Process

Once the environment has been set up, the makefile can handle building any myPhysicsLab application. The three main transformations are shown in the above diagram.

  1. TypeScript compiler tsc compiles all .ts files into the parallel locations in the build directory. For example src/sims/pendulum/DoublePendulumApp.ts is compiled to become build/sims/pendulum/DoublePendulumApp.js

  2. The esbuild tool bundles one Javascript file like DoublePendulumApp.js, along with all other required files (specified by import statements in TypeScript) into one localized file like DoublePendulumApp-en.js.

  3. The HTML application file – for example, DoublePendulumApp.html – is transformed via a perl script prep_html.pl to a language specific version – DoublePendulumApp-en.html– with various text transformations such as internationalization. The prep_html.pl script does macro substitutions (similar to C preprocessor) to transform text within the file, writing a new HTML file. Many of the macros are defined in the file src/macros.html.

The makefile also copies over all the image files in src/images to build/images and copies the CSS file src/stylesheet.css to build/stylesheet.css.

The HTML application files have “next” and “previous” links to make it easy to visit all of the applications. These links are automatically generated by the macros NEXT_LINK and PREV_LINK which use the ordering specified in the file src/index_order.txt. See prep_html.pl for more information.

There are “index” files that are essentially a “table of contents” for all the myPhysicsLab apps. These index files (one for each language such as English and German) have links to all of the apps. The links are in the order given by src/index_order.txt.

Customizing The Build Process

Use the command

make help

to see available targets and options. See comments in the makefile for more info.

There are variables used in makefile which control important aspects of the build process. These variables have default values which can be overridden via command-line arguments.

The makefile contains shortcuts for building applications. For example, instead of typing:

make build/sims/pendulum/DoublePendulumApp-en.html

You can just type

make doublependulum

The shortcut is the name of the application in all lowercase, minus the “app” suffix.

Directory Structure

The following describes the purpose and origin of the various files.

The make variable BUILD_DIR specifies where to put compiled code, see Customizing The Build Process.

Building the Documentation

Building the documentation requires installation of Typedoc and MultiMarkdown; see References for how to obtain these. After installing MultiMarkDown, you should be able to execute this command:

multimarkdown -help

MultiMarkDown is used to create documentation from markdown files such as docs/Building.md, docs/Architecture.md and docs/Engine2D.md.

To install Typedoc, see https://typedoc.org. After installation, make a symbolic link (within the myphysicslab directory) to the typedoc executable , for example:

ln -s node_modules/typedoc/bin/typedoc esbuild

You should then be able to execute ./typedoc --version from within the myphysicslab directory.

Typedoc creates the documentation by compiling all of the source code, and interpreting comments as markdown. Some Typedoc options are specified in the file typedoc.json.

All of the documentation can be built with the command

make docs

The file build/docs/index.html is the home page for the documentation, it is built by Typedoc from the src/docs/doc_start.md markdown file. View it by opening with a browser

open build/docs/index.html

To build only the markdown documentation use the command

make docs-md

Diagrams are mostly created with OmniGraffle .graffle source files and output to svg or pdf formats, which are stored alongside the source files and copied over to the docs directory by the makefile.

Papers written using LaTeX are files ending in .tex which produce .pdf files by using the command pdflatex. For example

pdflatex Rigid_Double_Pendulum.tex

will produce the file Rigid_Double_Pendulum.pdf as well as some associated files ending in .aux, .out and .log. Some of these files are needed to figure out cross-references within the document. Note that you usually have to run pdflatex twice in order to figure out the cross-references.

See References for information about obtaining OmniGraffle and LaTeX.

Testing

HTML Example Files

The HTML Example Files are for developer testing, not meant to be finished web pages.

The HTML files in the source directories cannot be used directly from a browser. You must complete the build process first.

The command to build the HTML Example Files is

make

The “home page” for the examples is src/index.html. It lists all the examples for quick access. The examples are linked together via “next” and “previous” links on each page to easily view all the examples. Clicking on the myPhysicsLab logo on any page takes you back to the examples “home page”.

After compiling, there will be a “home page” version for each locale such as index-en.html and index-de.html. Use a web browser to open these these:

open build/index-en.html

To build only the English versions of all the apps use this command:

make apps-en
open build/index-en.html

To build only the German versions:

make apps-de
open build/index-de.html

The “next” and “previous” links on each example page are generated by the prep_html.pl perl script, which gets the order by reading the file src/index_order.txt.

Each application has two file components:

  1. An HTML file that is opened in a browser and loads the JavaScript application. The source file is typically named with the suffix ...App-en.html, for example DoublePendulumApp-en.html.

  2. The JavaScript file containing the application code. Note that within the build directory there are many JavaScript files generated by TypeScript compiler. But only the bundled versions created by esbuild are used to run the application. Typically these versions end with the suffix ...App-en.js or ...App-de.js depending on the language.

When adding a new application follow these steps:

  1. Add the application to appropriate places in makefile so that it is built as part of the make all command.

  2. Add the application to the list in src/index_order.txt.

  3. Add a link to the application in src/index.html.

  4. Execute make all to ensure all the “next/previous” links are remade in other web pages that link to the new page.

Unit Tests

These are commands that will build and run the unit tests using various options for locale:

Unit tests are contained in /test subdirectories of many namespaces, for example in lab/util/test, or lab/engine2D/test.

To debug a unit test more easily, use SingleTest and remove the --minify flag from the esbuild command in makefile.

Note that the unit test coverage is far from complete!

Engine2D Tests

Engine2DTests are a set of tests of the myphysicslab.lab.engine2D namespace.

These tests are mainly useful as a warning that the behavior of the physics engine has changed. This can happen when changes are made to the physics engine or when browser behavior changes (because of an update to a browser). These tests don’t specify “correct” behavior, but rather the historical expected behavior.

To run engine2D tests, open the file build/test/Engine2DTests-en.html in a browser.

The engine2D tests will run the simulation, usually ContactSim, without any user interface view or controller. A typical test will create the simulation, set up initial conditions, run the simulation for a set amount of time, and then analyze the resulting simulation state. Typically the simulation variables are compared to a pre-computed “good” state. Also the energy of the simulation might be tested in some way; for example the energy of a system might be expected to be constant.

When changes are made to the myphysicslab.lab.engine2D simulation engine it is possible that the expected results for tests need to be modified. Changing the expected results should be avoided unless you are sure that the changes you’ve made are correct.

SingleTest runs a single test and is useful for debugging a test.

TestViewerApp

TestViewerApp is an application that provides the ability to interactively run the tests found in Engine2DTests. This is useful to ensure that the tests are functioning as designed, and for debugging.

TO run it, open the file build/test/TestViewerApp-en.html in a browser.

TestViewerApp has a set of menus for selecting which test to run. It looks for global functions whose name ends _setup and includes those in the menus. Therefore these _setup functions must be exported, see [Exporting Symbols][].

TestViewerApp only does the setup portion of the test, it won’t check the test results.

Note that subtle differences can occur in TestViewerApp which can make the results different from when tests are run by Engine2DTestRig. There are many options available with ContactSim, so you need to be careful to make sure they are the same in both places when debugging. The state of the random number generator is particularly important; the test setup function should set the seed of the random number generaotor for this reason.

Performance Tests

PerformanceTests runs a small set of performance tests of the engine2D physics engine.

Copy the file sampleMachineName.js to MachineName.js and edit the file to have a unique name for your development machine.

To run engine2D performance tests open the file build/test/PerformanceTests-en.html in a browser.

A performance test gets an expected time limit from properties defined in src/test/ExpectedPerf.js. Each combination of test, browser, and machine has a different expected time limit. The test passes if the time to run the test is near the expected time.

Because performance is different on each browser and machine, each computer that the performance test is run on should be given a unique machine name which is specified in the file MachineName.js. This MachineName.js file is not checked in to the source repository because it is different on each machine. There is a file sampleMachineName.js which can be copied and modified to specify the unique machine name.

The file src/test/ExpectedPerf.js is checked in to the repository so that we can then compare running times across various browsers and machines.

If the file src/test/MachineName.js doesn’t exist, then the performance test will still run and report the how long each test took, but the tests will all pass because the default expected time is very long (10000 seconds).

Internationalization (i18n)

This section explains programming details concerning internationalization (i18n).

How To Set Locale

To set the locale (language) when building an application, set the LOCALE variable during the make process; see Customizing The Build Process.

The ISO 639–1 language code is a two-letter lowercase code. For example, English is en, and German is de. The LOCALE variable seen in the makefile is inserted into the compiled code via the esbuild option

--define:MPL_LOCALE='"en"'

This defines the MPL_LOCALE variable which is then stored into Util.LOCALE.

Localized Strings

Most classes have a static i18n property where localized strings are stored. Strings are defined in versions for each supported language. The localized strings are found at the end of a class file. Here is an example from SimRunner with English and German strings.

type i18n_strings = {
  TIME_STEP: string,
  DISPLAY_PERIOD: string
}

static readonly en: i18n_strings = {
  TIME_STEP: 'time step',
  DISPLAY_PERIOD: 'display period'
};

static readonly de_strings: i18n_strings = {
  TIME_STEP: 'Zeitschritt',
  DISPLAY_PERIOD: 'Bild Dauer'
};

static readonly i18n = Util.LOCALE === 'de' ? SimRunner.de_strings : SimRunner.en;

Code that refers to SimRunner.i18n.TIME_STEP will use the localized version of that string in the current locale.

Language Independent Names

For scripting purposes it is often necessary to have a language independent name, so that a script can work regardless of the current locale. A language independent name is derived from the English version of the name by running it through the function Util.toName() which turns the name into all caps and replaces spaces and dashes with underscores. For example:

Util.toName('time step')

will return 'TIME_STEP'. Equivalently, you can use the property name:

Util.toName(SimRunner.en.TIME_STEP);

Many methods that deal with finding objects by name will do the toName() conversion automatically. In the following example, the VarsList.getVariable() method internally converts the input argument string 'angle-1' to the language independent form 'ANGLE_1':

vars = sim.getVarsList();
var angle = vars.getVariable('angle-1');
angle.setValue(Math.PI/4);

Other methods that convert an input string argument to a language independent version include:

Using the above methods helps avoid errors like the following from RigidBodyRollerApp:

if (this.path.getName() == CirclePath.en.NAME) {

That comparison won’t work because NumericalPath.getName() returns the language-independent version of the name which is not the same as the English version. There are two ways to fix this:

if (this.path.getName() == Util.toName(CirclePath.en.NAME)) {

or:

if (this.path.nameEquals(CirclePath.en.NAME)) {

Non-ASCII Characters

Because we use UTF-8 file encoding, it is appropriate to use non-ASCII characters in source code. Follow the Google JavaScript Style Guide section about Special Characters.

File Naming Convention

The last characters of a file name specify the locale, for example DoublePendulumApp-en.html is the English version which loads DoublePendulumApp-en.js, whereas DoublePendulumApp-de.html is the German version which loads DoublePendulumApp-de.js.

Bundling with esbuild creates a JavaScript file that is targeted for a particular locale, depending on the setting of the LOCALE variable in the makefile. So we typically make a separate bundled file for each language locale. The HTML web page then decides which localized compiled version to load.

The makefile handles the details about how to produce the desired localized version, you only have to tell it the name of the file you want to build, and the makefile figures out what the locale is from the requested filename. For example, the command

make build/sims/pendulum/DoublePendulumApp-de.html

results in the German version of the JavaScript and HTML files being made. Equivalently, you can use the shortcut name and specify the locale like this:

make doublependulum LOCALE=de

See Customizing The Build Process.

Separate File Per Locale

Note: before 2023, myPhysicsLab was built with the Google Closure Compiler. Since 2023 myPhysicsLab is built with TypeScript and esbuild. But the following approach to i18n is still in place, for now.

This approach of having a separate file for each locale is recommended in the book Closure: The Definitive Guide by Michael Bolin:

In the Closure Library, the intended technique for doing i18n is for the Compiler to produce one JavaScript file per locale with the translations for the respective locale baked into the file. This differs from other schemes, in which there is one file per locale that contains only translated messages assigned to variables and one JavaScript file with the bulk of the application logic that uses the message variables. Implementing the “two file” approach is also an option for doing i18n in the Closure Library today: the file with the translated messages should redefine goog.getMsg() as follows:

// File with application logic:
var MSG_HELLO_WORLD = goog.getMsg('hello world');

// File with translations:
goog.getMsg = function(str, values) {
  switch (str) {
    case 'hello world': return 'hola mundo';
    default: throw Error('No translation for: ' + str);
  }
};

The Compiler is not designed for the “two file” scheme because the “single file” scheme results in less JavaScript for the user to download. The drawback is that locales cannot be changed at runtime in the application (note how Google applications, such as Gmail, reload themselves if the user changes her locale setting), but this is fairly infrequent and is considered a reasonable trade-off.

From Closure: The Definitive Guide, by Michael Bolin, p. 67.

The code size savings only happen when using the Google Closure Compiler with “advanced” compilation which removes dead code. When myPhysicsLab is compiled by bundling with esbuild there are no savings of code size, in fact all the strings for each locale are included, but only one set of locale strings are used.

It is most practical to regard the locale as fixed when the web page loads so that we don’t have to rebuild or modify user interface controls when switching locale. Plus we may want to provide translated text on the web page itself, which usually requires having a separate web page per locale.

Language Menu in HTML File

The scheme of naming HTML and JavaScript files for a particular locale is seen in how the language menu operates in the HTML files for the various applications. For example in the compiled version DoublePendulumApp-en.html there is code like this:

<select id='language_menu'
  onchange='location = this.options[this.selectedIndex].value;'>
  <option value='DoublePendulumApp-en.html' selected>
    English
  </option>
  <option value='DoublePendulumApp-de.html' >
    German
  </option>
</select>

That creates a menu for selecting the current locale; selecting the German option would load the file DoublePendulumApp-de.html.

What should be localized?

Names that are visible to users should be localized. This includes:

What should not be localized?

Some names are not seen by the user, they are only used for scripting so they only have language-independent names. These include:

For example in SimList there are GenericEvents that occur when objects are added or removed; SimList defines only language independent names for these:

SimList.OBJECT_ADDED = 'OBJECT_ADDED';
SimList.OBJECT_REMOVED = 'OBJECT_REMOVED';

Using the static property reference SimList.OBJECT_ADDED rather than the literal string ‘OBJECT_ADDED’ will remain correct in case the string is ever changed.

Programming Details

Macros in HTML Files

The reason that the HTML files in the source directories cannot be used directly from a browser is that they contain macros (or “templates”) which must be expanded to become proper HTML.

When you look at the Start-Up HTML Files for applications, you will notice a mix of regular HTML and macros. The macros are the words starting with a hash-tag. for example:

#define #TOP_DIR ../..
#define #APP_PATH sims.pendulum.PendulumApp
#include "#TOP_DIR/macros.html"
#include "#TOP_DIR/macros_tab.html"
#DOC_TYPE
<html lang="en">
<head>
#META_TAGS
#HIDE_ALL
</head>
<body>
#SITE_LOGO
<h1>#APP_PATH</h1>
#HEADER_BAR
#CONTROLS_CONTAINER

During the build process, the .html file undergoes text manipulation to expand macro definitions (roughly similar to macros in the C preprocessor). The expanded version is written to a new file in the build directory.

The text manipulation is done with a perl script called prep_html.pl. That perl script interprets text-replacement rules defined in the macros.html and macros_tab.html files in the above example.

See the documentation at the start of prep_html.pl for more information.

Debugging

In the makefile find the esbuild command and remove the --minify option. Then the generated code will look just like it does in the source code.

There are various debugging flags that can be turned on in the code.

Bundling and Minification

The esbuild tool is used to bundle all the various classes needed for a particular application into a single JavaScript file. This tool also performs some minification, which reduces the length of names in the code that are not visible to the outside.

For the SingleSpringApp the resulting single JavaScript file for English is SingleSpringApp-en.js. In SingleSpringApp-en.html the code is executed like this

<script src="SingleSpringApp-en.js"></script>

This causes the class SingleSpringApp to be defined, along with many other classes.

Class Global Variables

Many classes define a global variable for the class. To avoid conflicts with pre-existing global variables, these globals have a longer name, for example sims$springs$SingleSpringApp. At the end of most class source files is a command that creates the global variable, for example

Util.defineGlobal('sims$springs$SingleSpringApp', SingleSpringApp);

The global variable for the application is used in the HTML file to instantiate the application. In SingleSpringApp-en.html it happens here:

app = new sims$springs$SingleSpringApp(elem_ids);

The other class names are used in Terminal Scripts for interactive programming. For example, to instantiate a Vector you can execute in Terminal

new lab$util$Vector(1, 2)

Terminal provides translations from “short names” to “long names” so that you can simply write

new Vector(1, 2)

App Global Variable

When an application is instantiated, a global variable app is created to hold it. For example, in SingleSpringApp-en.html

app = new sims$springs$SingleSpringApp(elem_ids);

When there are multiple applications on a page, these might be called app1, app2, etc.

The name of this global is passed to app.defineNames() so that short-names in scripts can be properly expanded during Terminal script execution.

    app.defineNames('app');

toString() format

The toString() method should print a format that looks somewhat like a JavaScript object literal preceded by the name of the class. Here is an example of a PointMass object; note that there are nested Vector objects inside it.

PointMass{
  name_: "BLOCK",
  expireTime_: Infinity,
  mass_: 0.5,
  loc_world_: Vector{
    x: -0.72021,
    y: 0
  },
  angle_: 0,
  velocity_: Vector{
    x: 4.38584,
    y: 0
  },
  angular_velocity_: 0,
  cm_body_: Vector{
    x: 0,
    y: 0
  },
  zeroEnergyLevel_: null,
  moment_: 0,
  shape_: 1,
  width_: 0.4,
  height_: 0.8
}

The indentation shown above is done automatically by the function Util.prettyPrint. Suppose you have a PointMass object in the variable block1, then the following would produce the above output

prettyPrint(block1)

Without pretty-printing, the above output of toString() looks like this:

PointMass{name_: "BLOCK", expireTime_: Infinity, mass_: 0.5, loc_world_: Vector{x:
-0.72021, y: 0}, angle_: 0, velocity_: Vector{x: 4.38584, y: 0}, angular_velocity_:
0, cm_body_: Vector{x: 0, y: 0}, zeroEnergyLevel_: null, moment_: 0, shape_: 1,
width_: 0.4, height_: 0.8}

toStringShort()

There is an alternate version of toString() called toStringShort() which prints minimal identifying information for the object, often just the class name and the name of the object. Many myPhysicsLab classes implement the Printable interface which specifies the toStringShort() method. There are two reasons for using the toStringShort() method:

  1. avoids infinite loops There can be circular references between objects – if a toString() method calls toString() on another object an infinite loop could occur. The toStringShort() method is safe because it never lists any sub-objects.

  2. makes toString() output more readable When an object has another sub-object as a property it may be important to show the sub-object, but a full toString() printout of the sub-object can result in too much output and distracts from showing the object.

Here is a pretty-printed example showing an array-like object where each member of the elements_ array is printed using toStringShort()

SimList{
  name_: "SIM_LIST",
  length: 3,
  tolerance_: 0.1,
  elements_: [
    PointMass{name_: "BLOCK"},
    PointMass{name_: "FIXED_POINT"},
    Spring{name_: "SPRING"}
  ],
  parameters: [
  ],
  observers: [
  ]
}

Programming Style

Sometimes variables start with an underscore, for example _timestep in the evaluate() method

evaluate(vars: number[], change: number[], _timeStep: number): null|object {

The underscore suppresses a TypeScript compiler warning “unused parameter”.

See Google JavaScript Style Guide; those style guidelines are mostly followed by myPhysicsLab code.

The order of items in a TypeScript source code file should be:

There are exceptions to keeping things in alphabetic order when a different order makes the code more readable.

Documentation Guidelines

Every public class, interface, method, function, enum, etc. should have documentation. Use markdown, following examples found in the code.

HTML tags can be used in markdown , for example <img> and <pre> tags can be used to add diagrams.

There are two flavors of markdown in use depending on whether the documentation is in TypeScript code (which is processed by Typedoc) or is in a markdown .md file (which is processed by MultiMarkdown).

See Documentation Links for the numerous wasy of writing links in myphysicslab documentation.

General documentation guidelines:

  1. The first line should be a summary of what the class/interface/function does.

  2. The first paragraph should give the gist of what the class does: a quick-to-read summary.

  3. Document what is relevant to a user of the class, rather than details about how the class works internally.

  4. Code examples and diagrams are helpful.

  5. Use section headings to group concepts.

  6. Supply links to relevant classes, to functions within the class, or to other docs.

References

esbuild is used to bundle and minify the JavaScript code.

GNU Make

LaTeX is used to produce .pdf files from .tex files using the pdflatex command. The MacTeX Distribution is an easy way for Mac users to get the necessary tools.

MultiMarkdown is used for myPhysicsLab documentation files ending in .md such as Overview.md. MultiMarkdown adds some features to standard markdown such as having links to sections within a page. See MultiMarkDown Guide for summary of available commands.

OmniGraffle is used for creating documentation diagrams. Files have suffix .graffle. They are exported to .svg or .pdf files which are included in the documentation. Version 4.2.2 of OmniGraffle was used.

Perl

Typedoc creates documentation from TypeScript files. The Doc Comments page describes the particular flavor of markdown used.

TypeScript