示例#1
0
        /// <summary>
        /// Converts a given point list into a set of path segments.
        /// </summary>
        /// <param name="points">The point list.</param>
        /// <param name="endPoint">Any extra endpoint to consider as part of the points. This will NOT be returned.</param>
        /// <param name="first">Whether this is the first segment in the set. If <c>true</c> the first of the returned segments will contain a zero point.</param>
        /// <param name="offset">The positional offset to apply to the control points.</param>
        /// <returns>The set of points contained by <paramref name="points"/> as one or more segments of the path, prepended by an extra zero point if <paramref name="first"/> is <c>true</c>.</returns>
        private IEnumerable <Memory <PathControlPoint> > convertPoints(ReadOnlyMemory <string> points, string endPoint, bool first, Vector2 offset)
        {
            PathType type = convertPathType(points.Span[0]);

            int readOffset     = first ? 1 : 0;            // First control point is zero for the first segment.
            int readablePoints = points.Length - 1;        // Total points readable from the base point span.
            int endPointLength = endPoint != null ? 1 : 0; // Extra length if an endpoint is given that lies outside the base point span.

            var vertices = new PathControlPoint[readOffset + readablePoints + endPointLength];

            // Fill any non-read points.
            for (int i = 0; i < readOffset; i++)
            {
                vertices[i] = new PathControlPoint();
            }

            // Parse into control points.
            for (int i = 1; i < points.Length; i++)
            {
                readPoint(points.Span[i], offset, out vertices[readOffset + i - 1]);
            }

            // If an endpoint is given, add it to the end.
            if (endPoint != null)
            {
                readPoint(endPoint, offset, out vertices[^ 1]);
示例#2
0
        private PathControlPoint addControlPoint(Vector2 position)
        {
            position -= HitObject.Position;

            int   insertionIndex = 0;
            float minDistance    = float.MaxValue;

            for (int i = 0; i < controlPoints.Count - 1; i++)
            {
                float dist = new Line(controlPoints[i].Position, controlPoints[i + 1].Position).DistanceToPoint(position);

                if (dist < minDistance)
                {
                    insertionIndex = i + 1;
                    minDistance    = dist;
                }
            }

            var pathControlPoint = new PathControlPoint {
                Position = position
            };

            // Move the control points from the insertion index onwards to make room for the insertion
            controlPoints.Insert(insertionIndex, pathControlPoint);

            HitObject.SnapTo(snapProvider);

            return(pathControlPoint);
        }
示例#3
0
 /// <summary>
 /// Selects the <see cref="PathControlPointPiece"/> corresponding to the given <paramref name="pathControlPoint"/>,
 /// and deselects all other <see cref="PathControlPointPiece"/>s.
 /// </summary>
 public void SetSelectionTo(PathControlPoint pathControlPoint)
 {
     foreach (var p in Pieces)
     {
         p.IsSelected.Value = p.ControlPoint == pathControlPoint;
     }
 }
示例#4
0
        private void updateCursor()
        {
            if (canPlaceNewControlPoint(out _))
            {
                // The cursor does not overlap a previous control point, so it can be added if not already existing.
                if (cursor == null)
                {
                    HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint {
                        Position = { Value = Vector2.Zero }
                    });

                    // The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier).
                    currentSegmentLength++;
                    updatePathType();
                }

                // Update the cursor position.
                cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
            }
            else if (cursor != null)
            {
                // The cursor overlaps a previous control point, so it's removed.
                HitObject.Path.ControlPoints.Remove(cursor);
                cursor = null;

                // The path type should be adjusted in the reverse progression of updatePathType() (Bezier -> PC -> Linear).
                currentSegmentLength--;
                updatePathType();
            }
        }
示例#5
0
        public SliderPlacementBlueprint()
            : base(new Objects.Slider())
        {
            RelativeSizeAxes = Axes.Both;

            HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.Linear));
            currentSegmentLength = 1;
        }
示例#6
0
 protected override void OnMouseUp(MouseUpEvent e)
 {
     if (placementControlPoint != null)
     {
         placementControlPoint = null;
         changeHandler?.EndChange();
     }
 }
