1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:42:57 +08:00

Merge branch 'slider-controlpoint-masks' into slider-placement

# Conflicts:
#	osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs
#	osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
This commit is contained in:
smoogipoo 2018-10-29 15:55:38 +09:00
commit 08b16be3b8
11 changed files with 247 additions and 15 deletions

View File

@ -1,11 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks; using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -15,6 +18,16 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestCaseSliderSelectionMask : HitObjectSelectionMaskTestCase public class TestCaseSliderSelectionMask : HitObjectSelectionMaskTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SliderSelectionMask),
typeof(SliderCircleSelectionMask),
typeof(SliderBodyPiece),
typeof(SliderCircle),
typeof(ControlPointVisualiser),
typeof(ControlPointPiece)
};
private readonly DrawableSlider drawableObject; private readonly DrawableSlider drawableObject;
public TestCaseSliderSelectionMask() public TestCaseSliderSelectionMask()

View File

@ -0,0 +1,118 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
{
public class ControlPointPiece : CompositeDrawable
{
private readonly Slider slider;
private readonly int index;
private readonly Path path;
private readonly CircularContainer marker;
[Resolved]
private OsuColour colours { get; set; }
public ControlPointPiece(Slider slider, int index)
{
this.slider = slider;
this.index = index;
Origin = Anchor.Centre;
Size = new Vector2(10);
InternalChildren = new Drawable[]
{
path = new SmoothPath
{
BypassAutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
PathWidth = 1
},
marker = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
}
};
}
protected override void Update()
{
base.Update();
Position = slider.StackedPosition + slider.ControlPoints[index];
marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow;
path.ClearVertices();
if (index != slider.ControlPoints.Length - 1)
{
path.AddVertex(Vector2.Zero);
path.AddVertex(slider.ControlPoints[index + 1] - slider.ControlPoints[index]);
}
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
}
protected override bool OnDragStart(DragStartEvent e) => true;
protected override bool OnDrag(DragEvent e)
{
if (index == 0)
{
// Special handling for the head - only the position of the slider changes
slider.Position += e.Delta;
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
var newControlPoints = slider.ControlPoints.ToArray();
for (int i = 1; i < newControlPoints.Length; i++)
newControlPoints[i] -= e.Delta;
slider.ControlPoints = newControlPoints;
slider.Curve.Calculate(true);
}
else
{
var newControlPoints = slider.ControlPoints.ToArray();
newControlPoints[index] += e.Delta;
slider.ControlPoints = newControlPoints;
slider.Curve.Calculate(true);
}
return true;
}
protected override bool OnDragEnd(DragEndEvent e) => true;
private bool isSegmentSeparator
{
get
{
bool separator = false;
if (index < slider.ControlPoints.Length - 1)
separator |= slider.ControlPoints[index + 1] == slider.ControlPoints[index];
if (index > 0)
separator |= slider.ControlPoints[index - 1] == slider.ControlPoints[index];
return separator;
}
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
{
public class ControlPointVisualiser : CompositeDrawable
{
private readonly Slider slider;
private readonly Container<ControlPointPiece> pieces;
public ControlPointVisualiser(Slider slider)
{
this.slider = slider;
InternalChild = pieces = new Container<ControlPointPiece> { RelativeSizeAxes = Axes.Both };
slider.ControlPointsChanged += _ => updateControlPoints();
updateControlPoints();
}
private void updateControlPoints()
{
while (slider.ControlPoints.Length > pieces.Count)
pieces.Add(new ControlPointPiece(slider, pieces.Count));
while (slider.ControlPoints.Length < pieces.Count)
pieces.Remove(pieces[pieces.Count - 1]);
}
}
}

View File

@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
{ {
this.slider = slider; this.slider = slider;
this.position = position; this.position = position;
slider.ControlPointsChanged += _ => UpdatePosition();
} }
protected override void UpdatePosition() protected override void UpdatePosition()

View File

@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
new SliderBodyPiece(sliderObject), new SliderBodyPiece(sliderObject),
headMask = new SliderCircleSelectionMask(slider.HeadCircle, sliderObject, SliderPosition.Start), headMask = new SliderCircleSelectionMask(slider.HeadCircle, sliderObject, SliderPosition.Start),
new SliderCircleSelectionMask(slider.TailCircle, sliderObject, SliderPosition.End), new SliderCircleSelectionMask(slider.TailCircle, sliderObject, SliderPosition.End),
new ControlPointVisualiser(sliderObject),
}; };
} }

