Make DrawableSlider contain the slider body

This commit is contained in:
smoogipoo 2018-02-23 20:27:05 +09:00
parent ce7d212c3c
commit 08bb25347c
7 changed files with 232 additions and 18 deletions

View File

@ -79,7 +79,7 @@ public void UpdateSnakingPosition(Vector2 start, Vector2 end)
List<Vector2> curve = drawableSlider.Body.CurrentCurve; List<Vector2> curve = drawableSlider.Body.CurrentCurve;
var positionOnCurve = isRepeatAtEnd ? end : start; var positionOnCurve = isRepeatAtEnd ? end : start;
Position = positionOnCurve + drawableSlider.HitObject.StackOffset; Position = positionOnCurve - curve[0] + drawableSlider.HitObject.StackOffset;
if (curve.Count < 2) if (curve.Count < 2)
return; return;

View File

@ -30,6 +30,8 @@ public DrawableSlider(Slider s)
{ {
slider = s; slider = s;
Position = s.StackedPosition;
DrawableSliderTail tail; DrawableSliderTail tail;
Container<DrawableSliderTick> ticks; Container<DrawableSliderTick> ticks;
Container<DrawableRepeatPoint> repeatPoints; Container<DrawableRepeatPoint> repeatPoints;
@ -39,20 +41,20 @@ public DrawableSlider(Slider s)
Body = new SliderBody(s) Body = new SliderBody(s)
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Position = s.StackedPosition,
PathWidth = s.Scale * 64, PathWidth = s.Scale * 64,
}, },
ticks = new Container<DrawableSliderTick>(), ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container<DrawableRepeatPoint>(), repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s) Ball = new SliderBall(s)
{ {
BypassAutoSizeAxes = Axes.Both,
Scale = new Vector2(s.Scale), Scale = new Vector2(s.Scale),
AccentColour = AccentColour, AccentColour = AccentColour,
AlwaysPresent = true, AlwaysPresent = true,
Alpha = 0 Alpha = 0
}, },
HeadCircle = new DrawableHitCircle(s.HeadCircle), HeadCircle = new DrawableHitCircle(s.HeadCircle) { Position = s.HeadCircle.StackedPosition },
tail = new DrawableSliderTail(s.TailCircle) tail = new DrawableSliderTail(s.TailCircle) { Position = s.TailCircle.StackedPosition }
}; };
components.Add(Body); components.Add(Body);
@ -112,6 +114,13 @@ protected override void Update()
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress); foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress);
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking; foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
Size = Body.Size;
OriginPosition = Body.PathOffset;
foreach (var obj in NestedHitObjects)
obj.RelativeAnchorPosition = Vector2.Divide(OriginPosition, Body.DrawSize);
Ball.RelativeAnchorPosition = Vector2.Divide(OriginPosition, Body.DrawSize);
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)

View File

