@mvarble/frames.js

A set of tools for working with coordinate systems in the plane.

Usage no npm install needed!

<script type="module">
  import mvarbleFramesJs from 'https://cdn.skypack.dev/@mvarble/frames.js';
</script>

README

frames.js

A set of tools for working with coordinate systems in the plane.

Download

npm install @mvarble/frames.js

The Mathematics

One can simply encode affine coordinate systems in the plane by 3x3 frame matrices. This way, calculations like rigid body transformations and coordinate transforms can be streamlined into simple matrix multiplication calculations. The purpose of this module is to wrap all such calculations into a simple API.

The Code

Because we would sometimes like a frame relative to another, it is very natural to store our frames in a tree structure. To this end, a frame is nothing but a JSON object which has worldMatrix and children fields. Because frames are created primarily for rendering to the HTML canvas, one should be able to easily search through all the frames within a given context and access any sort of metadata each frame has. It is then decided that the objects will behave like unist trees. Consider the example below.

const boxFrame = {
  type: 'box',
  worldMatrix: [[10, 0, 420], [0, -10, 69], [0, 0, 1]],
};

const planeFrame = {
  type: 'frame',
  worldMatrix: [[10, 0, 400], [0, -10, 400], [0, 0, 1]],
  children: [boxFrame],
};

Since our frames are encoded with simple JSON objects, the magic of the module is in the functions which parse/generate frames. For transforming frames, we have an API for updating the frame's world matrix explicitly, providing affine transformations, or simply saying which basic (translate/rotate/scale) operation we want. Each such transformation has two froms; one which is a pure function which simply returns a new frame and an impure one that simply manipulates the frame.worldMatrix attribute. Below, transformation will be identified with its (pure/impure) function names.

We can also perform coordinate calculations which allow one to get the coordinates of a location/vector in a particular frame's coordinate system, subject to having those from another frame's coordinate system. These functions are always pure, as there aren't many examples in which one would want otherwise.

API

Since this is a small package, one can look at the source code here. Otherwise, for the most part, there are only several functions which are very useful.

Transformations

If one would like to return a translated/rotated/scaled version of a frame, it is as simple as using the following functions.

translatedFrame/translateFrame

newFrame = translatedFrame(frame, disp [, relativeFrame])

This will return a frame which corresponds to translating frame by [disp[0], disp[1]] in the coordinate system of relativeFrame. The relativeFrame will be frame if not provided. The following shows the red frame being translated at a constant velocity of [3, 4] units/second with respect to different coordinate systems. The first is translatedFrame(redFrame, [t*3, t*4]) while the second is translatedFrame(redFrame, [t*3, t*4], greenFrame) with respect to time t.

translated self translated relative

rotatedFrame/rotateFrame

newFrame = rotatedFrame(frame, theta, [, relativeFrame])

This will return a frame which corresponds to rotating frame by theta in the coordinate system of relativeFrame. Note. relativeFrame should have an orthogonal basis with both vectors having the same length in order for this to make sense. The relativeFrame will be frame if not provided. The following shows the red frame being rotated at a angular velocity of 2pi radians/second with respect to different coordinate systems. The first is rotatedFrame(redFrame, 2 * Math.PI * t) while the second is rotatedFrame(redFrame, 2 * Math.PI * t, greenFrame) with respect to time t.

rotated self rotated relative

scaledFrame/scaleFrame

newFrame = scaledFrame(frame, scales, [, relativeFrame])

This will return a frame which corresponds to scaling frame by [scales[0], scales[1]] in the coordinate system of relativeFrame. The relativeFrame will be frame if not provided. The following shows the red frame being scaled with respect to different coordinate systems. The first is scaledFrame(redFrame, [0.9 * (1 - t), 0.9 * (1 - t)]) while the second is scaledFrame(redFrame, [0.9 * (1 - t), 0.9 * (1 - t)], greenFrame) with respect to time t.

scaled self scaled relative

Locations/Vectors in Different Frames

A location is considered to be a point in the plane. A vector is considered to be a displacement in the plane. The key distinction is that the coordinates of a vector in any frame should be independent of the frame's position in the plane.

locFrameTrans

sameLoc = locFrameTrans(loc, frame1, frame2)

This will return a pair [a, b] corresponding to the frame2-coordinates of the location with frame1-coordinates [loc[0], loc[1]].

locsFrameTrans

sameLocs = locsFrameTrans(locs, frame1, frame2)

This will simply run locs.map(loc => locFrameTrans(loc, frame1, frame2)). I am not savy enough with javascript to bootstrap this into a single function.

vecFrameTrans

sameVec = vecFrameTrans(vec, frame1, frame2)

This will return a pair [a, b] corresponding to the frame2-coordinates of the vector with frame1-coordinates [vec[0], vec[1]]. This is different from locFrameTrans, as we consider vectors independent of the origin of the frame.

vecsFrameTrans

sameVecs = vecsFrameTrans(vecs, frame1, frame2)

This will simply run vecs.map(vec => vecFrameTrans(vec, frame1, frame2)). I am not savy enough with javascript to bootstrap this into a single function.

Examples

  • viewport.js: an example of how one can use this module for more interactive apps.