From 193e41f8789c5f1da4acc038569fca9f1a858327 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= <dach.bartlomiej@gmail.com>
Date: Thu, 26 Dec 2019 20:23:16 +0100
Subject: [PATCH] Add origin adjustment for hitobject lifetime

Visual inspection of taiko gameplay has shown that hitobjects appeared
on screen only when the origin of the hitobject came into the bounds
of the screen, instead of appearing when any visible part of the
hitobject came into the screen bounds.

This behaviour was due to lifetime calculation being based on the origin
of the hitobject and not taking into account the actual object
dimensions. Adjust the lifetime start of the hitobject by subtracting
the time needed to show the part of the hitobject that should already
be visible on screen when the origin comes into frame.
---
 .../Scrolling/ScrollingHitObjectContainer.cs  | 31 ++++++++++++++++++-
 1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 857929ff9e..04b4374fc4 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
 
         private void computeLifetimeStartRecursive(DrawableHitObject hitObject)
         {
-            hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value);
+            hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
 
             foreach (var obj in hitObject.NestedHitObjects)
                 computeLifetimeStartRecursive(obj);
@@ -108,6 +108,35 @@ namespace osu.Game.Rulesets.UI.Scrolling
 
         private readonly Dictionary<DrawableHitObject, Cached> hitObjectInitialStateCache = new Dictionary<DrawableHitObject, Cached>();
 
+        private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject)
+        {
+            float originAdjustment = 0.0f;
+
+            // calculate the dimension of the part of the hitobject that should already be visible
+            // when the hitobject origin first appears inside the scrolling container
+            switch (direction.Value)
+            {
+                case ScrollingDirection.Up:
+                    originAdjustment = hitObject.OriginPosition.Y;
+                    break;
+
+                case ScrollingDirection.Down:
+                    originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y;
+                    break;
+
+                case ScrollingDirection.Left:
+                    originAdjustment = hitObject.OriginPosition.X;
+                    break;
+
+                case ScrollingDirection.Right:
+                    originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X;
+                    break;
+            }
+
+            var adjustedStartTime = scrollingInfo.Algorithm.TimeAt(-originAdjustment, hitObject.HitObject.StartTime, timeRange.Value, scrollLength);
+            return scrollingInfo.Algorithm.GetDisplayStartTime(adjustedStartTime, timeRange.Value);
+        }
+
         // Cant use AddOnce() since the delegate is re-constructed every invocation
         private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() =>
         {