2019-01-24 16:43:03 +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.
2018-04-13 17:19:50 +08:00
2022-06-17 15:37:17 +08:00
#nullable disable
2018-04-13 17:19:50 +08:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2019-12-18 23:52:50 +08:00
using JetBrains.Annotations ;
2021-10-01 17:24:46 +08:00
using osu.Framework.Graphics ;
using osu.Game.Storyboards.Drawables ;
using osuTK ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Storyboards
{
2021-04-18 12:45:24 +08:00
public class StoryboardSprite : IStoryboardElementWithDuration
2018-04-13 17:19:50 +08:00
{
private readonly List < CommandLoop > loops = new List < CommandLoop > ( ) ;
private readonly List < CommandTrigger > triggers = new List < CommandTrigger > ( ) ;
2020-01-20 22:59:21 +08:00
public string Path { get ; }
2018-04-13 17:19:50 +08:00
public bool IsDrawable = > HasCommands ;
public Anchor Origin ;
public Vector2 InitialPosition ;
public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup ( ) ;
2021-03-09 14:55:05 +08:00
public double StartTime
{
get
{
// check for presence affecting commands as an initial pass.
double earliestStartTime = TimelineGroup . EarliestDisplayedTime ? ? double . MaxValue ;
foreach ( var l in loops )
{
2021-12-14 16:41:02 +08:00
if ( l . EarliestDisplayedTime is double loopEarliestDisplayTime )
earliestStartTime = Math . Min ( earliestStartTime , l . LoopStartTime + loopEarliestDisplayTime ) ;
2021-03-09 14:55:05 +08:00
}
if ( earliestStartTime < double . MaxValue )
return earliestStartTime ;
// if an alpha-affecting command was not found, use the earliest of any command.
earliestStartTime = TimelineGroup . StartTime ;
foreach ( var l in loops )
earliestStartTime = Math . Min ( earliestStartTime , l . StartTime ) ;
2018-04-13 17:19:50 +08:00
2021-03-09 14:55:05 +08:00
return earliestStartTime ;
}
}
public double EndTime
{
get
{
double latestEndTime = TimelineGroup . EndTime ;
foreach ( var l in loops )
latestEndTime = Math . Max ( latestEndTime , l . EndTime ) ;
return latestEndTime ;
}
}
2018-04-13 17:19:50 +08:00
public bool HasCommands = > TimelineGroup . HasCommands | | loops . Any ( l = > l . HasCommands ) ;
private delegate void DrawablePropertyInitializer < in T > ( Drawable drawable , T value ) ;
2019-02-28 12:31:40 +08:00
2018-04-13 17:19:50 +08:00
private delegate void DrawableTransformer < in T > ( Drawable drawable , T value , double duration , Easing easing ) ;
public StoryboardSprite ( string path , Anchor origin , Vector2 initialPosition )
{
Path = path ;
Origin = origin ;
InitialPosition = initialPosition ;
}
2021-10-01 17:24:46 +08:00
public CommandLoop AddLoop ( double startTime , int repeatCount )
2018-04-13 17:19:50 +08:00
{
2021-10-01 17:24:46 +08:00
var loop = new CommandLoop ( startTime , repeatCount ) ;
2018-04-13 17:19:50 +08:00
loops . Add ( loop ) ;
return loop ;
}
public CommandTrigger AddTrigger ( string triggerName , double startTime , double endTime , int groupNumber )
{
var trigger = new CommandTrigger ( triggerName , startTime , endTime , groupNumber ) ;
triggers . Add ( trigger ) ;
return trigger ;
}
public virtual Drawable CreateDrawable ( )
= > new DrawableStoryboardSprite ( this ) ;
public void ApplyTransforms ( Drawable drawable , IEnumerable < Tuple < CommandTimelineGroup , double > > triggeredGroups = null )
{
2019-12-18 23:52:50 +08:00
// For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
// To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list
// The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially.
List < IGeneratedCommand > generated = new List < IGeneratedCommand > ( ) ;
generateCommands ( generated , getCommands ( g = > g . X , triggeredGroups ) , ( d , value ) = > d . X = value , ( d , value , duration , easing ) = > d . MoveToX ( value , duration , easing ) ) ;
generateCommands ( generated , getCommands ( g = > g . Y , triggeredGroups ) , ( d , value ) = > d . Y = value , ( d , value , duration , easing ) = > d . MoveToY ( value , duration , easing ) ) ;
generateCommands ( generated , getCommands ( g = > g . Scale , triggeredGroups ) , ( d , value ) = > d . Scale = new Vector2 ( value ) , ( d , value , duration , easing ) = > d . ScaleTo ( value , duration , easing ) ) ;
generateCommands ( generated , getCommands ( g = > g . Rotation , triggeredGroups ) , ( d , value ) = > d . Rotation = value , ( d , value , duration , easing ) = > d . RotateTo ( value , duration , easing ) ) ;
generateCommands ( generated , getCommands ( g = > g . Colour , triggeredGroups ) , ( d , value ) = > d . Colour = value , ( d , value , duration , easing ) = > d . FadeColour ( value , duration , easing ) ) ;
generateCommands ( generated , getCommands ( g = > g . Alpha , triggeredGroups ) , ( d , value ) = > d . Alpha = value , ( d , value , duration , easing ) = > d . FadeTo ( value , duration , easing ) ) ;
generateCommands ( generated , getCommands ( g = > g . BlendingParameters , triggeredGroups ) , ( d , value ) = > d . Blending = value , ( d , value , duration , easing ) = > d . TransformBlendingMode ( value , duration ) ,
2019-12-18 16:21:38 +08:00
false ) ;
if ( drawable is IVectorScalable vectorScalable )
{
2019-12-18 23:52:50 +08:00
generateCommands ( generated , getCommands ( g = > g . VectorScale , triggeredGroups ) , ( d , value ) = > vectorScalable . VectorScale = value ,
2019-12-18 16:21:38 +08:00
( d , value , duration , easing ) = > vectorScalable . VectorScaleTo ( value , duration , easing ) ) ;
}
2018-04-13 17:19:50 +08:00
2019-02-28 13:35:00 +08:00
if ( drawable is IFlippable flippable )
2018-04-13 17:19:50 +08:00
{
2019-12-18 23:52:50 +08:00
generateCommands ( generated , getCommands ( g = > g . FlipH , triggeredGroups ) , ( d , value ) = > flippable . FlipH = value , ( d , value , duration , easing ) = > flippable . TransformFlipH ( value , duration ) ,
2019-12-18 16:21:38 +08:00
false ) ;
2019-12-18 23:52:50 +08:00
generateCommands ( generated , getCommands ( g = > g . FlipV , triggeredGroups ) , ( d , value ) = > flippable . FlipV = value , ( d , value , duration , easing ) = > flippable . TransformFlipV ( value , duration ) ,
2019-12-18 16:21:38 +08:00
false ) ;
2018-04-13 17:19:50 +08:00
}
2019-12-18 23:52:50 +08:00
foreach ( var command in generated . OrderBy ( g = > g . StartTime ) )
command . ApplyTo ( drawable ) ;
2018-04-13 17:19:50 +08:00
}
2019-12-18 23:52:50 +08:00
private void generateCommands < T > ( List < IGeneratedCommand > resultList , IEnumerable < CommandTimeline < T > . TypedCommand > commands ,
DrawablePropertyInitializer < T > initializeProperty , DrawableTransformer < T > transform , bool alwaysInitialize = true )
2018-04-13 17:19:50 +08:00
{
2019-12-18 23:52:50 +08:00
bool initialized = false ;
2019-04-01 11:16:05 +08:00
2019-12-18 23:52:50 +08:00
foreach ( var command in commands )
2018-04-13 17:19:50 +08:00
{
2019-12-18 23:52:50 +08:00
DrawablePropertyInitializer < T > initFunc = null ;
2018-04-13 17:19:50 +08:00
if ( ! initialized )
{
if ( alwaysInitialize | | command . StartTime = = command . EndTime )
2019-12-18 23:52:50 +08:00
initFunc = initializeProperty ;
2018-04-13 17:19:50 +08:00
initialized = true ;
}
2019-02-28 12:31:40 +08:00
2019-12-18 23:52:50 +08:00
resultList . Add ( new GeneratedCommand < T > ( command , initFunc , transform ) ) ;
2018-04-13 17:19:50 +08:00
}
}
private IEnumerable < CommandTimeline < T > . TypedCommand > getCommands < T > ( CommandTimelineSelector < T > timelineSelector , IEnumerable < Tuple < CommandTimelineGroup , double > > triggeredGroups )
{
var commands = TimelineGroup . GetCommands ( timelineSelector ) ;
foreach ( var loop in loops )
commands = commands . Concat ( loop . GetCommands ( timelineSelector ) ) ;
2019-11-11 20:05:36 +08:00
2018-04-13 17:19:50 +08:00
if ( triggeredGroups ! = null )
2019-11-11 19:53:22 +08:00
{
2018-04-13 17:19:50 +08:00
foreach ( var pair in triggeredGroups )
commands = commands . Concat ( pair . Item1 . GetCommands ( timelineSelector , pair . Item2 ) ) ;
2019-11-11 19:53:22 +08:00
}
2018-04-13 17:19:50 +08:00
return commands ;
}
public override string ToString ( )
= > $"{Path}, {Origin}, {InitialPosition}" ;
2019-12-18 23:52:50 +08:00
private interface IGeneratedCommand
{
double StartTime { get ; }
void ApplyTo ( Drawable drawable ) ;
}
private readonly struct GeneratedCommand < T > : IGeneratedCommand
{
public double StartTime = > command . StartTime ;
private readonly DrawablePropertyInitializer < T > initializeProperty ;
private readonly DrawableTransformer < T > transform ;
private readonly CommandTimeline < T > . TypedCommand command ;
public GeneratedCommand ( [ NotNull ] CommandTimeline < T > . TypedCommand command , [ CanBeNull ] DrawablePropertyInitializer < T > initializeProperty , [ NotNull ] DrawableTransformer < T > transform )
{
this . command = command ;
this . initializeProperty = initializeProperty ;
this . transform = transform ;
}
public void ApplyTo ( Drawable drawable )
{
initializeProperty ? . Invoke ( drawable , command . StartValue ) ;
using ( drawable . BeginAbsoluteSequence ( command . StartTime ) )
{
transform ( drawable , command . StartValue , 0 , Easing . None ) ;
transform ( drawable , command . EndValue , command . Duration , command . Easing ) ;
}
}
}
2018-04-13 17:19:50 +08:00
}
}