private static List <SliderPartCherry> generateSliderBody(HitObject obj, IHasCurve curve, bool isKiai, int index)
        {
            var objPosition = (obj as IHasPosition)?.Position ?? Vector2.Zero;
            var comboData   = obj as IHasCombo;

            List <SliderPartCherry> hitObjects = new List <SliderPartCherry>();

            var bodyCherriesCount = Math.Min(curve.Distance * (curve.RepeatCount + 1) / 10, max_visuals_per_slider_span * (curve.RepeatCount + 1));

            for (int i = 0; i < bodyCherriesCount; i++)
            {
                var progress = (float)i / bodyCherriesCount;
                var position = curve.CurvePositionAt(progress) + objPosition;

                position *= new Vector2(1, 0.5f);

                if (positionIsValid(position))
                {
                    hitObjects.Add(new SliderPartCherry
                    {
                        StartTime      = obj.StartTime + curve.Duration * progress,
                        Position       = position,
                        NewCombo       = comboData?.NewCombo ?? false,
                        ComboOffset    = comboData?.ComboOffset ?? 0,
                        IndexInBeatmap = index,
                        IsKiai         = isKiai,
                    });
                }
            }

            return(hitObjects);
        }
        private static List <TouhouHitObject> generateDefaultSlider(HitObject obj, IBeatmap beatmap, IHasCurve curve, double spanDuration, bool isKiai, int index)
        {
            List <TouhouHitObject> hitObjects = new List <TouhouHitObject>();

            var objPosition = (obj as IHasPosition)?.Position ?? Vector2.Zero;
            var comboData   = obj as IHasCombo;
            var difficulty  = beatmap.BeatmapInfo.BaseDifficulty;

            var controlPointInfo = beatmap.ControlPointInfo;
            TimingControlPoint     timingPoint     = controlPointInfo.TimingPointAt(obj.StartTime);
            DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(obj.StartTime);

            double scoringDistance = 100 * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;

            var velocity     = scoringDistance / timingPoint.BeatLength;
            var tickDistance = scoringDistance / difficulty.SliderTickRate;

            double legacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0;

            foreach (var e in SliderEventGenerator.Generate(obj.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset))
            {
                var sliderEventPosition = (curve.CurvePositionAt(e.PathProgress / (curve.RepeatCount + 1)) + objPosition) * new Vector2(1, 0.5f);

                switch (e.Type)
                {
                case SliderEventType.Head:

                    hitObjects.Add(new SoundHitObject
                    {
                        StartTime = obj.StartTime,
                        Samples   = obj.Samples
                    });

                    break;

                case SliderEventType.Tick:

                    if (positionIsValid(sliderEventPosition))
                    {
                        hitObjects.Add(new TickCherry
                        {
                            Angle          = 180,
                            StartTime      = e.Time,
                            Position       = sliderEventPosition,
                            NewCombo       = comboData?.NewCombo ?? false,
                            ComboOffset    = comboData?.ComboOffset ?? 0,
                            IndexInBeatmap = index,
                            IsKiai         = isKiai
                        });
                    }

                    hitObjects.Add(new SoundHitObject
                    {
                        StartTime = e.Time,
                        Samples   = getTickSamples(obj.Samples)
                    });
                    break;

                case SliderEventType.Repeat:

                    if (positionIsValid(sliderEventPosition))
                    {
                        hitObjects.AddRange(generateTriangularExplosion(
                                                e.Time,
                                                20,
                                                sliderEventPosition,
                                                comboData,
                                                isKiai,
                                                index,
                                                MathExtensions.GetRandomTimedAngleOffset(e.Time)));
                    }

                    hitObjects.Add(new SoundHitObject
                    {
                        StartTime = e.Time,
                        Samples   = obj.Samples
                    });
                    break;

                case SliderEventType.Tail:

                    if (positionIsValid(sliderEventPosition))
                    {
                        hitObjects.AddRange(generateExplosion(
                                                e.Time,
                                                Math.Clamp((int)curve.Distance / 15, 5, 20),
                                                sliderEventPosition,
                                                comboData,
                                                isKiai,
                                                index));
                    }

                    hitObjects.Add(new SoundHitObject
                    {
                        StartTime = curve.EndTime,
                        Samples   = obj.Samples
                    });
                    break;
                }
            }

            hitObjects.AddRange(generateSliderBody(obj, curve, isKiai, index));

            return(hitObjects);
        }