Skip to content

Commit

Permalink
Automation: Integrate with Device Orientation API's Automation section
Browse files Browse the repository at this point in the history
Related to w3c/deviceorientation#124.

Both the Device Orientation API and the Orientation Sensor specifications
provide orientation data. Among other differences, the former provides the
data as a set of intrinsic Tait-Bryant Euler angles, whereas the latter uses
quaternions.

From an automation perspective, though, it makes sense to use the same
virtual sensor types for both APIs and take care of the conversions
internally.

The Device Orientation spec's Automation section defines steps for parsing a
user-provided reading as alpha, beta, gamma numbers that adhere to the
requirements laid out by the same spec. This commit augments the same "parse
orientation data reading" algorithm by also setting a "quaternion" entry in
the reading map that is set in two different ways:

1. If the user-provided readings contain a "quaternion" key, it is used
   as-is via the "parse quaternion reading" algorithm, and it is also
   converted to Euler angles so that the "alpha"/"beta"/"gamma" reading
   entries are also set.
2. Otherwise, the user-provider Euler angles are converted to a quaternion
   that is then used to set the "quaternion" entry in the readings map.

The conversions are not trivial, so some new abstract operations have
been added to aid in the process.

The Euler angles to quaternion algorithm has been copied from Chromium.

The quaternion to Euler angles algorithm has been copied from both Chromium
and Gecko, both of which need to do this to convert Android's orientation
data to a format suitable for their Device Orientation API implementations.
  • Loading branch information
rakuco committed Dec 5, 2023
1 parent 3c8cd70 commit d20b64b
Showing 1 changed file with 120 additions and 12 deletions.
132 changes: 120 additions & 12 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -506,26 +506,134 @@ To <dfn lt="converting a quaternion to rotation matrix">convert a quaternion to

</div>

<h3 id="helper-create-quaternion-from-euler-angles">Create a quaternion from Euler angles</h3>

<div algorithm>
<!--
This algorithm was copied from
https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/sensor/sensor_inspector_agent.cc;l=27;drc=047c7dc4ee1ce908d7fea38ca063fa2f80f92c77
-->

To <dfn>create a quaternion from Euler angles</dfn> given a number |alpha|, a number |beta| and a number |gamma|:

1. Let |alphaInRadians| be |alpha| converted from degrees to radians.
1. Let |betaInRadians| be |beta| converted from degrees to radians.
1. Let |gammaInRadians| be |gamma| converted from degrees to radians.
1. Let |cosZ| be the cosine of (0.5 * |alphaInRadians|).
1. Let |sinZ| be the sine of (0.5 * |alphaInRadians|).
1. Let |cosY| be the cosine of (0.5 * |gammaInRadians|).
1. Let |sinY| be the sine of (0.5 * |gammaInRadians|).
1. Let |cosX| be the cosine of (0.5 * |betaInRadians|).
1. Let |sinX| be the sine of (0.5 * |betaInRadians|).
1. Let |quaternionX| be (|sinX| * |cosY| * |cosZ| - |cosX| * |sinY| * |sinZ|).
1. Let |quaternionY| be (|cosX| * |sinY| * |cosZ| + |sinX| * |cosY| * |sinZ|).
1. Let |quaternionZ| be (|cosX| * |cosY| * |sinZ| + |sinX| * |sinY| * |cosZ|).
1. Let |quaternionW| be (|cosX| * |cosY| * |cosZ| - |sinX| * |sinY| * |sinZ|).
1. Return « |quaternionX|, |quaternionY|, |quaternionZ|, |quaternionW| ».

</div>

<h3 id="helper-derive-euler-angles-from-quaternion">Derive Euler angles from a quaternion</h3>

<div algorithm>
<!--
This algorithm was copied from
https://source.chromium.org/chromium/chromium/src/+/main:services/device/generic_sensor/orientation_util.cc;l=16;drc=098756533733ea50b2dcb1c40d9a9e18d49febbe
it also matches
https://searchfox.org/mozilla-central/rev/12ea2c521cdd071a6d25b0894f31f8f23b18b76a/dom/system/nsDeviceSensors.cpp#240
-->

To <dfn>derive Euler angles from a quaternion</dfn> given |originalRotationMatrix|, a 4x4 rotation matrix as returned by the [=convert a quaternion to rotation matrix=] algorithm:

1. Let **atan2(y, x)** be an [=implementation-defined=] function that returns the arc tangent of the quotient `y` / `x`, between `-π` to `+π` (inclusive), in radians, with the signs of `x` and `y` determining the quadrant of the result.
1. Let **asin(x)** be an [=implementation-defined=] function that returns the inverse sine of `x`. The result is expressed in radians and is in the interval `-π/2` to `π/2` (inclusive).
1. Let |r| be « |originalRotationMatrix|[0], |originalRotationMatrix|[1], |originalRotationMatrix|[2], |originalRotationMatrix|[4], |originalRotationMatrix|[5], |originalRotationMatrix|[6], |originalRotationMatrix|[8], |originalRotationMatrix|[9], |originalRotationMatrix|[10] ».

Note: This removes the last row and the last column from |originalRotationMatrix| for simplicity.

