2019-11-01 14:39:23 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System ;
2022-09-16 14:12:05 +08:00
using System.Diagnostics ;
2019-11-01 14:39:23 +08:00
using osu.Framework.Graphics ;
2020-11-19 23:11:31 +08:00
using osu.Framework.Graphics.Pooling ;
2019-11-25 18:01:24 +08:00
using osu.Game.Rulesets.Objects ;
2021-06-04 15:31:50 +08:00
using osu.Game.Rulesets.Objects.Pooling ;
2019-11-01 14:39:23 +08:00
using osuTK ;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
{
2019-11-05 22:20:46 +08:00
/// <summary>
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
/// </summary>
2021-06-04 15:31:50 +08:00
public class FollowPointConnection : PoolableDrawableWithLifetime < FollowPointLifetimeEntry >
2019-11-01 14:39:23 +08:00
{
// Todo: These shouldn't be constants
2020-11-19 23:11:31 +08:00
public const int SPACING = 32 ;
public const double PREEMPT = 800 ;
2019-11-01 14:39:23 +08:00
2022-09-16 14:12:05 +08:00
public DrawablePool < FollowPoint > ? Pool { private get ; set ; }
2019-11-01 14:39:23 +08:00
2021-06-04 15:31:50 +08:00
protected override void OnApply ( FollowPointLifetimeEntry entry )
2020-11-20 14:31:04 +08:00
{
2021-06-04 15:31:50 +08:00
base . OnApply ( entry ) ;
2020-11-20 14:31:04 +08:00
2022-09-16 14:12:05 +08:00
entry . Invalidated + = scheduleRefresh ;
// Our clock may not be correct at this point if `LoadComplete` has not run yet.
// Without a schedule, animations referencing FollowPoint's clock (see `IAnimationTimeReference`) would be incorrect on first pool usage.
scheduleRefresh ( ) ;
2020-11-20 14:31:04 +08:00
}
2021-06-04 15:31:50 +08:00
protected override void OnFree ( FollowPointLifetimeEntry entry )
2019-11-01 14:39:23 +08:00
{
2021-06-04 15:31:50 +08:00
base . OnFree ( entry ) ;
2020-11-20 14:31:04 +08:00
2022-09-16 14:12:05 +08:00
entry . Invalidated - = scheduleRefresh ;
2020-11-20 14:31:04 +08:00
// Return points to the pool.
2020-11-19 23:11:31 +08:00
ClearInternal ( false ) ;
2019-11-01 14:39:23 +08:00
}
2022-09-16 14:12:05 +08:00
private void scheduleRefresh ( ) = > Scheduler . AddOnce ( ( ) = >
2020-02-23 03:05:37 +08:00
{
2022-09-16 14:12:05 +08:00
Debug . Assert ( Pool ! = null ) ;
2020-11-20 14:31:04 +08:00
ClearInternal ( false ) ;
2019-11-01 18:21:39 +08:00
2021-06-04 17:41:02 +08:00
var entry = Entry ;
2022-09-16 14:12:05 +08:00
2021-06-04 17:41:02 +08:00
if ( entry ? . End = = null ) return ;
2021-06-04 15:41:56 +08:00
OsuHitObject start = entry . Start ;
OsuHitObject end = entry . End ;
2019-11-01 14:39:23 +08:00
2020-11-19 23:11:31 +08:00
double startTime = start . GetEndTime ( ) ;
2020-02-23 03:05:37 +08:00
2020-11-19 23:11:31 +08:00
Vector2 startPosition = start . StackedEndPosition ;
Vector2 endPosition = end . StackedPosition ;
2019-11-01 14:39:23 +08:00
Vector2 distanceVector = endPosition - startPosition ;
int distance = ( int ) distanceVector . Length ;
float rotation = ( float ) ( Math . Atan2 ( distanceVector . Y , distanceVector . X ) * ( 180 / Math . PI ) ) ;
2020-02-23 03:05:37 +08:00
double finalTransformEndTime = startTime ;
2020-11-19 23:11:31 +08:00
for ( int d = ( int ) ( SPACING * 1.5 ) ; d < distance - SPACING ; d + = SPACING )
2019-11-01 14:39:23 +08:00
{
float fraction = ( float ) d / distance ;
Vector2 pointStartPosition = startPosition + ( fraction - 0.1f ) * distanceVector ;
Vector2 pointEndPosition = startPosition + fraction * distanceVector ;
2020-11-20 15:43:07 +08:00
2021-10-27 12:04:41 +08:00
GetFadeTimes ( start , end , ( float ) d / distance , out double fadeInTime , out double fadeOutTime ) ;
2019-11-01 14:39:23 +08:00
FollowPoint fp ;
2020-11-19 23:11:31 +08:00
AddInternal ( fp = Pool . Get ( ) ) ;
2020-11-05 14:01:45 +08:00
2020-11-19 23:11:31 +08:00
fp . ClearTransforms ( ) ;
2020-03-18 23:34:24 +08:00
fp . Position = pointStartPosition ;
fp . Rotation = rotation ;
fp . Alpha = 0 ;
2020-11-19 23:11:31 +08:00
fp . Scale = new Vector2 ( 1.5f * end . Scale ) ;
2020-02-24 11:24:15 +08:00
2020-11-22 03:06:30 +08:00
fp . AnimationStartTime . Value = fadeInTime ;
2020-03-27 17:03:02 +08:00
2019-11-01 14:39:23 +08:00
using ( fp . BeginAbsoluteSequence ( fadeInTime ) )
{
2020-11-19 23:11:31 +08:00
fp . FadeIn ( end . TimeFadeIn ) ;
fp . ScaleTo ( end . Scale , end . TimeFadeIn , Easing . Out ) ;
fp . MoveTo ( pointEndPosition , end . TimeFadeIn , Easing . Out ) ;
2021-06-04 15:53:27 +08:00
fp . Delay ( fadeOutTime - fadeInTime ) . FadeOut ( end . TimeFadeIn ) . Expire ( ) ;
2019-11-05 22:03:05 +08:00
2021-06-04 15:53:27 +08:00
finalTransformEndTime = fp . LifetimeEnd ;
2020-02-23 03:05:37 +08:00
}
2019-11-01 14:39:23 +08:00
}
2020-02-23 03:05:37 +08:00
2021-06-04 15:41:56 +08:00
entry . LifetimeEnd = finalTransformEndTime ;
2022-09-16 14:12:05 +08:00
} ) ;
2020-11-20 15:43:07 +08:00
/// <summary>
/// Computes the fade time of follow point positioned between two hitobjects.
/// </summary>
/// <param name="start">The first <see cref="OsuHitObject"/>, where follow points should originate from.</param>
/// <param name="end">The second <see cref="OsuHitObject"/>, which follow points should target.</param>
/// <param name="fraction">The fractional distance along <paramref name="start"/> and <paramref name="end"/> at which the follow point is to be located.</param>
/// <param name="fadeInTime">The fade-in time of the follow point/</param>
/// <param name="fadeOutTime">The fade-out time of the follow point.</param>
public static void GetFadeTimes ( OsuHitObject start , OsuHitObject end , float fraction , out double fadeInTime , out double fadeOutTime )
{
double startTime = start . GetEndTime ( ) ;
double duration = end . StartTime - startTime ;
2021-02-10 21:06:19 +08:00
// Preempt time can go below 800ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
// This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear preempt function (see: OsuHitObject).
// Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good.
double preempt = PREEMPT * Math . Min ( 1 , start . TimePreempt / OsuHitObject . PREEMPT_MIN ) ;
2020-12-09 23:26:35 +08:00
2020-11-20 15:43:07 +08:00
fadeOutTime = startTime + fraction * duration ;
2020-12-09 23:26:35 +08:00
fadeInTime = fadeOutTime - preempt ;
2020-11-20 15:43:07 +08:00
}
2019-11-01 14:39:23 +08:00
}
}