Quickstart: Retrieving the Quaternion and rotation matrix with the Orientation sensor (C++)

You can use the Orientation sensor to retrieve a Quaternion and rotation matrix with an app written in C++. Developers typically use this data to control complex games.

The Orientation sensor returns two forms of data: a quaternion and a rotation matrix. A quaternion can be most easily understood as a rotation of a point [x,y,z] about an arbitrary axis (in contrast to a rotation matrix, which represents rotations around three axes). The mathematics behind quaternions is fairly exotic in that it involves the geometric properties of complex numbers and mathematical properties of imaginary numbers, but working with them is simple, and frameworks like DirectX support them.

A complex 3-D app can use the Orientation sensor to adjust the user's perspective. This sensor combines input from the accelerometer, gyrometer, and compass.

Roadmap: How does this topic relate to others? See: Roadmap for Windows Runtime apps using C++.

Objective: After completing this quickstart you will understand how to use the Orientation sensor to detect changes in movement.

Prerequisites

You should be familiar with XAML, Visual C++, and events.

The device or emulator that you're using must support an orientation sensor.

Time to complete: 25 minutes.

Instructions

1. Open Microsoft Visual Studio

Open an instance of Microsoft Visual Studio.

2. Create a new project

Create a new project, choosing a Blank App from the Visual C++/Store Apps project types.

3. Replace the header file contents

Open your project's MainPage.xaml.h file and replace the existing comments, code, and definitions with the following.

//
// MainPage.xaml.h
// Declaration of the MainPage.xaml class.
//

#pragma once


#include "MainPage.g.h"

namespace App1
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public ref class MainPage sealed
    {
    public:
        MainPage();

    private:
        void ReadingChanged(Windows::Devices::Sensors::OrientationSensor^ sender, Windows::Devices::Sensors::OrientationSensorReadingChangedEventArgs^ e);
        Windows::Devices::Sensors::OrientationSensor^ sensor;
        Windows::Foundation::EventRegistrationToken listenerToken;

    protected:
        virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override;
    };
}

You'll need to replace the class name in the previous snippet with the name of your app's class. For example, if you created a project named OrientationSensorCPP, you'd replace namespace App1 with namespace OrientationSensorCPP.

4. Replace the C++ code

Open your project's MainPage.xaml.cpp file and replace the existing code with the following.

//
// MainPage.xaml.cpp
// Implementation of the MainPage.xaml class.
//

#include "pch.h"
#include "MainPage.xaml.h"

using namespace App1;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Navigation;

// Below namespaces are required to support sensors and events
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Platform;
using namespace Windows::Devices::Sensors;
using namespace Windows::UI::Core;


// The Blank Page item template is documented at https://go.microsoft.com/fwlink/p/?linkid=234238

MainPage::MainPage()
{
    InitializeComponent();

    // Get the default accelerometer
    sensor = OrientationSensor::GetDefault();

    // Setup the event handler
    if (sensor != nullptr)
    {

        // Establish the report interval for all scenarios
        uint32 minReportInterval = sensor->MinimumReportInterval;
        uint32 reportInterval = minReportInterval > 16 ? minReportInterval : 16;
        sensor->ReportInterval = reportInterval;

        // Establish the event handler
        listenerToken = sensor->ReadingChanged::add(ref new TypedEventHandler<OrientationSensor^, OrientationSensorReadingChangedEventArgs^>(this, &MainPage::ReadingChanged));
   
    }  
}

// The ReadingChanged event handler is invoked each time
// we receive a new reading from the driver.

void MainPage::ReadingChanged(OrientationSensor^ /* sender */, OrientationSensorReadingChangedEventArgs^ e)
{

    auto ignored = Dispatcher->RunAsync(
        CoreDispatcherPriority::Normal,
        ref new DispatchedHandler(
            [this, e]()
            {
                OrientationSensorReading^ reading = e->Reading;

                // Quaternion values
                txtX->Text = reading->Quaternion->X.ToString();
                txtY->Text = reading->Quaternion->Y.ToString();
                txtZ->Text = reading->Quaternion->Z.ToString();
                txtW->Text = reading->Quaternion->W.ToString();

                // Rotation Matrix values
                txtM11->Text = reading->RotationMatrix->M11.ToString();
                txtM12->Text = reading->RotationMatrix->M12.ToString();
                txtM13->Text = reading->RotationMatrix->M13.ToString();
                txtM21->Text = reading->RotationMatrix->M21.ToString();
                txtM22->Text = reading->RotationMatrix->M22.ToString();
                txtM23->Text = reading->RotationMatrix->M23.ToString();
                txtM31->Text = reading->RotationMatrix->M31.ToString();
                txtM32->Text = reading->RotationMatrix->M32.ToString();
                txtM33->Text = reading->RotationMatrix->M33.ToString();
            },
            CallbackContext::Any
            )
        );
}

