My accelerometer blog ended rather abruptly, without a demo program so today I’m returning to the topic.
To test the accelerometer control I wrote a very simple program in which you can move a rectangle around the screen by tilting your iOS device. You can test it here.
This program builds heavily upon the source code I found on Alberto Sarullo’s blog. Thanks!
FCount counts number of times the ‘motion change’ event has been called. FMotion contains the TGpW3Motion control. FRect is used to display the bouncing rectangle on the screen. FRectColor is the color of this rectangle. FRectText is the text displayed inside the rectangle. FvX and FvY are rectangle speeds in horizontal and vertical (relative to the current display orientation) direction, respectively.
FvX, FvY: float;
function BounceX(x: float): float;
function BounceY(y: float): float;
procedure HandleMotionChange(Sender: TObject; info: TW3MotionData);
procedure LogMotion(info: TW3MotionData);
procedure Resize; override;
Initialization is pretty standard. Components are created, events hooked up and initial color for the rectangle is set.
FMotion := TGpW3Motion.Create(Self);
FMotion.OnChange := HandleMotionChange;
FRect := TW3GraphicControl.Create(Self);
FRect.OnPaint := PaintRect;
Nothing special happens in Resize, either. Rectangle is positioned in the middle of the screen and its velocity is set to zero.
FvX := 0; FvY := 0;
FRect.Width := 200;
FRect.Height := 200;
FRect.Top := (Height - FRect.Height) div 2;
FRect.Left:= (Width - FRect.Width) div 2;
RandomizeRectColor chooses RGB components which are random but all on the bright side so the rectangle will be of a slightly tinted light greyish tone.
FRectColor := RGBToColor(255 - Round(Random*35), 255 - Round(Random*35),
255 - Round(Random*35));
And that concludes the boring part.
Handling the Motion Event
TGpW3Motion.OnChange event does three distinct things. First it updates the velocity regarding the current acceleration (the speed calculation comes straight from the physics – v2 = v1 + a * t), then it logs the information about the motion data object and at the end it updates the rectangle coordinates.
Updating deserves more detailed description. I’ve wrapped it into BeginUpdate/EndUpdate calls to prevent rectangle from being redrawn twice. Speed is converted into distance (s2 = s1 + v * t) with an additional speedup factor. This factor was experimentally chosen so the application behaves nicely and responsively. Resulting offset is passed through the bounce function which bounces the rectangle off the edge of the screen.
procedure TFormMain.HandleMotionChange(Sender: TObject; info: TW3MotionData);
CSpeedUp = 200;
x, y: integer;
FvX := FvX + info.DisplayAccel.Horizontal * info.Interval;
FvY := FvY + info.DisplayAccel.Vertical * info.Interval;
FRect.Left := Round(BounceX(FRect.Left + FvX * info.Interval * CSpeedUp));
FRect.Top := Round(BounceY(FRect.Top + FvY * info.Interval * CSpeedUp));
There are two bounce functions – one handling the horizontal edges and another the verticals – but I’ll only show one because they are pretty much the same.
function TFormMain.BounceX(x: float): float;
Result := x;
if Result < 0 then
Result := -Result
else if Result > (Width - FRect.Width) then
Result := 2*(Width - FRect.Width) - Result
FvX := -FvX*0.8;
If the rectangle falls at least partially off the edge of the screen, its position is moved back into the display area (by as much as it has fallen off the screen) and speed is reduced by 20%. Again, this is an arbitrary number which I choose purely for the effect. You could do anything here – stop the rectangle in place by setting the velocity to 0 (or both velocity components to 0) or you could simulate an active edge by giving the rectangle an additional push by increasing the velocity.
LogMotion method merely sets a very long string which uses <br/> tags for line breaks. The reason for this is that initially I was displaying motion data with a TW3Label component which handles <br/> nicely.
procedure TFormMain.LogMotion(info: TW3MotionData);
FRectText := Format('%d: %.2f<br/>x:%.4f y:%.4f z:%.4f<br/>' +
'x:%.4fg y:%.4fg z:%.4fg<br/>'+
'a:%.4f b:%.4f g:%.4f<br/><br/>' +
'h:%.4f v:%.4f<br/><br/>' +
info.Acceleration.X, info.Acceleration.Y, info.Acceleration.Z,
info.AccelInclGravity.X, info.AccelInclGravity.Y, info.AccelInclGravity.Z,
info.RotationRate.Alpha, info.RotationRate.Beta, info.RotationRate.Gamma,
PaintRect method paints the rectangle. First it fills it with the current color and then it displays the FRectText variable. There are some complication, though, and they are caused not by Smart but by the HTML5 canvas.
The only way to draw text on the HTML5 canvas is the FillText function and it doesn’t handle multiline text. To circumvent this limitation, PaintRect uses w3_StrToArray built-in function to split the text into an array of lines and then paints lines one by one.
graph := sender as TW3GraphicControl;
Canvas.FillStyle := ColorToWebStr(FRectColor);
Canvas.FillRectF(0, 0, Width, Height);
Canvas.TextAlign := 'center';
Canvas.TextBaseLine := 'middle';
Canvas.FillStyle := '#000000';
Canvas.Font := '12px sans-serif';
text := w3_StrToArray(FRectText, '<br/>');
firstY := Round((graph.Height / 2) - 15 * ((text.Length-1) / 2));
for iText := 0 to text.Length - 1 do
Canvas.FillTextF(text[iText], graph.Width div 2, firstY + 15 * iText, 0);
And that’s how it looks on your screen.
If you try out the demo, you’ll see that the data coming from the accelerometer is very jittery. If you put the device on a perfectly flat surface, you won’t get zero from the accelerometer but the value will jump around zero, sometimes it will be positive and sometimes it will be negative. To reduce the impact of this jitter on your application I intend to implement a smoothing algorithm in the TGpW3Motion component.
Another planned change is the calibration capability. iOS offers no support for accelerometer calibration so you had to do it in your code. The procedure is simple – just add (subtract) the ‘would be zero’ acceleration values to (from) the accelerometer data – but it would be even simpler if the component would handle it.