@ -19,8 +19,12 @@ public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking
public DrawableSliderTail(HitCircle hitCircle) public DrawableSliderTail(HitCircle hitCircle)
: base(hitCircle) : base(hitCircle)
{ {
AlwaysPresent = true; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
AlwaysPresent = true;
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)

View File

@ -29,6 +29,13 @@ public float PathWidth
set { path.PathWidth = value; } set { path.PathWidth = value; }
} }
/// <summary>
/// Offset in absolute coordinates from the start of the curve.
/// </summary>
public Vector2 PathOffset { get; private set; }
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
public readonly Bindable<bool> SnakingIn = new Bindable<bool>(); public readonly Bindable<bool> SnakingIn = new Bindable<bool>();
public readonly Bindable<bool> SnakingOut = new Bindable<bool>(); public readonly Bindable<bool> SnakingOut = new Bindable<bool>();
@ -75,6 +82,8 @@ public Color4 AccentColour
private int textureWidth => (int)PathWidth * 2; private int textureWidth => (int)PathWidth * 2;
private Vector2 topLeftOffset;
private readonly Slider slider; private readonly Slider slider;
public SliderBody(Slider s) public SliderBody(Slider s)
{ {
@ -84,6 +93,7 @@ public SliderBody(Slider s)
{ {
container = new BufferedContainer container = new BufferedContainer
{ {
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true, CacheDrawnFrameBuffer = true,
Children = new Drawable[] Children = new Drawable[]
{ {
@ -107,11 +117,11 @@ public void SetRange(double p0, double p1)
if (updateSnaking(p0, p1)) if (updateSnaking(p0, p1))
{ {
// Autosizing does not give us the desired behaviour here. // The path is generated such that its size encloses it. This change of size causes the path
// We want the container to have the same size as the slider, // to move around while snaking, so we need to offset it to make sure it maintains the
// and to be positioned such that the slider head is at (0,0). // same position as when it is fully snaked.
container.Size = path.Size; var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
container.Position = -path.PositionInBoundingBox(slider.Curve.PositionAt(0) - CurrentCurve[0]); path.Position = topLeftOffset - newTopLeftOffset;
container.ForceRedraw(); container.ForceRedraw();
} }
@ -121,6 +131,7 @@ public void SetRange(double p0, double p1)
private void load() private void load()
{ {
reloadTexture(); reloadTexture();
computeSize();
} }
private void reloadTexture() private void reloadTexture()
@ -164,7 +175,19 @@ private void reloadTexture()
path.Texture = texture; path.Texture = texture;
} }
public readonly List<Vector2> CurrentCurve = new List<Vector2>(); private void computeSize()
{
// Generate the entire curve
slider.Curve.GetPathToProgress(CurrentCurve, 0, 1);
foreach (Vector2 p in CurrentCurve)
path.AddVertex(p);
Size = path.Size;
topLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
PathOffset = path.PositionInBoundingBox(CurrentCurve[0]);
}
private bool updateSnaking(double p0, double p1) private bool updateSnaking(double p0, double p1)
{ {
if (SnakedStart == p0 && SnakedEnd == p1) return false; if (SnakedStart == p0 && SnakedEnd == p1) return false;
@ -176,7 +199,7 @@ private bool updateSnaking(double p0, double p1)
path.ClearVertices(); path.ClearVertices();
foreach (Vector2 p in CurrentCurve) foreach (Vector2 p in CurrentCurve)
path.AddVertex(p - CurrentCurve[0]); path.AddVertex(p);
return true; return true;
} }

View File

@ -99,7 +99,7 @@ private void createSliderEnds()
HeadCircle = new HitCircle HeadCircle = new HitCircle
{ {
StartTime = StartTime, StartTime = StartTime,
Position = StackedPosition, Position = this.PositionAt(0),
IndexInCurrentCombo = IndexInCurrentCombo, IndexInCurrentCombo = IndexInCurrentCombo,
ComboColour = ComboColour, ComboColour = ComboColour,
Samples = Samples, Samples = Samples,
@ -109,7 +109,7 @@ private void createSliderEnds()
TailCircle = new HitCircle TailCircle = new HitCircle
{ {
StartTime = EndTime, StartTime = EndTime,
Position = StackedEndPosition, Position = this.PositionAt(1),
IndexInCurrentCombo = IndexInCurrentCombo, IndexInCurrentCombo = IndexInCurrentCombo,
ComboColour = ComboColour ComboColour = ComboColour
}; };
@ -156,7 +156,7 @@ private void createTicks()
SpanIndex = span, SpanIndex = span,
SpanStartTime = spanStartTime, SpanStartTime = spanStartTime,
StartTime = spanStartTime + timeProgress * SpanDuration, StartTime = spanStartTime + timeProgress * SpanDuration,
Position = Curve.PositionAt(distanceProgress), Position = Curve.PositionAt(distanceProgress) - Curve.PositionAt(0),
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,
ComboColour = ComboColour, ComboColour = ComboColour,
@ -175,7 +175,7 @@ private void createRepeatPoints()
RepeatIndex = repeatIndex, RepeatIndex = repeatIndex,
SpanDuration = SpanDuration, SpanDuration = SpanDuration,
StartTime = StartTime + repeat * SpanDuration, StartTime = StartTime + repeat * SpanDuration,
Position = Curve.PositionAt(repeat % 2), Position = Curve.PositionAt(repeat % 2) - Curve.PositionAt(0),
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,
ComboColour = ComboColour, ComboColour = ComboColour,
@ -184,4 +184,10 @@ private void createRepeatPoints()
} }
} }
} }
public static class SliderExtensions
{
public static Vector2 PositionAt(this Slider slider, double progress)
=> ((IHasCurve)slider).PositionAt(progress) - slider.Curve.PositionAt(0);
}
} }