/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached.  The Parameter
/// property is typically used to configure the page.</param>
void MainPage::OnNavigatedTo(NavigationEventArgs^ e)
{
}

You'll need to rename the namespace in the previous snippet with the name you gave your project. For example, if you created a project named OrientationSensorCS, you'd replace using namespace App1; with using namespace OrientationSensorCPP;.

5. Replace the XAML code

Open the file MainPage.xaml and copy the following XML into this file (replacing its original contents).

<Page
    x:Class="Application1.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Application1"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid x:Name="LayoutRoot" Background="#FFF6F2F2">
        <TextBlock x:Name="txtM11"  Text=" No data" Foreground="#FF040404" Margin="0,22,0,-22" />
        <TextBlock x:Name="txtM12"  Text=" No data" Foreground="#FF040404" Margin="0,37,0,-37"/>
        <TextBlock x:Name="txtM13"  Text=" No data" Foreground="#FF040404" Margin="0,51,0,-51"/>
        <TextBlock x:Name="txtM21"  Text=" No data" Foreground="#FF040404" Margin="0,65,0,-65"/>
        <TextBlock x:Name="txtM22"  Text=" No data" Foreground="#FF040404" Margin="0,80,0,-80"/>
        <TextBlock x:Name="txtM23"  Text=" No data" Foreground="#FF040404" Margin="0,95,0,-95"/>
        <TextBlock x:Name="txtM31"  Text=" No data" Foreground="#FF040404" Margin="0,109,0,-109"/>
        <TextBlock x:Name="txtM32"  Text=" No data" Foreground="#FF040404" Margin="0,124,0,-124"/>
        <TextBlock x:Name="txtM33"  Text=" No data" Foreground="#FF040404" Margin="0,140,0,-140"/>

        <TextBlock x:Name="txtX"  TextWrapping="Wrap" Text="No data" Foreground="#FF040404" Margin="0,179,0,-179"/>
        <TextBlock x:Name="txtY"  TextWrapping="Wrap" Text="No data" Foreground="#FF040404" Margin="0,195,0,-195"/>
        <TextBlock x:Name="txtZ"  TextWrapping="Wrap" Text="No data" Foreground="#FF040404" Margin="0,213,0,-213"/>
        <TextBlock x:Name="txtW"  TextWrapping="Wrap" Text="No data" Foreground="#FF040404" Margin="0,230,0,-230"/>
        <TextBlock HorizontalAlignment="Left" Height="18" TextWrapping="Wrap" Text="Rotation Matrix" VerticalAlignment="Top" Width="200" Foreground="#FF010101"/>
        <TextBlock HorizontalAlignment="Left" Height="13" Margin="0,162,0,0" TextWrapping="Wrap" Text="Quaternion" VerticalAlignment="Top" Width="103" Foreground="#FF030303"/>
    </Grid>
</Page>

You'll need to replace the first part of the class name in the previous snippet with the namespace of your app. For example, if you created a project named OrientationSensorCPP, you'd replace x:Class="App1.MainPage" with x:Class="OrientationSensorCPP.MainPage". You should also replace xmlns:local="using:App1" with xmlns:local="using:OrientationSensorCPP".

6. Build, deploy and run the app

Press F5 or select Debug > Start Debugging to build, deploy, and run the app.

Once the app is running, you can change the accelerometer values by moving the device or using the emulator tools.

7. Stop the app

  1. Press ALT+Tab to return to Visual Studio.
  2. Press Shift+F5 or select Debug > Stop Debugging to stop the app.

Summary

The previous example demonstrates how little code you'll need to write in order to integrate sensor input in your app.

The app establishes a connection with the default sensor in the MainPage method.

sensor = OrientationSensor::GetDefault();

The app establishes the report interval within the MainPage method. This code retrieves the minimum interval supported by the device and compares it to a requested interval of 16 milliseconds (which approximates a 60-Hz refresh rate). If the minimum supported interval is greater than the requested interval, the code sets the value to the minimum. Otherwise, it sets the value to the requested interval.

uint32 minReportInterval = accelerometer->MinimumReportInterval;
uint32 reportInterval = minReportInterval > 16 ? minReportInterval : 16;
 accelerometer->ReportInterval = reportInterval;

The new sensor data is captured in the ReadingChanged method. Each time the sensor driver receives new data from the sensor, it passes the values to your app using this event handler. The app registers this event handler on the following line.

listenerToken = sensor->ReadingChanged::add(ref new 
     TypedEventHandler<OrientationSensor^, 
     OrientationSensorReadingChangedEventArgs^>
     (this, &MainPage::ReadingChanged));

These new values are written to the TextBlocks found in the project's XAML.

OrientationSensor class

OrientationSensor Sample

Roadmap for creating apps using C#, C++, or VB