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
using osu.Framework.Graphics ;
2020-01-09 12:43:44 +08:00
using osu.Framework.Utils ;
2018-11-20 15:51:59 +08:00
using osuTK ;
using osuTK.Graphics ;
2018-04-13 17:19:50 +08:00
using System ;
using osu.Framework.Graphics.Shaders ;
using osu.Framework.Graphics.Textures ;
using osu.Framework.Graphics.Colour ;
using osu.Framework.Graphics.Primitives ;
using osu.Framework.Allocation ;
using System.Collections.Generic ;
using osu.Framework.Graphics.Batches ;
2021-09-15 17:32:52 +08:00
using osu.Framework.Graphics.OpenGL.Buffers ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics.OpenGL.Vertices ;
using osu.Framework.Lists ;
namespace osu.Game.Graphics.Backgrounds
{
public class Triangles : Drawable
{
private const float triangle_size = 100 ;
private const float base_velocity = 50 ;
/// <summary>
/// How many screen-space pixels are smoothed over.
/// Same behavior as Sprite's EdgeSmoothness.
/// </summary>
private const float edge_smoothness = 1 ;
2020-01-06 07:32:13 +08:00
private Color4 colourLight = Color4 . White ;
public Color4 ColourLight
{
get = > colourLight ;
set
{
2020-01-06 08:53:29 +08:00
if ( colourLight = = value ) return ;
2020-01-06 07:32:13 +08:00
colourLight = value ;
updateColours ( ) ;
}
}
private Color4 colourDark = Color4 . Black ;
public Color4 ColourDark
{
get = > colourDark ;
set
{
2020-01-06 08:53:29 +08:00
if ( colourDark = = value ) return ;
2020-01-06 07:32:13 +08:00
colourDark = value ;
updateColours ( ) ;
}
}
2018-04-13 17:19:50 +08:00
/// <summary>
/// Whether we should create new triangles as others expire.
/// </summary>
protected virtual bool CreateNewTriangles = > true ;
/// <summary>
/// The amount of triangles we want compared to the default distribution.
/// </summary>
protected virtual float SpawnRatio = > 1 ;
private float triangleScale = 1 ;
/// <summary>
/// Whether we should drop-off alpha values of triangles more quickly to improve
/// the visual appearance of fading. This defaults to on as it is generally more
/// aesthetically pleasing, but should be turned off in buffered containers.
/// </summary>
public bool HideAlphaDiscrepancies = true ;
/// <summary>
/// The relative velocity of the triangles. Default is 1.
/// </summary>
public float Velocity = 1 ;
private readonly SortedList < TriangleParticle > parts = new SortedList < TriangleParticle > ( Comparer < TriangleParticle > . Default ) ;
2020-11-17 12:06:30 +08:00
private Random stableRandom ;
2019-03-07 17:30:18 +08:00
private IShader shader ;
2018-04-13 17:19:50 +08:00
private readonly Texture texture ;
2020-10-02 00:48:41 +08:00
/// <summary>
/// Construct a new triangle visualisation.
/// </summary>
/// <param name="seed">An optional seed to stabilise random positions / attributes. Note that this does not guarantee stable playback when seeking in time.</param>
public Triangles ( int? seed = null )
2018-04-13 17:19:50 +08:00
{
2020-10-02 00:48:41 +08:00
if ( seed ! = null )
stableRandom = new Random ( seed . Value ) ;
2018-04-13 17:19:50 +08:00
texture = Texture . WhitePixel ;
}
[BackgroundDependencyLoader]
private void load ( ShaderManager shaders )
{
2019-03-07 17:30:18 +08:00
shader = shaders . Load ( VertexShaderDescriptor . TEXTURE_2 , FragmentShaderDescriptor . TEXTURE_ROUNDED ) ;
2018-04-13 17:19:50 +08:00
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
addTriangles ( true ) ;
}
public float TriangleScale
{
2019-02-28 12:58:19 +08:00
get = > triangleScale ;
2018-04-13 17:19:50 +08:00
set
{
float change = value / triangleScale ;
triangleScale = value ;
for ( int i = 0 ; i < parts . Count ; i + + )
{
TriangleParticle newParticle = parts [ i ] ;
newParticle . Scale * = change ;
parts [ i ] = newParticle ;
}
}
}
protected override void Update ( )
{
base . Update ( ) ;
2020-02-25 10:30:33 +08:00
Invalidate ( Invalidation . DrawNode ) ;
2018-04-13 17:19:50 +08:00
if ( CreateNewTriangles )
addTriangles ( false ) ;
2019-02-28 12:31:40 +08:00
float adjustedAlpha = HideAlphaDiscrepancies
2018-04-13 17:19:50 +08:00
// Cubically scale alpha to make it drop off more sharply.
2019-11-25 07:45:42 +08:00
? MathF . Pow ( DrawColourInfo . Colour . AverageColour . Linear . A , 3 )
2019-02-28 12:31:40 +08:00
: 1 ;
2018-04-13 17:19:50 +08:00
float elapsedSeconds = ( float ) Time . Elapsed / 1000 ;
// Since position is relative, the velocity needs to scale inversely with DrawHeight.
// Since we will later multiply by the scale of individual triangles we normalize by
// dividing by triangleScale.
float movedDistance = - elapsedSeconds * Velocity * base_velocity / ( DrawHeight * triangleScale ) ;
for ( int i = 0 ; i < parts . Count ; i + + )
{
TriangleParticle newParticle = parts [ i ] ;
// Scale moved distance by the size of the triangle. Smaller triangles should move more slowly.
2021-08-31 13:08:21 +08:00
newParticle . Position . Y + = Math . Max ( 0.5f , parts [ i ] . Scale ) * movedDistance ;
2018-04-13 17:19:50 +08:00
newParticle . Colour . A = adjustedAlpha ;
parts [ i ] = newParticle ;
float bottomPos = parts [ i ] . Position . Y + triangle_size * parts [ i ] . Scale * 0.866f / DrawHeight ;
if ( bottomPos < 0 )
parts . RemoveAt ( i ) ;
}
}
2020-11-17 12:06:30 +08:00
/// <summary>
/// Clears and re-initialises triangles according to a given seed.
/// </summary>
/// <param name="seed">An optional seed to stabilise random positions / attributes. Note that this does not guarantee stable playback when seeking in time.</param>
public void Reset ( int? seed = null )
{
if ( seed ! = null )
stableRandom = new Random ( seed . Value ) ;
parts . Clear ( ) ;
addTriangles ( true ) ;
}
protected int AimCount { get ; private set ; }
2019-07-18 23:53:00 +08:00
2018-04-13 17:19:50 +08:00
private void addTriangles ( bool randomY )
{
2021-09-15 17:32:52 +08:00
// limited by the maximum size of QuadVertexBuffer for safety.
const int max_triangles = QuadVertexBuffer < TexturedVertex2D > . MAX_QUADS ;
AimCount = ( int ) Math . Min ( max_triangles , ( DrawWidth * DrawHeight * 0.002f / ( triangleScale * triangleScale ) * SpawnRatio ) ) ;
2018-04-13 17:19:50 +08:00
2019-07-18 23:53:00 +08:00
for ( int i = 0 ; i < AimCount - parts . Count ; i + + )
2018-04-13 17:19:50 +08:00
parts . Add ( createTriangle ( randomY ) ) ;
}
private TriangleParticle createTriangle ( bool randomY )
{
TriangleParticle particle = CreateTriangle ( ) ;
2020-10-02 00:48:41 +08:00
particle . Position = new Vector2 ( nextRandom ( ) , randomY ? nextRandom ( ) : 1 ) ;
particle . ColourShade = nextRandom ( ) ;
2020-01-06 19:51:38 +08:00
particle . Colour = CreateTriangleShade ( particle . ColourShade ) ;
2018-04-13 17:19:50 +08:00
return particle ;
}
/// <summary>
/// Creates a triangle particle with a random scale.
/// </summary>
/// <returns>The triangle particle.</returns>
protected virtual TriangleParticle CreateTriangle ( )
{
const float std_dev = 0.16f ;
const float mean = 0.5f ;
2020-10-02 00:48:41 +08:00
float u1 = 1 - nextRandom ( ) ; //uniform(0,1] random floats
float u2 = 1 - nextRandom ( ) ;
2020-05-05 09:31:11 +08:00
float randStdNormal = ( float ) ( Math . Sqrt ( - 2.0 * Math . Log ( u1 ) ) * Math . Sin ( 2.0 * Math . PI * u2 ) ) ; // random normal(0,1)
var scale = Math . Max ( triangleScale * ( mean + std_dev * randStdNormal ) , 0.1f ) ; // random normal(mean,stdDev^2)
2018-04-13 17:19:50 +08:00
return new TriangleParticle { Scale = scale } ;
}
2020-01-06 19:51:38 +08:00
/// <summary>
/// Creates a shade of colour for the triangles.
/// </summary>
/// <returns>The colour.</returns>
protected virtual Color4 CreateTriangleShade ( float shade ) = > Interpolation . ValueAt ( shade , colourDark , colourLight , 0 , 1 ) ;
2020-01-06 07:32:13 +08:00
private void updateColours ( )
{
for ( int i = 0 ; i < parts . Count ; i + + )
{
TriangleParticle newParticle = parts [ i ] ;
2020-01-06 19:51:38 +08:00
newParticle . Colour = CreateTriangleShade ( newParticle . ColourShade ) ;
2020-01-06 07:32:13 +08:00
parts [ i ] = newParticle ;
}
}
2018-04-13 17:19:50 +08:00
2020-11-17 12:06:30 +08:00
private float nextRandom ( ) = > ( float ) ( stableRandom ? . NextDouble ( ) ? ? RNG . NextSingle ( ) ) ;
2019-04-02 10:56:22 +08:00
protected override DrawNode CreateDrawNode ( ) = > new TrianglesDrawNode ( this ) ;
2018-04-13 17:19:50 +08:00
2019-04-02 10:56:22 +08:00
private class TrianglesDrawNode : DrawNode
2018-04-13 17:19:50 +08:00
{
2019-04-02 10:56:22 +08:00
protected new Triangles Source = > ( Triangles ) base . Source ;
2018-04-13 17:19:50 +08:00
2019-04-02 10:56:22 +08:00
private IShader shader ;
private Texture texture ;
2018-04-13 17:19:50 +08:00
2019-04-02 10:56:22 +08:00
private readonly List < TriangleParticle > parts = new List < TriangleParticle > ( ) ;
private Vector2 size ;
2018-04-13 17:19:50 +08:00
2019-08-09 18:12:29 +08:00
private QuadBatch < TexturedVertex2D > vertexBatch ;
2018-04-13 17:19:50 +08:00
2019-04-02 10:56:22 +08:00
public TrianglesDrawNode ( Triangles source )
: base ( source )
{
}
2018-04-13 17:19:50 +08:00
2019-04-02 10:56:22 +08:00
public override void ApplyState ( )
{
base . ApplyState ( ) ;
2018-04-13 17:19:50 +08:00
2019-04-02 10:56:22 +08:00
shader = Source . shader ;
texture = Source . texture ;
size = Source . DrawSize ;
parts . Clear ( ) ;
parts . AddRange ( Source . parts ) ;
}
2019-03-01 12:01:52 +08:00
2018-04-13 17:19:50 +08:00
public override void Draw ( Action < TexturedVertex2D > vertexAction )
{
base . Draw ( vertexAction ) ;
2019-07-22 17:29:04 +08:00
if ( Source . AimCount > 0 & & ( vertexBatch = = null | | vertexBatch . Size ! = Source . AimCount ) )
2019-07-19 13:24:11 +08:00
{
vertexBatch ? . Dispose ( ) ;
2019-08-09 18:12:29 +08:00
vertexBatch = new QuadBatch < TexturedVertex2D > ( Source . AimCount , 1 ) ;
2019-07-19 13:24:11 +08:00
}
2019-04-02 10:56:22 +08:00
shader . Bind ( ) ;
2018-04-13 17:19:50 +08:00
Vector2 localInflationAmount = edge_smoothness * DrawInfo . MatrixInverse . ExtractScale ( ) . Xy ;
2019-04-02 10:56:22 +08:00
foreach ( TriangleParticle particle in parts )
2018-04-13 17:19:50 +08:00
{
var offset = triangle_size * new Vector2 ( particle . Scale * 0.5f , particle . Scale * 0.866f ) ;
var triangle = new Triangle (
2019-04-02 10:56:22 +08:00
Vector2Extensions . Transform ( particle . Position * size , DrawInfo . Matrix ) ,
Vector2Extensions . Transform ( particle . Position * size + offset , DrawInfo . Matrix ) ,
Vector2Extensions . Transform ( particle . Position * size + new Vector2 ( - offset . X , offset . Y ) , DrawInfo . Matrix )
2018-04-13 17:19:50 +08:00
) ;
2018-09-06 17:02:04 +08:00
ColourInfo colourInfo = DrawColourInfo . Colour ;
2018-04-13 17:19:50 +08:00
colourInfo . ApplyChild ( particle . Colour ) ;
2019-06-06 15:33:14 +08:00
DrawTriangle (
texture ,
2018-04-13 17:19:50 +08:00
triangle ,
colourInfo ,
null ,
2019-03-01 12:01:52 +08:00
vertexBatch . AddAction ,
2019-04-02 10:56:22 +08:00
Vector2 . Divide ( localInflationAmount , new Vector2 ( 2 * offset . X , offset . Y ) ) ) ;
2018-04-13 17:19:50 +08:00
}
2019-04-02 10:56:22 +08:00
shader . Unbind ( ) ;
2018-04-13 17:19:50 +08:00
}
2019-03-01 12:01:52 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2019-07-19 00:14:55 +08:00
vertexBatch ? . Dispose ( ) ;
2019-03-01 12:01:52 +08:00
}
2018-04-13 17:19:50 +08:00
}
2020-01-06 19:51:38 +08:00
protected struct TriangleParticle : IComparable < TriangleParticle >
2018-04-13 17:19:50 +08:00
{
/// <summary>
/// The position of the top vertex of the triangle.
/// </summary>
public Vector2 Position ;
2020-01-06 07:32:13 +08:00
/// <summary>
/// The colour shade of the triangle.
/// This is needed for colour recalculation of visible triangles when <see cref="ColourDark"/> or <see cref="ColourLight"/> is changed.
/// </summary>
2020-01-06 19:51:38 +08:00
public float ColourShade ;
2020-01-06 07:32:13 +08:00
2018-04-13 17:19:50 +08:00
/// <summary>
/// The colour of the triangle.
/// </summary>
public Color4 Colour ;
/// <summary>
/// The scale of the triangle.
/// </summary>
public float Scale ;
/// <summary>
/// Compares two <see cref="TriangleParticle"/>s. This is a reverse comparer because when the
/// triangles are added to the particles list, they should be drawn from largest to smallest
/// such that the smaller triangles appear on top.
/// </summary>
/// <param name="other"></param>
public int CompareTo ( TriangleParticle other ) = > other . Scale . CompareTo ( Scale ) ;
}
}
}