Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ppy
GitHub Repository: ppy/osu
Path: blob/master/osu.Game/Graphics/GhostIcon.cs
4378 views
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Shaders.Types;
using osu.Framework.Graphics.Sprites;
using osuTK;

namespace osu.Game.Graphics
{
    /// <summary>
    /// A (very cute) animated version of the <see cref="FontAwesome.Solid.Ghost"/> icon.
    /// </summary>
    public partial class GhostIcon : Drawable
    {
        private IShader ghostShader = null!;

        /// <summary>
        /// How long one complete loop of the ghost's animation takes, in milliseconds
        /// </summary>
        public float AnimationDuration = 2000;

        [BackgroundDependencyLoader]
        private void load(ShaderManager shaders)
        {
            ghostShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Ghost");
        }

        protected override void Update()
        {
            base.Update();

            Invalidate(Invalidation.DrawNode);
        }

        protected override DrawNode CreateDrawNode() => new GhostIconDrawNode(this);

        private class GhostIconDrawNode : DrawNode
        {
            protected new GhostIcon Source => (GhostIcon)base.Source;

            public GhostIconDrawNode(IDrawable source)
                : base(source)
            {
            }

            private Quad screenSpaceDrawQuad;
            private Vector4 drawRectangle;
            private Vector2 blend;
            private IShader shader = null!;
            private float time;

            public override void ApplyState()
            {
                base.ApplyState();

                screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad;
                drawRectangle = new Vector4(0, 0, Source.DrawWidth, Source.DrawHeight);
                shader = Source.ghostShader;
                blend = new Vector2(Math.Min(Source.DrawWidth, Source.DrawHeight) / Math.Min(screenSpaceDrawQuad.Width, screenSpaceDrawQuad.Height));
                time = (float)(Source.Time.Current / Source.AnimationDuration) % 1f;
            }

            private IUniformBuffer<GhostParameters>? ghostParametersBuffer;

            private IVertexBatch<TexturedVertex2D>? quadBatch;

            protected override void Draw(IRenderer renderer)
            {
                base.Draw(renderer);

                if (!renderer.BindTexture(renderer.WhitePixel))
                    return;

                quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(1, 2);
                ghostParametersBuffer ??= renderer.CreateUniformBuffer<GhostParameters>();

                ghostParametersBuffer.Data = new GhostParameters
                {
                    Time = time
                };

                shader.Bind();
                shader.BindUniformBlock("m_GhostParameters", ghostParametersBuffer);

                var vertexAction = quadBatch.AddAction;

                vertexAction(new TexturedVertex2D(renderer)
                {
                    Position = screenSpaceDrawQuad.BottomLeft,
                    TexturePosition = new Vector2(0, 1),
                    TextureRect = drawRectangle,
                    BlendRange = blend,
                    Colour = DrawColourInfo.Colour.BottomLeft.SRGB,
                });
                vertexAction(new TexturedVertex2D(renderer)
                {
                    Position = screenSpaceDrawQuad.BottomRight,
                    TexturePosition = new Vector2(1, 1),
                    TextureRect = drawRectangle,
                    BlendRange = blend,
                    Colour = DrawColourInfo.Colour.BottomRight.SRGB,
                });
                vertexAction(new TexturedVertex2D(renderer)
                {
                    Position = screenSpaceDrawQuad.TopRight,
                    TexturePosition = new Vector2(1, 0),
                    TextureRect = drawRectangle,
                    BlendRange = blend,
                    Colour = DrawColourInfo.Colour.TopRight.SRGB,
                });
                vertexAction(new TexturedVertex2D(renderer)
                {
                    Position = screenSpaceDrawQuad.TopLeft,
                    TexturePosition = Vector2.Zero,
                    TextureRect = drawRectangle,
                    BlendRange = blend,
                    Colour = DrawColourInfo.Colour.TopLeft.SRGB,
                });

                shader.Unbind();
            }

            [StructLayout(LayoutKind.Sequential, Pack = 1)]
            private record struct GhostParameters
            {
                public UniformFloat Time;
                private UniformPadding12 pad;
            }

            protected override void Dispose(bool isDisposing)
            {
                base.Dispose(isDisposing);

                ghostParametersBuffer?.Dispose();
                quadBatch?.Dispose();
            }
        }
    }
}