How to align the coordinate system of your tracking device with the physical coordinates of your space.

# Swapping Axes

Unless you're more mathematically sophisticated than we are, this is the tricky bit. You've got your tracking device talking with a Syzygy device driver, but its native coordinate axes point in different directions from the Syzygy coordinate system (which, like OpenGL coordinates, is +X=right, +Y=up, +Z=back when you are facing in your VR apparatus' "forwards" direction. For example, in the Beckman Institute Cube the axes are +X=East, +Y=up, +Z=south). How to get the two sets of axes aligned?

## Mapping the Tracker Axes

First, if you don't already know it you need to determine what directions the tracker axes are pointed. This is easiest to do with the DeviceClient utility in 'position' mode. For example, if you're running your tracker as input service `SZG_INPUT0` in the context of a virtual computer named 'vc', then you would start DeviceClient using e.g.:

```  DeviceClient 0 -position 0 -szg virtual=vc
```

...and it would print out a stream of positions computed from the incoming values of matrix event #0, which would correspond to the head tracking sensor (the first '0' above is the input service number, while the second is the matrix event index). Now you can move the sensor around and observe how the reported position values change from place to place.

## The Axis-Transformation Equation

You perform the axis transformation by bracketing each incoming matrix event with a pair of matrices, one of which is the inverse of the other:

```  M' = C * M * C-1
```

The `C` matrices are constructed such that when multiplied by M they swap two of its rows or columns (depending on whether it's left- or right-multiplication) along with an optional change of sign (multiplication by -1). Each row and column of each of these matrices has exactly one non-zero element, which is equal to 1 or -1, and the lower-right element is 1.

## Determining the Axis-Transformation Matrices: An Example

Take the Ascension Flock of Birds™ tracker. It uses a right-handed coordinate system with the X-axis pointing away from the transmitter power cable and the Z-axis pointing down (i.e. towards the side containing the hole for the mounting screw). Let's say that we've got it mounted so that the power cable points forwards (away from the user), such that +X=back, +Y=left, +Z=down, and we've confirmed with DeviceClient that these are the tracker coordinate axes. We need to map these to the Syzygy coordinates +X=right, +Y=up, +Z=back. In other words we want:

```  Tracker X => Syzygy +Z
Tracker Y => Syzygy -X
Tracker Z => Syzygy -Y
```

We construct the transformation matrix using the table below. The tracker axes go along the top and the desired Syzygy axes along the side, with the sign of the non-zero element corresponding to the sign of the mapping:

Tracker X Tracker Y Tracker Z
Syzygy X 0 -1 0 0
Syzygy Y 0 0 -1 0
Syzygy Z 1 0 0 0
0 0 0 1

i.e.

and because these are orthonormal matrices, the inverse is the same as the transpose, i.e.:

## Implementing the Axis Transformation in a PForth Filter

The best way to implement these transformations is in a PForth filter inside your global input device definition. Here's an example using the above transformations:

```<param>
<name>
fob_tracker
</name>
<value>
<szg_device>
<input_sources> arBirdWinDriver </input_sources>
<input_sinks></input_sinks>
<input_filters></input_filters>
<pforth>
/* Declare matrix variables (each 'matrix' call allocates
16 cells in the dataspace and defines a new word, e.g.
'Xin', that pushes the address of the first cell onto
the stack. */
matrix Xin
matrix Xout
matrix C
matrix Cinv

/* Store transformation matrices. */
0 -1  0  0
0  0  -1 0
1  0  0  0
0  0  0  1
C matrixStoreTranspose

0 -1  0  0
0  0  -1 0
1  0  0  0
0  0  0  1
Cinv matrixStore

/* Define 'filter' word to be called when any matrix event
passes through the filter. */
define filter_all_matrices
Xin getCurrentEventMatrix
/* Multiply C * Xin * Cinv, store result in Xout */
C Xin Cinv 3 Xout concatMatrices
Xout setCurrentEventMatrix
enddef
</pforth>
</szg_device>
</value>
</param>
```

This device definition loads one device driver plugin (shared library): `arBirdWinDriver`, the Syzygy Flock-of-Birds™ driver that is based on the Bird.dll supplied by Ascension (Windows only). It also defines a PForth filter to be applied to the output of this device.

PForth (or PseudoForth) is a FORTH-like language. This is, it's stack-based and uses RPN notation like an HP calculator. For example, to add two numbers together you would type `3 2 +` ("Place the numbers 3 and 2 on the stack, then call the '+' word, which takes the top two numbers off the stack and pushes their sum onto the stack"). PForth is compiled; when the PForth filter is loaded, the source code gets converted into an STL vector<> of pointers to objects, one for each PForth word. Running a filter word consists in iterating through its vector<> of pointers and calling `i->action()` for each one, so it's quite fast.

The filter defines four matrix variables, `Xin`, `Xout`, `C`, and `Cinv`. `Xin` and `Xout` are just temporary storage for the initial and final values of the tracker matrices. `C` and `Cinv` are the two axis-transformation matrices we constructed above. It may be a bit confusing that we use `matrixStoreTranspose` to store the values in `C` and `matrixStore` for `Cinv`. In Syzygy, as in OpenGL, matrix values are internally stored in a one-dimensional array with the row subscript varying faster, i.e. going down the first column, then the second, and so on. The `matrixStore` word reads in the numbers and stores them in the same order: In other words, the numbers in the top row of the matrix text above end up being stored internally as the first column of the matrix, so you end up with the transpose of the matrix as it appears in the PForth source code. The `matrixStoreTranspose` word allows you to enter matrices in human-readable form.

Finally, the code defines a filter word, `filter_all_matrices`, which will be applied to any outgoing matrix event. The matrix value is stored in `Xin` in the first line, the transformation equation is applied in the second (`concatMatrices` multiplies together a variable number of matrices, specified in this case by the '3'), and the result is stuffed back into the matrix event in the third.

Still to do...

# Specifying the Origin Offset

Suppose the aforementioned tracker transmitter is mounted two feet off the ground, so we need to add 2 to the Y position coordinate of each matrix event from the tracker. Modify the PForth program as follows:

```        /* Declare matrix variables (each 'matrix' call allocates
16 cells in the dataspace and defines a new word, e.g.
'Xin', that pushes the address of the first cell onto
the stack. */
matrix Xin
matrix Xout
matrix C
matrix Cinv
matrix originOffset

/* Store transformation matrices. */
0 -1  0  0
0  0  -1 0
1  0  0  0
0  0  0  1
C matrixStoreTranspose

0 -1  0  0
0  0  -1 0
1  0  0  0
0  0  0  1
Cinv matrixStore

/* create a matrix that translates by (x,y,z)=(0,2,0) */
0 2 0 originOffset translationMatrix

/* Define 'filter' word to be called when any matrix event
passes through the filter. */
define filter_all_matrices
Xin getCurrentEventMatrix
/* Multiply originOffset * C * Xin * Cinv, store result in Xout */
originOffset C Xin Cinv 4 Xout concatMatrices
Xout setCurrentEventMatrix
enddef
```

The tracker origin offset transformation comes before the rest.

# Correcting Sensor Orientations

Now we have the problem of attaching tracking sensors to things that we want to track. The natural orientation for the Flock-of-Birds™ sensors is base-down with the cord pointing forwards. That will most likely not be a convenient way to mount them. Let's say that we have two of them and we want to mount one on the left side of a pair of glasses with the base facing right and the cord facing the back; this sensor will provide matrix event #0. The other one will be mounted on the bottom of a gamepad with the base facing up and cord facing back; this will provide matrix event #1.

The first mounting transformation can be composed of a 180-degree rotation around the Y axis (to get the cord pointing backwards) followed by a -90-degree rotation around the Z-axis (remember, after the first rotation the positive Z axis points forwards).

The second one is a 180-degree rotation around Y followed by another 180-degree rotation around Z.

We extend the PForth filter as follows:

```        /* Declare matrix variables (each 'matrix' call allocates
16 cells in the dataspace and defines a new word, e.g.
'Xin', that pushes the address of the first cell onto
the stack. */
matrix Xin
matrix Xout
matrix C
matrix Cinv
matrix originOffset

/* Store transformation matrices. */
0 -1  0  0
0  0  -1 0
1  0  0  0
0  0  0  1
C matrixStoreTranspose

0 -1  0  0
0  0  -1 0
1  0  0  0
0  0  0  1
Cinv matrixStore

/* create a matrix that translates by (x,y,z)=(0,2,0) */
0 2 0 originOffset translationMatrix

/* Define 'filter' word to be called when any matrix event
passes through the filter.  Applies transmitter coordinate
transformations (common to all sensors) */
define filter_all_matrices
Xin getCurrentEventMatrix
/* Multiply originOffset * C * Xin * Cinv, store result in Xout */
originOffset C Xin Cinv 4 Xout concatMatrices
Xout setCurrentEventMatrix
enddef

matrix ySensorRot

/* Construct 3 rotation matrices, 2 containing separate
180 yaxis ySensorRot rotationMatrix

/* Define filter words to be called when matrix events with
particular indices (i.e. that originate from particular
sensors) pass through. They apply mounting transformations
for individual sensors */
define filter_matrix_0
Xin getCurrentEventMatrix
All of the new code is at the bottom. We need five additional matrices, one for each of the three rotation components discussed above and two more to hold the concatenation of the two components for the head and for the gamepad. We could have left the components separate and multiplied them inside the `define`/`enddef` blocks using `concatMatrices`, but that would have been less efficient; better to perform the multiplication once at compile time when the filter is initialized.
The `filter_matrix_#` words are called for matrix events with the appropriate index # after `filter_all_matrices` is called.