The Wiimote includes an IR sensor that provides position data of up to four infrared hotspots. This data needs to be converted to a cursor position for most applications. What follows is a rough sketch of an algorithm to do this, inspired by the official behavior. Contributions are welcome, as usual.
The Wiimote's acceleration values are actually an important part of the pointer functionality, since they provide the orientation of the Wiimote with respect to gravity.
For now, I'm not taking into account the perspective effects of having the wiimote at a horizontal plus vertical angle to the sensor bar. Purely vertical angles are okay, as are purely horizontal angles, but the combination will make the sensor bar appear at an angle when it really isn't. How much this is significant will have to be investigated, but if we need to we should be able to compensate for this using measurements from the accelerometer and some trigonometry.
Finding the Sensor Bar
The first step in the algorithm is locating the sensor bar. Since spurious IR sources may be present, we need to locate the two dots that most likely correspond to the sensor bar. To do this, we look for the two dots that are at least spaced a certain minimum distance appart (to remove any possible duplicated dots if the wiimote picks up several dots clustered around one IR emitter) that lie in a horizontal plane, using the wiimote's tilt calculated from the accelerometers to determine what this plane should be. Any pairs off by a certain margin or more (say, 10 degrees) are thrown away. From the remaining pairs we should pick the two that are closest to the horizontal, while avoiding any pair with a third dot very close to the imaginary line between them. This is because there should never be an IR emitter between the sensor bad edges, so the outermost pair of a horizontal line of three dots can't be the sensor bar, for example.
If the Wiimote loses tracking of one of the ends (it disappears from view after being close to the edge of the sensor area), it should attempt to continue using the other dot and making a best guess as to where the first one is. For example, the algorithm should keep the distance between the dots constant, and use the accelerometer data to calculate the angle:
- Calculate the distance between the dots as last seen
- Calculate the angle from the accelerometers when the dots were last seen
- Calculate the angle of the last two dots as last seen, subtract and store the result until the dot comes back
- While we only have one dot, make a new guess for the angle, using the result of the subtraction to map the original tilt to be equal to the original angle (this gets rid of any jerks in the sensed angle, since the new angle is based on the old one).
- The off-sensor dot is at the calculated angle from the on-sensor dot, at the original distance.
This obviously doesn't take into account any change of distance between the wiimote and the screen, but we can't do anything about that.
Once we know the two dots of the sensor bar, we rotate the sensor field to make the two dots appear on a horizontal line (using the accelerometer values to make sure we're in the right quadrant if the wiimote is upside down). The angle of rotation corresponds to the angle of the wiimote as reported to the software. We should probably apply at least a basic moving average filter to this to reduce jerkiness. Once we have the sensor bar mapped into a new coordinate system where it is horizontal, we can simply take the average position of the two dots and use that to calculate the pointer position by defining a box to map the screen to it. The position of this box will vary depending on the sensor bar position setting (above, below TV). The box should probably be small enough to accommodate several angles without any dead areas (Mario Galaxy seems to be an offender here; I have trouble getting to one of the screen corners if the wiimote is at an angle to the sensor bar).
A rough pointing position can be found using the following formulas:
Rotation = Math.Atan2(Accelerometer.Z, Accelerometer.X) - (float)(Math.PI / 2.0); Position = (1 - Midpoint.x, Midpoint.y); Position.Subtract(0.5,0.5); Position.Rotate(Rotation); Position.Add(0.5,0.5)
If the midpoint is not available you can use the remaining sensor and compare it's position to the last position when both points were available. Then adjust the last known midpoint by the same amount that the visible sensor moved.
An example implementation in C# can be found at
Brian Peek's Library is used to get the sensor information from the Wiimote.
Smoothing the cursor
Especially for menus and GUIs, some filtering for the cursor position is desirable. I propose implementing a dragging circle scheme. The software will draw an imaginary circle around the new pointing position. If the cursor is outside of this circle, it will immediately drag it to lie within this circle. However, if the cursor is already within the circle, it will move it towards the circle's center at a speed proportional to the distance from the center. This smooths down the cursor movement and allows for some physical vibration while keeping a smooth cursor, but still allows for small corrections to take effect. This is just an idea and needs to be tested and improved, but I think it's a decent start.
Another option is to us a smoothing formula already in place. One smooths more the slower you move wiimote and smooths less the faster you move the wiimote. This looks very smooth but it is hard to make minor adjustments when you are only moving the wiimote a few pixels. Perhaps it would be better if it had a toggle button that sets the sensitivity level to a lower one. Or we could use a setup like in the Metroid Prime 3 menu with a toggle button in place.
A better option for things like Wii Linux would be to have a setup menu with a control settings window in it which allows the user to select a control style from a number of pre-made styles. Then the user could make adjustments to sliders that would change the sensitivity and some other options.
A rough distance value can be calculated by using the standard sensor bar dimensions and the distance between the dots. This is simply applying the model for perspective in reverse to find the distance of an object of width X if its camera width is Y.