View File

@ -0,0 +1,171 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseNewSliderBody : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Path) };
private readonly NewSliderBody body;
public TestCaseNewSliderBody()
{
Add(body = new NewSliderBody(new SliderCurve
{
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0),
new Vector2(-50, 75),
new Vector2(0, 100),
new Vector2(100, -200),
new Vector2(230, 0)
},
Distance = 480,
CurveType = CurveType.Bezier
})
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
AddSliderStep("In", 0f, 1f, 0f, v => inLength = v);
AddSliderStep("Out", 0f, 1f, 1f, v => outLength = v);
AddSliderStep("Path Width", 0f, 100f, 10f, v => body.PathWidth = v);
}
private float _inLength;
private float inLength
{
set
{
_inLength = value;
body.UpdateSnaking(_inLength, _outLength);
}
}
private float _outLength;
private float outLength
{
set
{
_outLength = value;
body.UpdateSnaking(_inLength, _outLength);
}
}
private class NewSliderBody : CompositeDrawable
{
private readonly Path path;
private readonly SliderCurve curve;
public NewSliderBody(SliderCurve curve)
{
this.curve = curve;
InternalChild = path = new Path();
}
[BackgroundDependencyLoader]
private void load()
{
reloadTexture();
computeSize();
}
public float PathWidth
{
get => path.PathWidth;
set { path.PathWidth = value; reloadTexture(); }
}
private void reloadTexture()
{
var textureWidth = (int)PathWidth * 2;
//initialise background
var texture = new Texture(textureWidth, 1);
var upload = new TextureUpload(textureWidth * 4);
var bytes = upload.Data;
const float aa_portion = 0.02f;
const float border_portion = 0.128f;
const float gradient_portion = 1 - border_portion;
const float opacity_at_centre = 0.3f;
const float opacity_at_edge = 0.8f;
for (int i = 0; i < textureWidth; i++)
{
float progress = (float)i / (textureWidth - 1);
if (progress <= border_portion)
{
bytes[i * 4] = (byte)(Color4.White.R * 255);
bytes[i * 4 + 1] = (byte)(Color4.White.G * 255);
bytes[i * 4 + 2] = (byte)(Color4.White.B * 255);
bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (Color4.White.A * 255));
}
else
{
progress -= border_portion;
bytes[i * 4] = (byte)(Color4.Blue.R * 255);
bytes[i * 4 + 1] = (byte)(Color4.Blue.G * 255);
bytes[i * 4 + 2] = (byte)(Color4.Blue.B * 255);
bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (Color4.Blue.A * 255));
}
}
texture.SetData(upload);
path.Texture = texture;
}
private Vector2 topLeftOffset;
private void computeSize()
{
// Compute the final size
var fullPath = new List<Vector2>();
curve.GetPathToProgress(fullPath, 0, 1);
foreach (Vector2 p in fullPath)
path.AddVertex(p);
Size = path.Size;
topLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
OriginPosition = path.PositionInBoundingBox(fullPath[0]);
}
public void UpdateSnaking(float t0, float t1)
{
var curvePath = new List<Vector2>();
curve.GetPathToProgress(curvePath, t0, t1);
path.ClearVertices();
foreach (Vector2 p in curvePath)
path.AddVertex(p);
var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
path.Position = topLeftOffset - newTopLeftOffset;
}
}
}
}

View File

@ -124,6 +124,7 @@
<Compile Include="Replays\OsuReplayInputHandler.cs" /> <Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="Tests\TestCaseHitCircle.cs" /> <Compile Include="Tests\TestCaseHitCircle.cs" />
<Compile Include="Tests\TestCaseHitCircleHidden.cs" /> <Compile Include="Tests\TestCaseHitCircleHidden.cs" />
<Compile Include="Tests\TestCaseNewSliderBody.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" /> <Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Tests\TestCaseSlider.cs" /> <Compile Include="Tests\TestCaseSlider.cs" />
<Compile Include="Tests\TestCaseSliderHidden.cs" /> <Compile Include="Tests\TestCaseSliderHidden.cs" />