示例#7
0
        protected override bool OnDoubleClick(DoubleClickEvent e)
        {
            // Todo: This should all not occur on double click, but rather if the previous control point is hovered.
            segmentStart            = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1];
            segmentStart.Type.Value = PathType.Linear;

            currentSegmentLength = 1;
            return(true);
        }
示例#8
0
        /// <summary>
        /// Whether a new control point can be placed at the current mouse position.
        /// </summary>
        /// <param name="lastPoint">The last-placed control point. May be null, but is not null if <c>false</c> is returned.</param>
        /// <returns>Whether a new control point can be placed at the current position.</returns>
        private bool canPlaceNewControlPoint([CanBeNull] out PathControlPoint lastPoint)
        {
            // We cannot rely on the ordering of drawable pieces, so find the respective drawable piece by searching for the last non-cursor control point.
            var last      = HitObject.Path.ControlPoints.LastOrDefault(p => p != cursor);
            var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last);

            lastPoint = last;
            return(lastPiece?.IsHovered != true);
        }
示例#9
0
        private void dragStarted(PathControlPoint controlPoint)
        {
            dragStartPositions       = slider.Path.ControlPoints.Select(point => point.Position).ToArray();
            dragPathTypes            = slider.Path.ControlPoints.Select(point => point.Type).ToArray();
            draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint);
            selectedControlPoints    = new HashSet <PathControlPoint>(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint));

            Debug.Assert(draggedControlPointIndex >= 0);

            changeHandler?.BeginChange();
        }
示例#10
0
        public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
        {
            this.slider  = slider;
            ControlPoint = controlPoint;

            // we don't want to run the path type update on construction as it may inadvertently change the slider.
            cachePoints(slider);

            slider.Path.Version.BindValueChanged(_ =>
            {
                cachePoints(slider);
                updatePathType();
            });

            controlPoint.Changed += updateMarkerDisplay;

            Origin       = Anchor.Centre;
            AutoSizeAxes = Axes.Both;

            InternalChildren = new Drawable[]
            {
                marker = new Container
                {
                    Anchor       = Anchor.Centre,
                    Origin       = Anchor.Centre,
                    AutoSizeAxes = Axes.Both,
                    Children     = new[]
                    {
                        new Circle
                        {
                            Anchor = Anchor.Centre,
                            Origin = Anchor.Centre,
                            Size   = new Vector2(20),
                        },
                        markerRing = new CircularContainer
                        {
                            Anchor          = Anchor.Centre,
                            Origin          = Anchor.Centre,
                            Size            = new Vector2(28),
                            Masking         = true,
                            BorderThickness = 2,
                            BorderColour    = Color4.White,
                            Alpha           = 0,
                            Child           = new Box
                            {
                                RelativeSizeAxes = Axes.Both,
                                Alpha            = 0,
                                AlwaysPresent    = true
                            }
                        }
                    }
                }
            };
        }
示例#11
0
        private void ensureCursor()
        {
            if (cursor == null)
            {
                HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint {
                    Position = { Value = Vector2.Zero }
                });
                currentSegmentLength++;

                updatePathType();
            }
        }
示例#12
0
        protected PathControlPoint(PathControlPoint obj)
        {
            if (obj == null)
            {
                throw new System.ArgumentNullException("obj");
            }

            _path  = obj._path;
            _brush = obj.Brush;
            _pen   = obj.Pen;
            _size  = obj.Size;
        }
