Fix display orientation handling

- Apply the orientation compensator before the filter. Otherwise, we
  won't tend to identity, but rather to a rotated frame, which is
  incorrect.
- Enable rate limiting when orientation changes, but only when a new
  world-to-screen pose arrives. This makes sure that rate limiting is
  applied at the point of discontinuity regardless of the order of
  operations.

Test: atest --host libheadtracking-test
Bug: 188502620
Change-Id: I58e1c3807f875ae2735e65122a3ffb8483bde2a3
diff --git a/media/libheadtracking/HeadTrackingProcessor-test.cpp b/media/libheadtracking/HeadTrackingProcessor-test.cpp
index 1739c6d..299192f 100644
--- a/media/libheadtracking/HeadTrackingProcessor-test.cpp
+++ b/media/libheadtracking/HeadTrackingProcessor-test.cpp
@@ -53,10 +53,10 @@
     processor->setWorldToHeadPose(0, Pose3f(), Twist3f());
     processor->setWorldToScreenPose(0, Pose3f());
 
+    processor->setDisplayOrientation(physicalToLogical);
     processor->setWorldToHeadPose(0, worldToHead, Twist3f());
     processor->setWorldToScreenPose(0, worldToScreen);
     processor->setScreenToStagePose(screenToStage);
-    processor->setDisplayOrientation(physicalToLogical);
     processor->calculate(0);
     ASSERT_EQ(processor->getActualMode(), HeadTrackingMode::SCREEN_RELATIVE);
     EXPECT_EQ(processor->getHeadToStagePose(), worldToHead.inverse() * worldToScreen *
diff --git a/media/libheadtracking/HeadTrackingProcessor.cpp b/media/libheadtracking/HeadTrackingProcessor.cpp
index 3f1a18d..47f7cf0 100644
--- a/media/libheadtracking/HeadTrackingProcessor.cpp
+++ b/media/libheadtracking/HeadTrackingProcessor.cpp
@@ -57,7 +57,14 @@
     }
 
     void setWorldToScreenPose(int64_t timestamp, const Pose3f& worldToScreen) override {
-        mScreenPoseDriftCompensator.setInput(timestamp, worldToScreen);
+        if (mPhysicalToLogicalAngle != mPendingPhysicalToLogicalAngle) {
+            // We're introducing an artificial discontinuity. Enable the rate limiter.
+            mRateLimiter.enable();
+            mPhysicalToLogicalAngle = mPendingPhysicalToLogicalAngle;
+        }
+
+        mScreenPoseDriftCompensator.setInput(
+                timestamp, worldToScreen * Pose3f(rotateY(-mPhysicalToLogicalAngle)));
         mWorldToScreenTimestamp = timestamp;
     }
 
@@ -66,10 +73,7 @@
     }
 
     void setDisplayOrientation(float physicalToLogicalAngle) override {
-        if (mPhysicalToLogicalAngle != physicalToLogicalAngle) {
-            mRateLimiter.enable();
-        }
-        mPhysicalToLogicalAngle = physicalToLogicalAngle;
+        mPendingPhysicalToLogicalAngle = physicalToLogicalAngle;
     }
 
     void calculate(int64_t timestamp) override {
@@ -80,8 +84,7 @@
         }
 
         if (mWorldToScreenTimestamp.has_value()) {
-            const Pose3f worldToLogicalScreen = mScreenPoseDriftCompensator.getOutput() *
-                                                Pose3f(rotateY(-mPhysicalToLogicalAngle));
+            const Pose3f worldToLogicalScreen = mScreenPoseDriftCompensator.getOutput();
             mScreenHeadFusion.setWorldToScreenPose(mWorldToScreenTimestamp.value(),
                                                    worldToLogicalScreen);
         }
@@ -129,6 +132,9 @@
   private:
     const Options mOptions;
     float mPhysicalToLogicalAngle = 0;
+    // We store the physical to logical angle as "pending" until the next world-to-screen sample it
+    // applies to arrives.
+    float mPendingPhysicalToLogicalAngle = 0;
     std::optional<int64_t> mWorldToHeadTimestamp;
     std::optional<int64_t> mWorldToScreenTimestamp;
     Pose3f mHeadToStagePose;
diff --git a/media/libheadtracking/PoseProcessingGraph.png b/media/libheadtracking/PoseProcessingGraph.png
index 8e6dfd2..0363068 100644
--- a/media/libheadtracking/PoseProcessingGraph.png
+++ b/media/libheadtracking/PoseProcessingGraph.png
Binary files differ