1. Let |alphaInRadians| be null.
1. Let |betaInRadians| be null.
1. Let |gammaInRadians| be null.
1. If |r|[8] is greater than 0:
1. Set |alphaInRadians| to **atan2**(-|r|[1], |r|[4]).
1. Set |betaInRadians| to **asin**(|r|[7]).
1. Set |gammaInRadians| to **atan2**(-|r|[6], |r|[8]).
1. Otherwise, if |r|[8] is less than 0:
1. Set |alphaInRadians| to **atan2**(|r|[1], -|r|[4]).
1. Set |betaInRadians| to -**asin**(|r|[7]).
1. If |betaInRadians| is greater than or equal to 0:
1. Set |betaInRadians| to |betaInRadians| plus -`π`.
1. Otherwise:
1. Set |betaInRadians| to |betaInRadians| plus `π`.
1. Set |gammaInRadians| to **atan2**(|r|[6], -|r|[8]).
1. Otherwise:
1. If |r|[6] is greater than 0:
1. Set |alphaInRadians| to **atan2**(-|r|[1], |r|[4]).
1. Set |betaInRadians| to **asin**(|r|[7]).
1. Set |gammaInRadians| to -`π/2`.
1. Otherwise, if |r|[6] is less than 0:
1. Set |alphaInRadians| to **atan2**(|r|[1], -|r|[4]).
1. Set |betaInRadians| to -**asin**(|r|[7]).
1. If |betaInRadians| is greater than or equal to 0:
1. Set |betaInRadians| to |betaInRadians| plus -`π`.
1. Otherwise:
1. Set |betaInRadians| to |betaInRadians| plus `π`.
1. Set |gammaInRadians| to -`π/2`.
1. Otherwise:
1. Set |alphaInRadians| to **atan2**(|r|[3], |r|[0]).
1. If |r|[7] is greater than 0:
1. Set |betaInRadians| to `π/2`.
1. Otherwise:
1. Set |betaInRadians| to -`π/2`.
1. Set |gammaInRadians| to 0.
1. If |alphaInRadians| is less than 0:
1. Set |alphaInRadians| to |alphaInRadians| plus (2 * `π`).
1. Let |alpha| be |alphaInRadians| converted from radians to degrees.
1. Let |beta| be |betaInRadians| converted from radians to degrees.
1. Let |gamma| be |gammaInRadians| converted from radians to degrees.
1. Return « |alpha|, |beta|, |gamma| ».

</div>

Automation {#automation}
==========

This section extends [[GENERIC-SENSOR#automation]] by providing [=Orientation Sensor=]-specific virtual sensor metadata.

<h3 id="absolute-orientation-sensor-automation">Absolute Orientation Sensor automation</h3>
Modifications to other specifications {#modifications-to-other-specifications}
-------------------------------------

This specification integrates with [[DEVICE-ORIENTATION#automation]] as follows.

<div algorithm="parse-orientation-data-reading-modifications">
The [=parse orientation data reading=] algorithm given a JSON {{Object}} |parameters| is modified as follows:

* Prepend the following steps to the beginning of the algorithm:
1. If ! [$HasOwnProperty$](|parameters|, "`quaternion`") is true:
1. Let |quaternion| be the result of invoking [=parse quaternion reading=] with |parameters|.
1. If |quaternion| is **undefined**, return **undefined**.
1. Let |rotationMatrix| be the result of invoking [=convert a quaternion to rotation matrix=] with |quaternion|[0], |quaternion|[1], |quaternion|[2], and |quaternion|[3].
1. Let |eulerAngles| be the result of invoking [=derive Euler angles from a quaternion=] with |rotationMatrix|.
1. Let |reading| be a new [=map=].
1. [=map/Set=] |reading|["`quaternion`"] to |quaternion|.
1. [=map/Set=] |reading|["`alpha`"] to |eulerAngles|[0].
1. [=map/Set=] |reading|["`beta`"] to |eulerAngles|[1].
1. [=map/Set=] |reading|["`gamma`"] to |eulerAngles|[2].
1. Return |reading|.
* Add the following steps after setting |reading|'s "`alpha`", "`beta`", and "`gamma`" keys and before returning |reading|:
1. [=map/Set=] |reading|["`quaternion`"] to the result of invoking [=create a quaternion from Euler angles=] with |reading|["`alpha`"], |reading|["`beta`"], and |reading|["`gamma`"].

</div>

Absolute Orientation Sensor automation {#absolute-orientation-sensor-automation}
--------------------------------------

The [=per-type virtual sensor metadata=] [=map=] must have the following [=map/entry=]:
: [=map/key=]
:: "<code>[=absolute-orientation virtual sensor type|absolute-orientation=]</code>"
: [=map/value=]
:: A [=virtual sensor metadata=] whose [=virtual sensor metadata/reading parsing algorithm=] is [=parse quaternion reading=].
The [=absolute-orientation virtual sensor type=] and its corresponding entry in the [=per-type virtual sensor metadata=] [=map=] are defined in [[DEVICE-ORIENTATION#automation]].

<h3 id="relative-orientation-sensor-automation">Relative Orientation Sensor automation</h3>
Relative Orientation Sensor automation {#relative-orientation-sensor-automation}
--------------------------------------

The [=per-type virtual sensor metadata=] [=map=] must have the following [=map/entry=]:
: [=map/key=]
:: "<code>[=relative-orientation virtual sensor type|relative-orientation=]</code>"
: [=map/value=]
:: A [=virtual sensor metadata=] whose [=virtual sensor metadata/reading parsing algorithm=] is [=parse quaternion reading=].
The [=relative-orientation virtual sensor type=] and its corresponding entry in the [=per-type virtual sensor metadata=] [=map=] are defined in [[DEVICE-ORIENTATION#automation]].

Acknowledgements {#acknowledgements}
================
Expand Down

0 comments on commit d20b64b

Please sign in to comment.