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 ;
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 ;
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>
2020-11-19 23:11:31 +08:00
public class FollowPointConnection : PoolableDrawable
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
2020-11-20 15:53:08 +08:00
public FollowPointLifetimeEntry Entry ;
2020-11-19 23:11:31 +08:00
public DrawablePool < FollowPoint > Pool ;
2019-11-01 14:39:23 +08:00
2020-11-20 14:31:04 +08:00
protected override void PrepareForUse ( )
{
base . PrepareForUse ( ) ;
Entry . Invalidated + = onEntryInvalidated ;
refreshPoints ( ) ;
}
2020-11-19 23:11:31 +08:00
protected override void FreeAfterUse ( )
2019-11-01 14:39:23 +08:00
{
2020-11-19 23:11:31 +08:00
base . FreeAfterUse ( ) ;
2020-11-20 14:31:04 +08:00
Entry . Invalidated - = onEntryInvalidated ;
// Return points to the pool.
2020-11-19 23:11:31 +08:00
ClearInternal ( false ) ;
2020-11-20 14:31:04 +08:00
Entry = null ;
2019-11-01 14:39:23 +08:00
}
2021-05-05 17:30:57 +08:00
private void onEntryInvalidated ( ) = > Scheduler . AddOnce ( refreshPoints ) ;
2020-11-20 14:31:04 +08:00
private void refreshPoints ( )
2020-02-23 03:05:37 +08:00
{
2020-11-20 14:31:04 +08:00
ClearInternal ( false ) ;
2019-11-01 18:21:39 +08:00
2020-11-19 23:11:31 +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
GetFadeTimes ( start , end , ( float ) d / distance , out var fadeInTime , out var 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 ) ;
fp . Delay ( fadeOutTime - fadeInTime ) . FadeOut ( end . TimeFadeIn ) ;
2019-11-05 22:03:05 +08:00
2020-11-19 23:11:31 +08:00
finalTransformEndTime = fadeOutTime + end . TimeFadeIn ;
2020-02-23 03:05:37 +08:00
}
2019-11-01 14:39:23 +08:00
}
2020-02-23 03:05:37 +08:00
2020-02-24 11:24:15 +08:00
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
2020-11-19 23:11:31 +08:00
Entry . LifetimeEnd = finalTransformEndTime ;
2019-11-01 14:39:23 +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
}
}