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
2016-12-01 17:53:13 +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 ;
2017-02-04 21:09:44 +08:00
using System ;
2017-05-25 20:33:04 +08:00
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 ;
2022-07-29 21:33:34 +08:00
using osu.Framework.Graphics.Rendering ;
2022-08-05 19:36:28 +08:00
using osu.Framework.Graphics.Rendering.Vertices ;
2017-05-25 21:03:36 +08:00
using osu.Framework.Lists ;
2022-11-15 16:06:28 +08:00
using osu.Framework.Bindables ;
2018-04-13 17:19:50 +08:00
2016-12-01 17:53:13 +08:00
namespace osu.Game.Graphics.Backgrounds
{
2017-05-25 20:33:04 +08:00
public class Triangles : Drawable
2016-12-01 17:53:13 +08:00
{
2017-05-25 20:33:04 +08:00
private const float triangle_size = 100 ;
2017-05-30 01:08:06 +08:00
private const float base_velocity = 50 ;
2018-04-13 17:19:50 +08:00
2022-11-15 21:20:08 +08:00
/// <summary>
/// sqrt(3) / 2
/// </summary>
private const float equilateral_triangle_ratio = 0.866f ;
2017-05-30 00:20:51 +08:00
/// <summary>
/// How many screen-space pixels are smoothed over.
/// Same behavior as Sprite's EdgeSmoothness.
/// </summary>
private const float edge_smoothness = 1 ;
2018-04-13 17:19:50 +08:00
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
2017-02-15 19:39:10 +08:00
/// <summary>
/// Whether we should create new triangles as others expire.
/// </summary>
protected virtual bool CreateNewTriangles = > true ;
2018-04-13 17:19:50 +08:00
2017-02-15 19:39:10 +08:00
/// <summary>
/// The amount of triangles we want compared to the default distribution.
/// </summary>
protected virtual float SpawnRatio = > 1 ;
2018-04-13 17:19:50 +08:00
2022-11-15 16:06:28 +08:00
private readonly BindableFloat triangleScale = new BindableFloat ( 1f ) ;
public float TriangleScale
{
get = > triangleScale . Value ;
set = > triangleScale . Value = value ;
}
2018-04-13 17:19:50 +08:00
2017-02-28 10:28:12 +08:00
/// <summary>
2017-03-01 02:01:02 +08:00
/// 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
2017-06-21 10:35:19 +08:00
/// aesthetically pleasing, but should be turned off in buffered containers.
2017-02-28 10:28:12 +08:00
/// </summary>
2017-03-01 02:01:02 +08:00
public bool HideAlphaDiscrepancies = true ;
2018-04-13 17:19:50 +08:00
2017-05-24 12:05:11 +08:00
/// <summary>
/// The relative velocity of the triangles. Default is 1.
/// </summary>
public float Velocity = 1 ;
2018-04-13 17:19:50 +08:00
2017-05-25 21:03:36 +08:00
private readonly SortedList < TriangleParticle > parts = new SortedList < TriangleParticle > ( Comparer < TriangleParticle > . Default ) ;
2018-04-13 17:19:50 +08:00
2020-11-17 12:06:30 +08:00
private Random stableRandom ;
2019-03-07 17:30:18 +08:00
private IShader shader ;
2022-08-02 18:50:57 +08:00
private Texture texture ;
2018-04-13 17:19:50 +08:00
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 )
2016-12-01 19:21:14 +08:00
{
2020-10-02 00:48:41 +08:00
if ( seed ! = null )
stableRandom = new Random ( seed . Value ) ;
2017-05-25 20:33:04 +08:00
}
2018-04-13 17:19:50 +08:00
2017-05-25 20:33:04 +08:00
[BackgroundDependencyLoader]
2022-08-02 18:50:57 +08:00
private void load ( IRenderer renderer , ShaderManager shaders )
2017-05-25 20:33:04 +08:00
{
2022-08-02 18:50:57 +08:00
texture = renderer . WhitePixel ;
2022-11-12 16:05:29 +08:00
shader = shaders . Load ( VertexShaderDescriptor . TEXTURE_2 , FragmentShaderDescriptor . TEXTURE ) ;
2016-12-01 19:21:14 +08:00
}
2018-04-13 17:19:50 +08:00
2017-02-04 21:09:44 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2022-11-15 16:06:28 +08:00
triangleScale . BindValueChanged ( _ = > Reset ( ) , true ) ;
2017-05-25 20:33:04 +08:00
}
2018-04-13 17:19:50 +08:00
2016-12-01 17:53:13 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2018-04-13 17:19:50 +08:00
2020-02-25 10:30:33 +08:00
Invalidate ( Invalidation . DrawNode ) ;
2018-04-13 17:19:50 +08:00
2017-05-30 00:30:49 +08:00
if ( CreateNewTriangles )
addTriangles ( false ) ;
2018-04-13 17:19:50 +08:00
2019-02-28 12:31:40 +08:00
float adjustedAlpha = HideAlphaDiscrepancies
2017-05-30 00:30:49 +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
2017-05-30 01:08:06 +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.
2022-11-15 16:06:28 +08:00
float movedDistance = - elapsedSeconds * Velocity * base_velocity / ( DrawHeight * TriangleScale ) ;
2018-04-13 17:19:50 +08:00
2017-05-25 20:33:04 +08:00
for ( int i = 0 ; i < parts . Count ; i + + )
2016-12-01 17:53:13 +08:00
{
2017-05-25 20:33:04 +08:00
TriangleParticle newParticle = parts [ i ] ;
2018-04-13 17:19:50 +08:00
2017-05-30 00:30:49 +08:00
// 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 ;
2017-05-25 20:33:04 +08:00
newParticle . Colour . A = adjustedAlpha ;
2018-04-13 17:19:50 +08:00
2017-05-25 20:33:04 +08:00
parts [ i ] = newParticle ;
2018-04-13 17:19:50 +08:00
2022-11-15 21:20:08 +08:00
float bottomPos = parts [ i ] . Position . Y + triangle_size * parts [ i ] . Scale * equilateral_triangle_ratio / DrawHeight ;
2017-05-25 20:33:04 +08:00
if ( bottomPos < 0 )
2017-05-25 21:03:36 +08:00
parts . RemoveAt ( i ) ;
2016-12-01 17:53:13 +08:00
}
2017-05-25 20:33:04 +08:00
}
2018-04-13 17:19:50 +08:00
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
2017-05-25 20:33:04 +08:00
private void addTriangles ( bool randomY )
{
2022-07-29 22:32:06 +08:00
// Limited by the maximum size of QuadVertexBuffer for safety.
const int max_triangles = ushort . MaxValue / ( IRenderer . VERTICES_PER_QUAD + 2 ) ;
2021-09-15 17:32:52 +08:00
2022-11-15 16:06:28 +08:00
AimCount = ( int ) Math . Min ( max_triangles , DrawWidth * DrawHeight * 0.002f / ( TriangleScale * TriangleScale ) * SpawnRatio ) ;
2018-04-13 17:19:50 +08:00
2022-11-15 16:32:05 +08:00
int currentCount = parts . Count ;
for ( int i = 0 ; i < AimCount - currentCount ; i + + )
2017-05-25 20:33:04 +08:00
parts . Add ( createTriangle ( randomY ) ) ;
2016-12-01 17:53:13 +08:00
}
2018-04-13 17:19:50 +08:00
2017-05-25 20:33:04 +08:00
private TriangleParticle createTriangle ( bool randomY )
{
2017-05-25 21:09:02 +08:00
TriangleParticle particle = CreateTriangle ( ) ;
2018-04-13 17:19:50 +08:00
2022-11-15 16:49:53 +08:00
particle . Position = getRandomPosition ( randomY , particle . Scale ) ;
2020-10-02 00:48:41 +08:00
particle . ColourShade = nextRandom ( ) ;
2020-01-06 19:51:38 +08:00
particle . Colour = CreateTriangleShade ( particle . ColourShade ) ;
2018-04-13 17:19:50 +08:00
2017-05-25 20:33:04 +08:00
return particle ;
}
2018-04-13 17:19:50 +08:00
2022-11-15 16:49:53 +08:00
private Vector2 getRandomPosition ( bool randomY , float scale )
{
float y = 1 ;
if ( randomY )
{
// since triangles are drawn from the top - allow them to be positioned a bit above the screen
2022-11-15 21:20:08 +08:00
float maxOffset = triangle_size * scale * equilateral_triangle_ratio / DrawHeight ;
2022-11-15 16:49:53 +08:00
y = Interpolation . ValueAt ( nextRandom ( ) , - maxOffset , 1f , 0f , 1f ) ;
}
return new Vector2 ( nextRandom ( ) , y ) ;
}
2017-05-25 20:33:04 +08:00
/// <summary>
/// Creates a triangle particle with a random scale.
/// </summary>
/// <returns>The triangle particle.</returns>
protected virtual TriangleParticle CreateTriangle ( )
2016-12-01 17:53:13 +08:00
{
2017-03-23 12:52:38 +08:00
const float std_dev = 0.16f ;
const float mean = 0.5f ;
2018-04-13 17:19:50 +08:00
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)
2022-11-15 16:06:28 +08:00
float scale = Math . Max ( TriangleScale * ( mean + std_dev * randStdNormal ) , 0.1f ) ; // random normal(mean,stdDev^2)
2018-04-13 17:19:50 +08:00
2017-05-25 20:33:04 +08:00
return new TriangleParticle { Scale = scale } ;
}
2018-04-13 17:19:50 +08:00
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
2017-05-25 20:33:04 +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
2022-07-29 22:32:06 +08:00
private IVertexBatch < 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
2022-07-29 21:33:34 +08:00
public override void Draw ( IRenderer renderer )
2017-04-05 19:00:22 +08:00
{
2022-07-29 21:33:34 +08:00
base . Draw ( renderer ) ;
2018-04-13 17:19:50 +08:00
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 ( ) ;
2022-07-29 22:32:06 +08:00
vertexBatch = renderer . CreateQuadBatch < 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
2017-05-30 00:20:51 +08:00
Vector2 localInflationAmount = edge_smoothness * DrawInfo . MatrixInverse . ExtractScale ( ) . Xy ;
2018-04-13 17:19:50 +08:00
2019-04-02 10:56:22 +08:00
foreach ( TriangleParticle particle in parts )
2017-05-25 20:33:04 +08:00
{
2022-11-15 21:20:08 +08:00
var offset = triangle_size * new Vector2 ( particle . Scale * 0.5f , particle . Scale * equilateral_triangle_ratio ) ;
2018-04-13 17:19:50 +08:00
2017-05-25 20:33:04 +08:00
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 )
2017-05-25 20:33:04 +08:00
) ;
2018-04-13 17:19:50 +08:00
2018-09-06 17:02:04 +08:00
ColourInfo colourInfo = DrawColourInfo . Colour ;
2017-05-25 20:33:04 +08:00
colourInfo . ApplyChild ( particle . Colour ) ;
2018-04-13 17:19:50 +08:00
2022-08-02 18:50:57 +08:00
renderer . DrawTriangle (
2019-06-06 15:33:14 +08:00
texture ,
2017-05-30 00:20:51 +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 ) ) ) ;
2017-05-25 20:33:04 +08:00
}
2018-04-13 17:19:50 +08:00
2019-04-02 10:56:22 +08:00
shader . Unbind ( ) ;
2017-04-05 19:00:22 +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
}
2016-12-01 17:53:13 +08:00
}
2018-04-13 17:19:50 +08:00
2020-01-06 19:51:38 +08:00
protected struct TriangleParticle : IComparable < TriangleParticle >
2017-05-25 20:33:04 +08:00
{
2017-05-25 21:09:02 +08:00
/// <summary>
/// The position of the top vertex of the triangle.
/// </summary>
2017-05-25 20:33:04 +08:00
public Vector2 Position ;
2018-04-13 17:19:50 +08:00
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
2017-05-25 21:09:02 +08:00
/// <summary>
/// The colour of the triangle.
/// </summary>
2017-05-25 20:33:04 +08:00
public Color4 Colour ;
2018-04-13 17:19:50 +08:00
2017-05-25 21:09:02 +08:00
/// <summary>
/// The scale of the triangle.
/// </summary>
2017-05-25 20:33:04 +08:00
public float Scale ;
2018-04-13 17:19:50 +08:00
2017-05-25 21:03:36 +08:00
/// <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 ) ;
2017-05-25 20:33:04 +08:00
}
2016-12-01 17:53:13 +08:00
}
}