示例#13
0
        public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
        {
            this.slider  = slider;
            ControlPoint = controlPoint;

            slider.Path.Version.BindValueChanged(_ =>
            {
                PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
                updatePathType();
            }, runOnceImmediately: true);

            controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());

            Origin       = Anchor.Centre;
            AutoSizeAxes = Axes.Both;

            InternalChildren = new Drawable[]
            {
                marker = new Container
                {
                    Anchor       = Anchor.Centre,
                    Origin       = Anchor.Centre,
                    AutoSizeAxes = Axes.Both,
                    Children     = new[]
                    {
                        new Circle
                        {
                            Anchor = Anchor.Centre,
                            Origin = Anchor.Centre,
                            Size   = new Vector2(20),
                        },
                        markerRing = new CircularContainer
                        {
                            Anchor          = Anchor.Centre,
                            Origin          = Anchor.Centre,
                            Size            = new Vector2(28),
                            Masking         = true,
                            BorderThickness = 2,
                            BorderColour    = Color4.White,
                            Alpha           = 0,
                            Child           = new Box
                            {
                                RelativeSizeAxes = Axes.Both,
                                Alpha            = 0,
                                AlwaysPresent    = true
                            }
                        }
                    }
                }
            };
        }
        public PathControlPointConnectionPiece(Slider slider, PathControlPoint controlPoint)
        {
            this.slider  = slider;
            ControlPoint = controlPoint;

            Origin       = Anchor.Centre;
            AutoSizeAxes = Axes.Both;

            InternalChild = path = new SmoothPath
            {
                Anchor     = Anchor.Centre,
                PathRadius = 1
            };
        }
示例#15
0
        public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
        {
            this.slider = slider;

            ControlPoint = controlPoint;

            Origin       = Anchor.Centre;
            AutoSizeAxes = Axes.Both;

            InternalChildren = new Drawable[]
            {
                path = new SmoothPath
                {
                    Anchor     = Anchor.Centre,
                    PathRadius = 1
                },
                marker = new Container
                {
                    Anchor       = Anchor.Centre,
                    Origin       = Anchor.Centre,
                    AutoSizeAxes = Axes.Both,
                    Children     = new[]
                    {
                        new Circle
                        {
                            Anchor = Anchor.Centre,
                            Origin = Anchor.Centre,
                            Size   = new Vector2(10),
                        },
                        markerRing = new CircularContainer
                        {
                            Anchor          = Anchor.Centre,
                            Origin          = Anchor.Centre,
                            Size            = new Vector2(14),
                            Masking         = true,
                            BorderThickness = 2,
                            BorderColour    = Color4.White,
                            Alpha           = 0,
                            Child           = new Box
                            {
                                RelativeSizeAxes = Axes.Both,
                                Alpha            = 0,
                                AlwaysPresent    = true
                            }
                        }
                    }
                }
            };
        }
        public PathControlPointConnectionPiece(Slider slider, int controlPointIndex)
        {
            this.slider            = slider;
            this.controlPointIndex = controlPointIndex;

            Origin       = Anchor.Centre;
            AutoSizeAxes = Axes.Both;

            ControlPoint = slider.Path.ControlPoints[controlPointIndex];

            InternalChild = path = new SmoothPath
            {
                Anchor     = Anchor.Centre,
                PathRadius = 1
            };
        }
示例#17
0
        protected override bool OnClick(ClickEvent e)
        {
            switch (state)
            {
            case PlacementState.Initial:
                beginCurve();
                break;

            case PlacementState.Body:
                switch (e.Button)
                {
                case MouseButton.Left:
                    ensureCursor();

                    // Detatch the cursor
                    cursor = null;
                    break;
                }

                break;
            }

            return(true);
        }
示例#18
0
        protected override bool OnMouseDown(MouseDownEvent e)
        {
            if (e.Button != MouseButton.Left)
            {
                return(base.OnMouseDown(e));
            }

            switch (state)
            {
            case SliderPlacementState.Initial:
                beginCurve();
                break;

            case SliderPlacementState.Body:
                if (canPlaceNewControlPoint(out var lastPoint))
                {
                    // Place a new point by detatching the current cursor.
                    updateCursor();
                    cursor = null;
                }
                else
                {
                    // Transform the last point into a new segment.
                    Debug.Assert(lastPoint != null);

                    segmentStart            = lastPoint;
                    segmentStart.Type.Value = PathType.Linear;

                    currentSegmentLength = 1;
                }

                break;
            }

            return(true);
        }
