osu/osu.Game.Rulesets.Osu/Skinning/Smoke.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

306 lines
10 KiB
C#
Raw Normal View History

2022-09-18 19:08:34 +00: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;
2022-09-18 19:08:34 +00:00
using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
2022-09-18 19:08:34 +00:00
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
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.Textures;
using osu.Framework.Utils;
2022-09-18 19:08:34 +00:00
using osu.Game.Rulesets.Osu.UI;
using osuTK;
using osuTK.Graphics;
2022-09-18 19:08:34 +00:00
namespace osu.Game.Rulesets.Osu.Skinning
{
public abstract class Smoke : Drawable, ITexturedShaderDrawable
2022-09-18 19:08:34 +00:00
{
public IShader? TextureShader { get; private set; }
public IShader? RoundedTextureShader { get; private set; }
private float? radius;
2022-09-19 01:32:33 +00:00
protected float Radius
{
get => radius ?? Texture?.DisplayWidth * 0.165f ?? 3;
2022-09-20 12:01:18 +00:00
set => radius = value;
}
protected Texture? Texture { get; set; }
2022-09-19 01:32:33 +00:00
protected double SmokeStartTime { get; private set; } = double.MinValue;
protected double SmokeEndTime { get; private set; } = double.MaxValue;
protected virtual float PointInterval => Radius * 7f / 8;
2022-09-18 19:08:34 +00:00
protected readonly List<SmokePoint> SmokePoints = new List<SmokePoint>();
private float totalDistance;
2022-09-19 01:32:33 +00:00
private Vector2? lastPosition;
2022-09-20 11:36:44 +00:00
private const int max_point_count = 18_000;
2022-09-18 19:08:34 +00:00
[Resolved(CanBeNull = true)]
private SmokeContainer? smokeContainer { get; set; }
[BackgroundDependencyLoader]
private void load(ShaderManager shaders)
2022-09-18 19:08:34 +00:00
{
RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
2022-09-18 19:08:34 +00:00
}
protected override void LoadComplete()
{
base.LoadComplete();
2022-09-20 19:03:07 +00:00
RelativeSizeAxes = Axes.Both;
2022-09-19 17:16:05 +00:00
SmokeStartTime = Time.Current;
totalDistance = PointInterval;
2022-09-18 19:08:34 +00:00
if (smokeContainer != null)
{
smokeContainer.SmokeMoved += onSmokeMoved;
smokeContainer.SmokeEnded += onSmokeEnded;
2022-09-18 19:08:34 +00:00
2022-09-19 17:16:05 +00:00
onSmokeMoved(smokeContainer.LastMousePosition, Time.Current);
}
2022-09-18 19:08:34 +00:00
}
private Vector2 nextPointDirection()
{
float angle = RNG.NextSingle(0, 2 * MathF.PI);
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
}
private void onSmokeMoved(Vector2 position, double time)
2022-09-18 19:08:34 +00:00
{
lastPosition ??= position;
2022-09-18 19:08:34 +00:00
float delta = (position - (Vector2)lastPosition).LengthFast;
totalDistance += delta;
int count = (int)(totalDistance / PointInterval);
if (count > 0)
{
Vector2 increment = position - (Vector2)lastPosition;
increment.NormalizeFast();
Vector2 pointPos = (PointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
increment *= PointInterval;
if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time)
{
int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer());
SmokePoints.RemoveRange(index, SmokePoints.Count - index);
}
totalDistance %= PointInterval;
2022-09-19 01:32:33 +00:00
for (int i = 0; i < count; i++)
{
SmokePoints.Add(new SmokePoint
{
Position = pointPos,
Time = time,
Direction = nextPointDirection(),
});
pointPos += increment;
}
Invalidate(Invalidation.DrawNode);
}
lastPosition = position;
if (SmokePoints.Count >= max_point_count)
onSmokeEnded(time);
2022-09-18 19:08:34 +00:00
}
public abstract override double LifetimeEnd { get; }
private void onSmokeEnded(double time)
2022-09-18 19:08:34 +00:00
{
2022-10-03 23:07:39 +00:00
if (smokeContainer != null)
{
smokeContainer.SmokeMoved -= onSmokeMoved;
smokeContainer.SmokeEnded -= onSmokeEnded;
}
SmokeEndTime = time;
2022-09-18 19:08:34 +00:00
}
protected abstract override DrawNode CreateDrawNode();
2022-09-18 19:08:34 +00:00
protected override void Update()
{
base.Update();
Invalidate(Invalidation.DrawNode);
2022-09-18 19:08:34 +00:00
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (smokeContainer != null)
{
smokeContainer.SmokeMoved -= onSmokeMoved;
smokeContainer.SmokeEnded -= onSmokeEnded;
2022-09-18 19:08:34 +00:00
}
}
protected struct SmokePoint
2022-09-18 19:08:34 +00:00
{
public Vector2 Position;
public double Time;
public Vector2 Direction;
public struct UpperBoundComparer : IComparer<SmokePoint>
{
public int Compare(SmokePoint x, SmokePoint target)
{
// By returning -1 when the target value is equal to x, guarantees that the
// element at BinarySearch's returned index will always be the first element
// larger. Since 0 is never returned, the target is never "found", so the return
// value will be the index's complement.
return x.Time > target.Time ? 1 : -1;
}
}
}
protected abstract class SmokeDrawNode : TexturedShaderDrawNode
{
protected new Smoke Source => (Smoke)base.Source;
2022-09-20 11:42:12 +00:00
protected double SmokeStartTime { get; private set; }
protected double SmokeEndTime { get; private set; }
protected double CurrentTime { get; private set; }
private readonly List<SmokePoint> points = new List<SmokePoint>();
private IVertexBatch<TexturedVertex2D>? quadBatch;
private float radius;
private Vector2 drawSize;
private Texture? texture;
2022-09-19 01:32:33 +00:00
protected SmokeDrawNode(ITexturedShaderDrawable source)
: base(source)
{
}
public override void ApplyState()
{
base.ApplyState();
points.Clear();
points.AddRange(Source.SmokePoints);
radius = Source.Radius;
drawSize = Source.DrawSize;
texture = Source.Texture;
SmokeStartTime = Source.SmokeStartTime;
SmokeEndTime = Source.SmokeEndTime;
CurrentTime = Source.Clock.CurrentTime;
}
public sealed override void Draw(IRenderer renderer)
2022-09-18 19:08:34 +00:00
{
base.Draw(renderer);
if (points.Count == 0)
return;
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(max_point_count / 10, 10);
texture ??= renderer.WhitePixel;
RectangleF textureRect = texture.GetTextureRect();
var shader = GetAppropriateShader(renderer);
renderer.SetBlend(BlendingParameters.Additive);
2022-09-20 11:20:32 +00:00
renderer.PushLocalMatrix(DrawInfo.Matrix);
shader.Bind();
texture.Bind();
foreach (var point in points)
drawPointQuad(point, textureRect);
shader.Unbind();
2022-09-20 11:20:32 +00:00
renderer.PopLocalMatrix();
}
2022-09-20 11:39:12 +00:00
protected Color4 ColourAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour
? ((SRGBColour)DrawColourInfo.Colour).Linear
: DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, drawSize)).Linear;
2022-09-20 11:39:12 +00:00
protected abstract Color4 PointColour(SmokePoint point);
protected abstract float PointScale(SmokePoint point);
protected abstract Vector2 PointDirection(SmokePoint point);
private void drawPointQuad(SmokePoint point, RectangleF textureRect)
{
Debug.Assert(quadBatch != null);
2022-09-20 11:39:12 +00:00
var colour = PointColour(point);
float scale = PointScale(point);
var dir = PointDirection(point);
var ortho = dir.PerpendicularLeft;
2022-09-20 11:39:12 +00:00
if (colour.A == 0 || scale == 0)
return;
2022-09-20 19:03:07 +00:00
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
var localBotRight = point.Position + (radius * scale * (ortho + dir));
2022-09-18 19:08:34 +00:00
quadBatch.Add(new TexturedVertex2D
{
2022-09-20 11:20:32 +00:00
Position = localTopLeft,
TexturePosition = textureRect.TopLeft,
2022-09-20 11:39:12 +00:00
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopLeft), colour),
});
quadBatch.Add(new TexturedVertex2D
{
2022-09-20 11:20:32 +00:00
Position = localTopRight,
TexturePosition = textureRect.TopRight,
2022-09-20 11:39:12 +00:00
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopRight), colour),
});
quadBatch.Add(new TexturedVertex2D
{
2022-09-20 11:20:32 +00:00
Position = localBotRight,
TexturePosition = textureRect.BottomRight,
2022-09-20 11:39:12 +00:00
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotRight), colour),
});
quadBatch.Add(new TexturedVertex2D
{
2022-09-20 11:20:32 +00:00
Position = localBotLeft,
TexturePosition = textureRect.BottomLeft,
2022-09-20 11:39:12 +00:00
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotLeft), colour),
});
2022-09-18 19:08:34 +00:00
}
2022-09-20 11:38:22 +00:00
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
quadBatch?.Dispose();
2022-09-20 11:38:22 +00:00
}
2022-09-18 19:08:34 +00:00
}
}
}