1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-18 11:53:00 +08:00
osu-lazer/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs

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

381 lines
15 KiB
C#
Raw Normal View History

2022-09-19 03:08:34 +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.
using System;
2022-09-19 03:08:34 +08:00
using System.Collections.Generic;
using System.Diagnostics;
2022-10-18 18:33:03 +08:00
using System.Linq;
2022-09-19 03:08:34 +08:00
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
2022-09-19 03:08:34 +08: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-10-14 07:18:47 +08:00
using osu.Game.Utils;
2022-09-19 03:08:34 +08:00
using osuTK;
using osuTK.Graphics;
2022-09-19 03:08:34 +08:00
namespace osu.Game.Rulesets.Osu.Skinning
{
public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable
2022-09-19 03:08:34 +08:00
{
// fade anim values
private const double initial_fade_out_duration = 4000;
private const double re_fade_in_speed = 3;
private const double re_fade_in_duration = 50;
private const double final_fade_out_speed = 2;
private const double final_fade_out_duration = 8000;
private const float initial_alpha = 0.6f;
private const float re_fade_in_alpha = 1f;
private readonly int rotationSeed = RNG.Next();
// scale anim values
private const double scale_duration = 1200;
private const float initial_scale = 0.65f;
private const float final_scale = 1f;
// rotation anim values
private const double rotation_duration = 500;
private const float max_rotation = 0.25f;
2022-09-19 03:08:34 +08:00
2022-10-05 17:54:14 +08:00
public IShader? TextureShader { get; private set; }
public IShader? RoundedTextureShader { get; private set; }
protected Texture? Texture { get; set; }
private float radius => Texture?.DisplayWidth * 0.165f ?? 3;
protected readonly List<SmokePoint> SmokePoints = new List<SmokePoint>();
private float pointInterval => radius * 7f / 8;
private double smokeStartTime { get; set; } = double.MinValue;
private double smokeEndTime { get; set; } = double.MaxValue;
private float totalDistance;
private Vector2? lastPosition;
[BackgroundDependencyLoader]
private void load(ShaderManager shaders)
2022-09-19 03:08:34 +08:00
{
RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
2022-09-19 03:08:34 +08:00
}
protected override void LoadComplete()
{
base.LoadComplete();
2022-09-21 03:03:07 +08:00
RelativeSizeAxes = Axes.Both;
2022-09-20 01:16:05 +08:00
LifetimeStart = smokeStartTime = Time.Current;
2022-09-20 01:16:05 +08:00
2022-10-05 17:54:14 +08:00
totalDistance = pointInterval;
2022-09-19 03:08:34 +08:00
}
public void AddPosition(Vector2 position, double time)
2022-09-19 03:08:34 +08:00
{
lastPosition ??= position;
2022-09-19 03:08:34 +08:00
float delta = (position - (Vector2)lastPosition).LengthFast;
totalDistance += delta;
2022-10-05 17:54:14 +08:00
int count = (int)(totalDistance / pointInterval);
if (count > 0)
{
Vector2 increment = position - (Vector2)lastPosition;
increment.NormalizeFast();
2022-10-05 17:54:14 +08:00
Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
increment *= pointInterval;
2022-10-05 17:54:14 +08:00
totalDistance %= pointInterval;
2022-09-19 09:32:33 +08:00
2022-10-18 18:33:03 +08:00
if (SmokePoints.Count == 0 || SmokePoints[^1].Time <= time)
{
2022-10-18 18:33:03 +08:00
for (int i = 0; i < count; i++)
{
2022-10-18 18:33:03 +08:00
SmokePoints.Add(new SmokePoint
{
Position = pointPos,
Time = time,
Angle = RNG.NextSingle(0, 2 * MathF.PI),
});
pointPos += increment;
}
}
Invalidate(Invalidation.DrawNode);
}
lastPosition = position;
2022-09-19 03:08:34 +08:00
}
public void FinishDrawing(double time)
2022-09-19 03:08:34 +08:00
{
2022-10-05 17:54:14 +08:00
smokeEndTime = time;
2022-10-05 17:54:14 +08:00
double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, smokeEndTime - smokeStartTime);
LifetimeEnd = smokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed;
2022-09-19 03:08:34 +08:00
}
protected override DrawNode CreateDrawNode() => new SmokeDrawNode(this);
2022-09-19 03:08:34 +08:00
protected override void Update()
{
base.Update();
Invalidate(Invalidation.DrawNode);
2022-09-19 03:08:34 +08:00
}
protected struct SmokePoint
2022-09-19 03:08:34 +08:00
{
public Vector2 Position;
public double Time;
2022-10-18 18:33:03 +08:00
public float Angle;
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;
}
}
2022-10-18 18:33:03 +08:00
public struct LowerBoundComparer : IComparer<SmokePoint>
{
public int Compare(SmokePoint x, SmokePoint target)
{
// Similar logic as UpperBoundComparer, except returned index will always be
// the first element larger or equal
return x.Time < target.Time ? -1 : 1;
}
}
}
protected class SmokeDrawNode : TexturedShaderDrawNode
{
protected new SmokeSegment Source => (SmokeSegment)base.Source;
2022-09-20 19:42:12 +08: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-10-14 07:18:47 +08:00
private int rotationSeed;
2022-10-19 14:05:09 +08:00
private int firstVisiblePointIndex;
// anim calculation vars (color, scale, direction)
private double initialFadeOutDurationTrunc;
2022-10-18 18:33:03 +08:00
private double firstVisiblePointTimeAfterSmokeEnded;
private double initialFadeOutTime;
private double reFadeInTime;
private double finalFadeOutTime;
public SmokeDrawNode(ITexturedShaderDrawable source)
: base(source)
{
}
public override void ApplyState()
{
base.ApplyState();
2022-10-05 17:54:14 +08:00
radius = Source.radius;
drawSize = Source.DrawSize;
texture = Source.Texture;
2022-10-05 17:54:14 +08:00
SmokeStartTime = Source.smokeStartTime;
SmokeEndTime = Source.smokeEndTime;
CurrentTime = Source.Clock.CurrentTime;
2022-10-14 07:18:47 +08:00
rotationSeed = Source.rotationSeed;
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
2022-10-18 18:33:03 +08:00
firstVisiblePointTimeAfterSmokeEnded = SmokeEndTime - initialFadeOutDurationTrunc;
initialFadeOutTime = Math.Min(CurrentTime, SmokeEndTime);
reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTimeAfterSmokeEnded * (1 - 1 / re_fade_in_speed);
finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTimeAfterSmokeEnded * (1 - 1 / final_fade_out_speed);
2022-10-19 14:05:09 +08:00
double firstVisiblePointTime = Math.Min(SmokeEndTime, CurrentTime) - initialFadeOutDurationTrunc;
firstVisiblePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = firstVisiblePointTime }, new SmokePoint.LowerBoundComparer());
int futurePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = CurrentTime }, new SmokePoint.UpperBoundComparer());
2022-10-18 18:33:03 +08:00
points.Clear();
2022-10-19 14:05:09 +08:00
points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex));
}
public sealed override void Draw(IRenderer renderer)
2022-09-19 03:08:34 +08:00
{
base.Draw(renderer);
if (points.Count == 0)
return;
2022-10-18 18:33:03 +08:00
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(200, 4);
2022-10-19 13:59:58 +08:00
if (points.Count > quadBatch.Size && quadBatch.Size != IRenderer.MAX_QUADS)
2022-10-18 18:33:03 +08:00
{
int batchSize = Math.Min(quadBatch.Size * 2, IRenderer.MAX_QUADS);
2022-10-18 18:33:03 +08:00
quadBatch = renderer.CreateQuadBatch<TexturedVertex2D>(batchSize, 4);
}
2022-10-19 13:59:58 +08:00
texture ??= renderer.WhitePixel;
RectangleF textureRect = texture.GetTextureRect();
var shader = GetAppropriateShader(renderer);
renderer.SetBlend(BlendingParameters.Additive);
2022-09-20 19:20:32 +08:00
renderer.PushLocalMatrix(DrawInfo.Matrix);
shader.Bind();
texture.Bind();
2022-10-18 18:33:03 +08:00
for (int i = 0; i < points.Count; i++)
2022-10-19 14:05:09 +08:00
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
shader.Unbind();
2022-09-20 19:20:32 +08:00
renderer.PopLocalMatrix();
}
2022-09-20 19:39:12 +08:00
protected Color4 ColourAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour
? ((SRGBColour)DrawColourInfo.Colour).Linear
: DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, drawSize)).Linear;
protected virtual Color4 PointColour(SmokePoint point)
{
var color = Color4.White;
2022-10-18 18:33:03 +08:00
double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
2022-10-19 13:59:58 +08:00
2022-10-19 14:05:09 +08:00
if (timeDoingFinalFadeOut > 0 && point.Time >= firstVisiblePointTimeAfterSmokeEnded)
{
2022-10-18 18:33:03 +08:00
float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
fraction = MathF.Pow(fraction, 5);
color.A = (1 - fraction) * re_fade_in_alpha;
}
2022-10-18 18:33:03 +08:00
else
{
2022-10-18 18:33:03 +08:00
double timeDoingInitialFadeOut = initialFadeOutTime - point.Time;
2022-10-19 13:59:58 +08:00
2022-10-18 18:33:03 +08:00
if (timeDoingInitialFadeOut > 0)
{
2022-10-18 18:33:03 +08:00
float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
color.A = (1 - fraction) * initial_alpha;
}
2022-10-18 18:33:03 +08:00
if (point.Time > firstVisiblePointTimeAfterSmokeEnded)
{
2022-10-18 18:33:03 +08:00
double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
2022-10-19 13:59:58 +08:00
2022-10-18 18:33:03 +08:00
if (timeDoingReFadeIn > 0)
{
float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
fraction = 1 - MathF.Pow(1 - fraction, 5);
color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
}
}
}
return color;
}
protected virtual float PointScale(SmokePoint point)
{
double timeDoingScale = CurrentTime - point.Time;
float fraction = Math.Clamp((float)(timeDoingScale / scale_duration), 0, 1);
fraction = 1 - MathF.Pow(1 - fraction, 5);
return fraction * (final_scale - initial_scale) + initial_scale;
}
2022-10-18 18:33:03 +08:00
protected virtual Vector2 PointDirection(SmokePoint point, int index)
{
double timeDoingRotation = CurrentTime - point.Time;
float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1);
fraction = 1 - MathF.Pow(1 - fraction, 5);
2022-10-18 18:33:03 +08:00
float angle = fraction * getRotation(index) + point.Angle;
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
}
2022-10-18 18:33:03 +08:00
private float getRotation(int index) => max_rotation * (StatelessRNG.NextSingle(rotationSeed, index) * 2 - 1);
2022-10-18 18:33:03 +08:00
private void drawPointQuad(SmokePoint point, RectangleF textureRect, int index)
{
Debug.Assert(quadBatch != null);
2022-09-20 19:39:12 +08:00
var colour = PointColour(point);
2022-10-18 18:33:03 +08:00
if (colour.A == 0)
return;
2022-10-18 18:33:03 +08:00
float scale = PointScale(point);
if (scale == 0)
return;
2022-10-18 18:33:03 +08:00
var dir = PointDirection(point, index);
var ortho = dir.PerpendicularLeft;
2022-09-21 03:03:07 +08: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-19 03:08:34 +08:00
quadBatch.Add(new TexturedVertex2D
{
2022-09-20 19:20:32 +08:00
Position = localTopLeft,
TexturePosition = textureRect.TopLeft,
2022-09-20 19:39:12 +08:00
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopLeft), colour),
});
quadBatch.Add(new TexturedVertex2D
{
2022-09-20 19:20:32 +08:00
Position = localTopRight,
TexturePosition = textureRect.TopRight,
2022-09-20 19:39:12 +08:00
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopRight), colour),
});
quadBatch.Add(new TexturedVertex2D
{
2022-09-20 19:20:32 +08:00
Position = localBotRight,
TexturePosition = textureRect.BottomRight,
2022-09-20 19:39:12 +08:00
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotRight), colour),
});
quadBatch.Add(new TexturedVertex2D
{
2022-09-20 19:20:32 +08:00
Position = localBotLeft,
TexturePosition = textureRect.BottomLeft,
2022-09-20 19:39:12 +08:00
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotLeft), colour),
});
2022-09-19 03:08:34 +08:00
}
2022-09-20 19:38:22 +08:00
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
quadBatch?.Dispose();
2022-09-20 19:38:22 +08:00
}
2022-09-19 03:08:34 +08:00
}
}
}