示例#19
0
        private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position)
        {
            PathType?lastType = null;

            for (int i = 0; i < pathData.Path.ControlPoints.Count; i++)
            {
                PathControlPoint point = pathData.Path.ControlPoints[i];

                if (point.Type != null)
                {
                    // We've reached a new (explicit) segment!

                    // Explicit segments have a new format in which the type is injected into the middle of the control point string.
                    // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point.
                    // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments
                    bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PerfectCurve;

                    // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable.
                    // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder.
                    if (i > 1)
                    {
                        // We need to use the absolute control point position to determine equality, otherwise floating point issues may arise.
                        Vector2 p1 = position + pathData.Path.ControlPoints[i - 1].Position;
                        Vector2 p2 = position + pathData.Path.ControlPoints[i - 2].Position;

                        if ((int)p1.X == (int)p2.X && (int)p1.Y == (int)p2.Y)
                        {
                            needsExplicitSegment = true;
                        }
                    }

                    if (needsExplicitSegment)
                    {
                        switch (point.Type)
                        {
                        case PathType.Bezier:
                            writer.Write("B|");
                            break;

                        case PathType.Catmull:
                            writer.Write("C|");
                            break;

                        case PathType.PerfectCurve:
                            writer.Write("P|");
                            break;

                        case PathType.Linear:
                            writer.Write("L|");
                            break;
                        }

                        lastType = point.Type;
                    }
                    else
                    {
                        // New segment with the same type - duplicate the control point
                        writer.Write(FormattableString.Invariant($"{position.X + point.Position.X}:{position.Y + point.Position.Y}|"));
                    }
                }

                if (i != 0)
                {
                    writer.Write(FormattableString.Invariant($"{position.X + point.Position.X}:{position.Y + point.Position.Y}"));
                    writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ",");
                }
            }

            var curveData = pathData as IHasPathWithRepeats;

            writer.Write(FormattableString.Invariant($"{(curveData?.RepeatCount ?? 0) + 1},"));
            writer.Write(FormattableString.Invariant($"{pathData.Path.Distance},"));

            if (curveData != null)
            {
                for (int i = 0; i < curveData.NodeSamples.Count; i++)
                {
                    writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}"));
                    writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
                }

                for (int i = 0; i < curveData.NodeSamples.Count; i++)
                {
                    writer.Write(getSampleBank(curveData.NodeSamples[i], true));
                    writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
                }
            }
        }
示例#20
0
 protected override bool OnDoubleClick(DoubleClickEvent e)
 {
     // Todo: This should all not occur on double click, but rather if the previous control point is hovered.
     segmentStart            = HitObject.Path.ControlPoints[^ 1];
示例#21
0
        private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position)
        {
            PathType?lastType = null;

            for (int i = 0; i < pathData.Path.ControlPoints.Count; i++)
            {
                PathControlPoint point = pathData.Path.ControlPoints[i];

                if (point.Type.Value != null)
                {
                    if (point.Type.Value != lastType)
                    {
                        switch (point.Type.Value)
                        {
                        case PathType.Bezier:
                            writer.Write("B|");
                            break;

                        case PathType.Catmull:
                            writer.Write("C|");
                            break;

                        case PathType.PerfectCurve:
                            writer.Write("P|");
                            break;

                        case PathType.Linear:
                            writer.Write("L|");
                            break;
                        }

                        lastType = point.Type.Value;
                    }
                    else
                    {
                        // New segment with the same type - duplicate the control point
                        writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}|"));
                    }
                }

                if (i != 0)
                {
                    writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}"));
                    writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ",");
                }
            }

            var curveData = pathData as IHasPathWithRepeats;

            writer.Write(FormattableString.Invariant($"{(curveData?.RepeatCount ?? 0) + 1},"));
            writer.Write(FormattableString.Invariant($"{pathData.Path.Distance},"));

            if (curveData != null)
            {
                for (int i = 0; i < curveData.NodeSamples.Count; i++)
                {
                    writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}"));
                    writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
                }

                for (int i = 0; i < curveData.NodeSamples.Count; i++)
                {
                    writer.Write(getSampleBank(curveData.NodeSamples[i], true));
                    writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
                }
            }
        }