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); }
/// <summary> /// Returns the path progress from specified time progress which interpolates between start and end time. /// </summary> public static float GetProgress(this IHasCurve context, float progress) { float p = progress * context.SpanCount() % 1; // If is a repeat and is reversing back, invert progress towards end to start. if(context.GetSpan(progress) % 2 == 1) return 1 - p; return p; }
/// <summary> /// Computes the progress along the curve relative to how much of the <see cref="HitObject"/> has been completed. /// </summary> /// <param name="obj">The curve.</param> /// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param> /// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns> public static double ProgressAt(this IHasCurve obj, double progress) { var p = progress * obj.SpanCount() % 1; if (obj.SpanAt(progress) % 2 == 1) { p = 1 - p; } return(p); }
/// <summary> /// this method does not in use /// </summary> /// <param name="original"></param> /// <param name="beatmap"></param> /// <returns></returns> protected override IEnumerable <BaseRpObject> ConvertHitObject(HitObject original, Beatmap beatmap) { IHasCurve curveData = original as IHasCurve; IHasEndTime endTimeData = original as IHasEndTime; IHasPosition positionData = original as IHasPosition; IHasCombo comboData = original as IHasCombo; /* * if (curveData != null) * { * yield return new RpSlider * { * StartTime = original.StartTime, * Samples = original.Samples, * CurveObject = curveData, * Position = positionData?.Position ?? Vector2.Zero, * NewCombo = comboData?.NewCombo ?? false * }; * } * else if (endTimeData != null) * { * yield return new Spinner * { * StartTime = original.StartTime, * Samples = original.Samples, * EndTime = endTimeData.EndTime, * * Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2, * }; * } * else * { * yield return new HitCircle * { * StartTime = original.StartTime, * Samples = original.Samples, * Position = positionData?.Position ?? Vector2.Zero, * NewCombo = comboData?.NewCombo ?? false * }; * } */ yield return((BaseRpObject)original); }
private OsuHitObject convertHitObject(HitObject original) { IHasCurve curveData = original as IHasCurve; IHasEndTime endTimeData = original as IHasEndTime; IHasPosition positionData = original as IHasPosition; IHasCombo comboData = original as IHasCombo; if (curveData != null) { return(new Slider { StartTime = original.StartTime, Sample = original.Sample, CurveObject = curveData, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false }); } if (endTimeData != null) { return(new Spinner { StartTime = original.StartTime, Sample = original.Sample, Position = new Vector2(512, 384) / 2, EndTime = endTimeData.EndTime }); } return(new HitCircle { StartTime = original.StartTime, Sample = original.Sample, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false }); }
/// <summary> /// Determines which span of the curve the progress point is on. /// </summary> /// <param name="obj">The curve.</param> /// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param> /// <returns>[0, SpanCount) where 0 is the first run.</returns> public static int SpanAt(this IHasCurve obj, double progress) => (int)(progress * obj.SpanCount());
/// <summary> /// Computes the position on the curve relative to how much of the <see cref="HitObject"/> has been completed. /// </summary> /// <param name="obj">The curve.</param> /// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param> /// <returns>The position on the curve.</returns> public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) => obj.Path.PositionAt(obj.ProgressAt(progress));
protected override IEnumerable <HitObject> ConvertHitObjects(BaseHitObject hitObject) { IHasCurve curve = hitObject as IHasCurve; IHasPositionX posX = hitObject as IHasPositionX; IHasEndTime endTime = hitObject as IHasEndTime; IHasCombo combo = hitObject as IHasCombo; if (curve != null) { // Regenerate path using conversion method. SliderPath newPath = curve.Path; if (pixelDefinition.FromMode != GameModeType.BeatsStandard) { Vector2[] origPoints = newPath.Points; Vector2[] points = new Vector2[origPoints.Length]; for (int i = 0; i < points.Length; i++) { points[i] = origPoints[i]; points[i].x = points[i].x * pixelDefinition.Scale; } // Final path newPath = new SliderPath(newPath.PathType, points, newPath.ExpectedDistance * pixelDefinition.Scale); } yield return(new Dragger() { StartTime = hitObject.StartTime, Samples = hitObject.Samples, X = pixelDefinition.GetX(posX.X), RepeatCount = curve.RepeatCount, IsNewCombo = (combo == null ? false : combo.IsNewCombo), ComboOffset = (combo == null ? 0 : combo.ComboOffset), Path = newPath, NodeSamples = curve.NodeSamples, EndTime = curve.EndTime }); } else if (endTime != null) { yield return(new Dragger() { StartTime = hitObject.StartTime, Samples = new List <SoundInfo>(), X = pixelDefinition.GetX(posX.X), RepeatCount = 0, IsNewCombo = (combo == null ? false : combo.IsNewCombo), ComboOffset = (combo == null ? 0 : combo.ComboOffset), Path = new SliderPath(PathType.Linear, new Vector2[] { new Vector2(0, 0), new Vector2(0, 1) }, null), NodeSamples = new List <List <SoundInfo> >() { new List <SoundInfo>(), hitObject.Samples }, EndTime = endTime.EndTime }); } else { yield return(new HitCircle() { StartTime = hitObject.StartTime, Samples = hitObject.Samples, X = pixelDefinition.GetX(posX.X), IsNewCombo = (combo == null ? false : combo.IsNewCombo), ComboOffset = (combo == null ? 0 : combo.ComboOffset) }); } }
private void addCurveData(TextWriter writer, IHasCurve curveData, IHasPosition positionData) { PathType?lastType = null; for (int i = 0; i < curveData.Path.ControlPoints.Count; i++) { PathControlPoint point = curveData.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($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}|")); } } if (i != 0) { writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}")); writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ","); } } writer.Write(FormattableString.Invariant($"{curveData.RepeatCount + 1},")); writer.Write(FormattableString.Invariant($"{curveData.Path.Distance},")); 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 ? "|" : ","); } }
/// <summary> /// Returns the path span index at specified path progress. /// </summary> public static int GetSpan(this IHasCurve context, float progress) { return (int)(progress * context.SpanCount()); }
/// <summary> /// Returns the position on the path at specified time progress. /// </summary> public static Vector2 GetPosition(this IHasCurve context, float progress) { return context.Path.GetPosition(context.GetProgress(progress)); }
public static List <TouhouHitObject> ConvertSlider(HitObject obj, IBeatmap beatmap, IHasCurve curve, bool isKiai, int index) { double spanDuration = curve.Duration / (curve.RepeatCount + 1); bool isRepeatSpam = spanDuration < 75 && curve.RepeatCount > 0; if (isRepeatSpam) { return(generateRepeatSpamSlider(obj, beatmap, curve, spanDuration, isKiai, index)); } else { return(generateDefaultSlider(obj, beatmap, curve, spanDuration, isKiai, index)); } }
private static List <TouhouHitObject> generateRepeatSpamSlider(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 = objPosition * new Vector2(1, 0.5f); switch (e.Type) { case SliderEventType.Head: if (positionIsValid(sliderEventPosition)) { hitObjects.AddRange(generateExplosion( e.Time, bullets_per_slider_reverse, sliderEventPosition, comboData, isKiai, index)); } hitObjects.Add(new SoundHitObject { StartTime = obj.StartTime, Samples = obj.Samples }); break; case SliderEventType.Repeat: if (positionIsValid(sliderEventPosition)) { hitObjects.AddRange(generateExplosion( e.Time, bullets_per_slider_reverse, sliderEventPosition, comboData, isKiai, index, slider_angle_per_span * (e.SpanIndex + 1))); } hitObjects.Add(new SoundHitObject { StartTime = e.Time, Samples = obj.Samples }); break; case SliderEventType.Tail: if (positionIsValid(sliderEventPosition)) { hitObjects.AddRange(generateExplosion( e.Time, bullets_per_slider_reverse, sliderEventPosition, comboData, isKiai, index, slider_angle_per_span * (curve.RepeatCount + 1))); } hitObjects.Add(new SoundHitObject { StartTime = curve.EndTime, Samples = obj.Samples }); break; } } hitObjects.AddRange(generateSliderBody(obj, curve, isKiai, index)); 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); }