View File

@ -90,6 +90,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Body.PathWidth = HitObject.Scale * 64; Body.PathWidth = HitObject.Scale * 64;
Ball.Scale = new Vector2(HitObject.Scale); Ball.Scale = new Vector2(HitObject.Scale);
}; };
slider.ControlPointsChanged += _ => Body.Refresh();
} }
public override Color4 AccentColour public override Color4 AccentColour

View File

@ -16,7 +16,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
this.slider = slider; this.slider = slider;
Position = HitObject.Position - slider.Position; h.PositionChanged += _ => updatePosition();
slider.ControlPointsChanged += _ => updatePosition();
updatePosition();
} }
protected override void Update() protected override void Update()
@ -33,5 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Action<double> OnShake; public Action<double> OnShake;
protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
private void updatePosition() => Position = HitObject.Position - slider.Position;
} }
} }

View File

@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking
{ {
private readonly Slider slider;
/// <summary> /// <summary>
/// The judgement text is provided by the <see cref="DrawableSlider"/>. /// The judgement text is provided by the <see cref="DrawableSlider"/>.
/// </summary> /// </summary>
@ -18,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
: base(hitCircle) : base(hitCircle)
{ {
this.slider = slider;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -25,7 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true; AlwaysPresent = true;
Position = HitObject.Position - slider.Position; hitCircle.PositionChanged += _ => updatePosition();
slider.ControlPointsChanged += _ => updatePosition();
updatePosition();
} }
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
@ -33,5 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!userTriggered && timeOffset >= 0) if (!userTriggered && timeOffset >= 0)
ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss); ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
} }
private void updatePosition() => Position = HitObject.Position - slider.Position;
} }
} }

View File

@ -45,15 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
// Generate the entire curve Refresh();
slider.Curve.GetPathToProgress(CurrentCurve, 0, 1);
SetVertices(CurrentCurve);
// The body is sized to the full path size to avoid excessive autosize computations
Size = Path.Size;
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
} }
public void UpdateProgress(double completionProgress) public void UpdateProgress(double completionProgress)
@ -80,6 +72,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
setRange(start, end); setRange(start, end);
} }
public void Refresh()
{
// Generate the entire curve
slider.Curve.GetPathToProgress(CurrentCurve, 0, 1);
SetVertices(CurrentCurve);
// The body is sized to the full path size to avoid excessive autosize computations
Size = Path.Size;
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
var lastSnakedStart = SnakedStart ?? 0;
var lastSnakedEnd = SnakedEnd ?? 0;
SnakedStart = null;
SnakedEnd = null;
setRange(lastSnakedStart, lastSnakedEnd);
}
private void setRange(double p0, double p1) private void setRange(double p0, double p1)
{ {
if (p0 > p1) if (p0 > p1)

View File

@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary> /// </summary>
private const float base_scoring_distance = 100; private const float base_scoring_distance = 100;
public event Action<Vector2[]> ControlPointsChanged;
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
@ -54,8 +56,18 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2[] ControlPoints public Vector2[] ControlPoints
{ {
get { return Curve.ControlPoints; } get => Curve.ControlPoints;
set { Curve.ControlPoints = value; } set
{
if (Curve.ControlPoints == value)
return;
Curve.ControlPoints = value;
ControlPointsChanged?.Invoke(value);
if (TailCircle != null)
TailCircle.Position = EndPosition;
}
} }
public CurveType CurveType public CurveType CurveType

View File

@ -120,10 +120,33 @@ namespace osu.Game.Rulesets.Objects
} }
} }
public void Calculate() private void calculateCumulativeLength()
{
double l = 0;
cumulativeLength.Clear();
cumulativeLength.Add(l);
for (int i = 0; i < calculatedPath.Count - 1; ++i)
{
Vector2 diff = calculatedPath[i + 1] - calculatedPath[i];
double d = diff.Length;
l += d;
cumulativeLength.Add(l);
}
Distance = l;
}
public void Calculate(bool updateDistance = false)
{ {
calculatePath(); calculatePath();
calculateCumulativeLengthAndTrimPath();
if (!updateDistance)
calculateCumulativeLengthAndTrimPath();
else
calculateCumulativeLength();
} }
private int indexOfDistance(double d) private int indexOfDistance(double d)