public static AlmostEquals ( this a, double b, double epsilon = DOUBLE_EPSILON ) : bool | ||
a | this | |
b | double | |
epsilon | double | |
Résultat | bool |
public void TestContainerAutoSizeUpdatesWhenChildBecomesAlive() { Box box = null; Container parent = null; AddStep("create test", () => { Child = parent = new Container { RemoveCompletedTransforms = false, AutoSizeAxes = Axes.Both, Child = box = new Box { Size = new Vector2(200), LifetimeStart = double.MaxValue } }; }); AddStep("make child alive", () => box.LifetimeStart = double.MinValue); AddAssert("parent has size 200", () => Precision.AlmostEquals(new Vector2(200), parent.DrawSize)); }
public void TestContentAnchors() { AddStep("Create scroll container with centre-left content", () => { Add(scrollContainer = new BasicScrollContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(300), ScrollContent = { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, Child = new Box { Size = new Vector2(300, 400) } }); }); AddStep("Scroll to 0", () => scrollContainer.ScrollTo(0, false)); AddAssert("Content position at top", () => Precision.AlmostEquals(scrollContainer.ScreenSpaceDrawQuad.TopLeft, scrollContainer.ScrollContent.ScreenSpaceDrawQuad.TopLeft)); }
[TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] // (0 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; IBeatmap fourObjectBeatmap = new TestBeatmap(new RulesetInfo()) { HitObjects = new List <HitObject>(Enumerable.Repeat(new TestHitObject(maxResult), 4)) }; scoreProcessor.Mode.Value = scoringMode; scoreProcessor.ApplyBeatmap(fourObjectBeatmap); for (int i = 0; i < 4; i++) { var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], new Judgement()) { Type = i == 2 ? minResult : hitResult }; scoreProcessor.ApplyResult(judgementResult); } Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); }
public void TestSpinRateUnaffectedByMods(Type additionalModType) { var mods = new List <Mod> { new OsuModSpunOut() }; if (additionalModType != null) { mods.Add((Mod)Activator.CreateInstance(additionalModType)); } CreateModTest(new ModTestData { Mods = mods, Autoplay = false, Beatmap = singleSpinnerBeatmap, PassCondition = () => { var counter = Player.ChildrenOfType <SpinnerSpmCounter>().SingleOrDefault(); return(counter != null && Precision.AlmostEquals(counter.SpinsPerMinute, 286, 1)); } }); }
public void TestAutoSizeDoesNotConsiderRelativeSizeChildren(bool row) { Box relativeBox = null; Box absoluteBox = null; setSingleDimensionContent(() => new[] { new Drawable[] { relativeBox = new FillBox { RelativeSizeAxes = Axes.Both }, absoluteBox = new FillBox { RelativeSizeAxes = Axes.None, Size = new Vector2(100) } } }, new[] { new Dimension(GridSizeMode.AutoSize) }, row); AddStep("resize absolute box", () => absoluteBox.Size = new Vector2(50)); AddAssert("relative box has length 50", () => Precision.AlmostEquals(row ? relativeBox.DrawHeight : relativeBox.DrawWidth, 50, 1)); }
public void Test3CellRowOrColumnDistributedXy(bool row) { FillBox[] boxes = new FillBox[3]; setSingleDimensionContent(() => new[] { new Drawable[] { boxes[0] = new FillBox(), boxes[1] = new FillBox(), boxes[2] = new FillBox() } }, row: row); for (int i = 0; i < 3; i++) { int local = i; if (row) { AddAssert($"box {local} has correct size", () => Precision.AlmostEquals(boxes[local].DrawSize, new Vector2(grid.DrawWidth / 3f, grid.DrawHeight))); } else { AddAssert($"box {local} has correct size", () => Precision.AlmostEquals(boxes[local].DrawSize, new Vector2(grid.DrawWidth, grid.DrawHeight / 3f))); } } }
public void TestEnableAndDisablePassword() { DrawableRoom drawableRoom = null; Room room = null; AddStep("create room", () => Child = drawableRoom = new DrawableRoom(room = new Room { Name = { Value = "Room with password" }, Status = { Value = new RoomStatusOpen() }, Category = { Value = RoomCategory.Realtime }, }) { MatchingFilter = true }); AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType <DrawableRoom.PasswordProtectedIcon>().Single().Alpha)); AddStep("set password", () => room.Password.Value = "password"); AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType <DrawableRoom.PasswordProtectedIcon>().Single().Alpha)); AddStep("unset password", () => room.Password.Value = string.Empty); AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType <DrawableRoom.PasswordProtectedIcon>().Single().Alpha)); }
protected override void Update() { base.Update(); var elapsedFrameTime = Clock.ElapsedFrameTime; if (Math.Abs(verticalSpeed) > max_vertical_speed) { verticalSpeed = Math.Sign(verticalSpeed) * max_vertical_speed; } if (Precision.AlmostEquals(verticalSpeed, 0, 0.0001)) { verticalSpeed = 0; } var adjustedVerticalDistance = verticalSpeed * (elapsedFrameTime / 20); Y -= (float)adjustedVerticalDistance; X += (float)(speedVector.X * (elapsedFrameTime / 20)); verticalSpeed -= gravity * (elapsedFrameTime / 20); }
public void TestBypassAutoSizeAxes() { const float autosize_height = 300; Container autoSizeContainer = null; Box boxSizeReference = null; AddStep("init", () => { Child = autoSizeContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 300, AutoSizeAxes = Axes.Y, Children = new Drawable[] { new Box { Colour = Color4.Green, RelativeSizeAxes = Axes.Both }, boxSizeReference = new Box { RelativeSizeAxes = Axes.X, Height = autosize_height, Colour = Color4.Red.Opacity(0.2f), } } }; }); AddAssert($"height = {autosize_height}", () => Precision.AlmostEquals(autosize_height, autoSizeContainer.DrawHeight)); AddStep("bypass y", () => boxSizeReference.BypassAutoSizeAxes = Axes.Y); AddAssert("height = 0", () => Precision.AlmostEquals(0, autoSizeContainer.DrawHeight)); AddStep("bypass none", () => boxSizeReference.BypassAutoSizeAxes = Axes.None); AddAssert($"height = {autosize_height}", () => Precision.AlmostEquals(autosize_height, autoSizeContainer.DrawHeight)); }
public void TestRowSize() { AddStep("set content", () => { table.Content = createContent(2, 2); table.RowSize = new Dimension(GridSizeMode.Absolute, 30f); }); AddAssert("all row size = 30", () => testRows(30)); AddStep("add headers", () => table.Columns = new[] { new TableColumn("Header 1"), new TableColumn("Header 2"), new TableColumn("Header 3"), }); AddAssert("all row size = 30", () => testRows(30)); AddStep("change row size", () => table.RowSize = new Dimension(GridSizeMode.Absolute, 50)); AddAssert("all row size = 50", () => testRows(50)); AddStep("change content", () => table.Content = createContent(4, 4)); AddAssert("all row size = 50", () => testRows(50)); AddStep("remove custom row size", () => table.RowSize = null); AddAssert("all row size = distributed", () => testRows(table.DrawHeight / 5f)); bool testRows(float expectedHeight) { for (int row = 0; row < getGrid().Content.Count; row++) { if (!Precision.AlmostEquals(expectedHeight, getGrid().Content[row][0].Parent.DrawHeight)) { return(false); } } return(true); } }
public void TestSeekPerformsInGameplayTime( [Values(1.0, 0.5, 2.0)] double clockRate, [Values(0.0, 200.0, -200.0)] double userOffset, [Values(false, true)] bool whileStopped, [Values(false, true)] bool setAudioOffsetBeforeConstruction) { ClockBackedTestWorkingBeatmap working = null; GameplayClockContainer gameplayClockContainer = null; if (setAudioOffsetBeforeConstruction) { AddStep($"preset audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); } AddStep("create container", () => { working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); working.LoadTrack(); Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); gameplayClockContainer.Reset(startClock: !whileStopped); }); AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); if (!setAudioOffsetBeforeConstruction) { AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); } AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500)); AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f)); AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000)); AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f)); }
public void TestShowHideStatistics() { TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); AddUntilStep("wait for load", () => this.ChildrenOfType <ScorePanelList>().Single().AllPanelsVisible); AddStep("click expanded panel", () => { var expandedPanel = this.ChildrenOfType <ScorePanel>().Single(p => p.State == PanelState.Expanded); InputManager.MoveMouseTo(expandedPanel); InputManager.Click(MouseButton.Left); }); AddAssert("statistics shown", () => this.ChildrenOfType <StatisticsPanel>().Single().State.Value == Visibility.Visible); AddUntilStep("expanded panel at the left of the screen", () => { var expandedPanel = this.ChildrenOfType <ScorePanel>().Single(p => p.State == PanelState.Expanded); return(expandedPanel.ScreenSpaceDrawQuad.TopLeft.X - screen.ScreenSpaceDrawQuad.TopLeft.X < 150); }); AddStep("click expanded panel", () => { var expandedPanel = this.ChildrenOfType <ScorePanel>().Single(p => p.State == PanelState.Expanded); InputManager.MoveMouseTo(expandedPanel); InputManager.Click(MouseButton.Left); }); AddAssert("statistics hidden", () => this.ChildrenOfType <StatisticsPanel>().Single().State.Value == Visibility.Hidden); AddUntilStep("expanded panel in centre of screen", () => { var expandedPanel = this.ChildrenOfType <ScorePanel>().Single(p => p.State == PanelState.Expanded); return(Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, screen.ScreenSpaceDrawQuad.Centre.X, 1)); }); }
/// <summary> /// Start a sequence of <see cref="Transform"/>s with a (cumulative) relative delay applied. /// </summary> /// <param name="delay">The offset in milliseconds from current time. Note that this stacks with other nested sequences.</param> /// <param name="recursive">Whether this should be applied to all children. True by default.</param> /// <returns>An <see cref="InvokeOnDisposal"/> to be used in a using() statement.</returns> public IDisposable BeginDelayedSequence(double delay, bool recursive = true) { EnsureTransformMutationAllowed(); if (delay == 0) { return(null); } AddDelay(delay, recursive); double newTransformDelay = TransformDelay; return(new ValueInvokeOnDisposal <DelayedSequenceSender>(new DelayedSequenceSender(this, delay, recursive, newTransformDelay), sender => { if (!Precision.AlmostEquals(sender.NewTransformDelay, sender.Transformable.TransformDelay)) { throw new InvalidOperationException( $"{nameof(sender.Transformable.TransformStartTime)} at the end of delayed sequence is not the same as at the beginning, but should be. " + $"(begin={sender.NewTransformDelay} end={sender.Transformable.TransformDelay})"); } AddDelay(-sender.Delay, sender.Recursive); })); }
public void ScaleSequence() { boxTest(box => { box.Scale = Vector2.One; box.ScaleTo(0.75f, interval).Then() .ScaleTo(0.5f, interval).Then() .ScaleTo(0.25f, interval).Then() .ScaleTo(0, interval); }); int i = 0; checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.75f)); checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.5f)); checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.25f)); checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0f)); checkAtTime(interval * (i -= 2), box => Precision.AlmostEquals(box.Scale.X, 0.5f)); checkAtTime(interval * --i, box => Precision.AlmostEquals(box.Scale.X, 0.75f)); AddAssert("check transform count", () => box.Transforms.Count == 4); }
public void TestSpinnerMiddleRewindingRotation() { double finalAbsoluteDiscRotation = 0, finalRelativeDiscRotation = 0, finalSpinnerSymbolRotation = 0; addSeekStep(5000); AddStep("retrieve disc relative rotation", () => finalRelativeDiscRotation = drawableSpinner.Disc.Rotation); AddStep("retrieve disc absolute rotation", () => finalAbsoluteDiscRotation = drawableSpinner.Disc.CumulativeRotation); AddStep("retrieve spinner symbol rotation", () => finalSpinnerSymbolRotation = spinnerSymbol.Rotation); addSeekStep(2500); AddUntilStep("disc rotation rewound", // we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in. () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, finalRelativeDiscRotation / 2, 100)); AddUntilStep("symbol rotation rewound", () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, 100)); addSeekStep(5000); AddAssert("is disc rotation almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, finalRelativeDiscRotation, 100)); AddAssert("is symbol rotation almost same", () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, 100)); AddAssert("is disc rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, finalAbsoluteDiscRotation, 100)); }
public OsuTKJoystickState(JoystickDevice device) { // Populate axes for (int i = 0; i < JoystickDevice.MAX_AXES; i++) { var value = device.RawState.GetAxis(i); if (!Precision.AlmostEquals(value, 0, device.DefaultDeadzones?[i] ?? Precision.FLOAT_EPSILON)) { Axes.Add(new JoystickAxis(i, value)); } } // Populate normal buttons for (int i = 0; i < JoystickDevice.MAX_BUTTONS; i++) { if (device.RawState.GetButton(i) == ButtonState.Pressed) { Buttons.SetPressed(JoystickButton.FirstButton + i, true); } } // Populate hat buttons for (int i = 0; i < JoystickDevice.MAX_HATS; i++) { foreach (var hatButton in getHatButtons(device, i)) { Buttons.SetPressed(hatButton, true); } } // Populate axis buttons (each axis has two buttons) foreach (var axis in Axes) { Buttons.SetPressed((axis.Value < 0 ? JoystickButton.FirstAxisNegative : JoystickButton.FirstAxisPositive) + axis.Axis, true); } }
protected override void Update() { base.Update(); // position updates should not occur if the item is filtered away. // this avoids panels flying across the screen only to be eventually off-screen or faded out. if (!Item.Visible) { return; } float targetY = Item.CarouselYPosition; if (Precision.AlmostEquals(targetY, Y)) { Y = targetY; } else { // algorithm for this is taken from ScrollContainer. // while it doesn't necessarily need to match 1:1, as we are emulating scroll in some cases this feels most correct. Y = (float)Interpolation.Lerp(targetY, Y, Math.Exp(-0.01 * Time.Elapsed)); } }
private void assertHue(float hue, float tolerance = 0.005f) { AddAssert($"hue selector has {hue}", () => Precision.AlmostEquals(colourPicker.HueControl.Hue.Value, hue, tolerance)); AddAssert($"saturation/value selector has {hue}", () => Precision.AlmostEquals(colourPicker.SaturationValueControl.Hue.Value, hue, tolerance)); }
private void assertSaturationAndValue(float saturation, float value, float tolerance = 0.005f) { AddAssert($"saturation is {saturation}", () => Precision.AlmostEquals(colourPicker.SaturationValueControl.Saturation.Value, saturation, tolerance)); AddAssert($"value is {value}", () => Precision.AlmostEquals(colourPicker.SaturationValueControl.Value.Value, value, tolerance)); }
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => { Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position; return(Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position))); });
/// <summary> /// Checks if a cursor is at the current inputmanager screen position. /// </summary> /// <param name="cursorContainer">The cursor to check.</param> private bool checkAtMouse(CursorContainer cursorContainer) => Precision.AlmostEquals(InputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition));
private void pressAndCheckTime(Key key, double expectedTime) { AddStep($"press {key}", () => InputManager.Key(key)); AddUntilStep($"time is {expectedTime}", () => Precision.AlmostEquals(expectedTime, EditorClock.CurrentTime, 1)); }
public override HitObject Parse(string text) { try { string[] split = text.Split(','); Vector2 pos = new Vector2((int)Convert.ToSingle(split[0], CultureInfo.InvariantCulture), (int)Convert.ToSingle(split[1], CultureInfo.InvariantCulture)); ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]); int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; type &= ~ConvertHitObjectType.ComboOffset; bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); type &= ~ConvertHitObjectType.NewCombo; var soundType = (LegacySoundType)int.Parse(split[4]); var bankInfo = new SampleBankInfo(); HitObject result = null; if (type.HasFlag(ConvertHitObjectType.Circle)) { result = CreateHit(pos, combo, comboOffset); if (split.Length > 5) { readCustomSampleBanks(split[5], bankInfo); } } else if (type.HasFlag(ConvertHitObjectType.Slider)) { PathType pathType = PathType.Catmull; double length = 0; string[] pointSplit = split[5].Split('|'); int pointCount = 1; foreach (var t in pointSplit) { if (t.Length > 1) { pointCount++; } } var points = new Vector2[pointCount]; int pointIndex = 1; foreach (string t in pointSplit) { if (t.Length == 1) { switch (t) { case @"C": pathType = PathType.Catmull; break; case @"B": pathType = PathType.Bezier; break; case @"L": pathType = PathType.Linear; break; case @"P": pathType = PathType.PerfectCurve; break; } continue; } string[] temp = t.Split(':'); points[pointIndex++] = new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)) - pos; } // osu-stable special-cased colinear perfect curves to a CurveType.Linear bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) { pathType = PathType.Linear; } int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); if (repeatCount > 9000) { throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); } // osu-stable treated the first span of the slider as a repeat, but no repeats are happening repeatCount = Math.Max(0, repeatCount - 1); if (split.Length > 7) { length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); } if (split.Length > 10) { readCustomSampleBanks(split[10], bankInfo); } // One node for each repeat + the start and end nodes int nodes = repeatCount + 2; // Populate node sample bank infos with the default hit object sample bank var nodeBankInfos = new List <SampleBankInfo>(); for (int i = 0; i < nodes; i++) { nodeBankInfos.Add(bankInfo.Clone()); } // Read any per-node sample banks if (split.Length > 9 && split[9].Length > 0) { string[] sets = split[9].Split('|'); for (int i = 0; i < nodes; i++) { if (i >= sets.Length) { break; } SampleBankInfo info = nodeBankInfos[i]; readCustomSampleBanks(sets[i], info); } } // Populate node sound types with the default hit object sound type var nodeSoundTypes = new List <LegacySoundType>(); for (int i = 0; i < nodes; i++) { nodeSoundTypes.Add(soundType); } // Read any per-node sound types if (split.Length > 8 && split[8].Length > 0) { string[] adds = split[8].Split('|'); for (int i = 0; i < nodes; i++) { if (i >= adds.Length) { break; } int sound; int.TryParse(adds[i], out sound); nodeSoundTypes[i] = (LegacySoundType)sound; } } // Generate the final per-node samples var nodeSamples = new List <List <SampleInfo> >(nodes); for (int i = 0; i < nodes; i++) { nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); } result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); // The samples are played when the slider ends, which is the last node result.Samples = nodeSamples[nodeSamples.Count - 1]; } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + Offset); if (split.Length > 6) { readCustomSampleBanks(split[6], bankInfo); } } else if (type.HasFlag(ConvertHitObjectType.Hold)) { // Note: Hold is generated by BMS converts double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) { string[] ss = split[5].Split(':'); endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture); readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } result = CreateHold(pos, combo, comboOffset, endTime + Offset); } if (result == null) { Logger.Log($"Unknown hit object type: {type}. Skipped.", level: LogLevel.Error); return(null); } result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset; if (result.Samples.Count == 0) { result.Samples = convertSoundType(soundType, bankInfo); } FirstObject = false; return(result); } catch (FormatException) { throw new FormatException("One or more hit objects were malformed."); } }
private void check(float ratio) => AddAssert($"Check @{ratio}", () => Precision.AlmostEquals(autoSizeContainer.Size, new Vector2(changed_value * ratio)) && Precision.AlmostEquals(box2.Position, new Vector2(changed_value * (1 - ratio), changed_value * ratio)));
private void addSeekStep(double time) { AddStep($"seek to {time}", () => track.Seek(time)); AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100)); }
private static bool almostEquals(double value1, double value2) { return(Precision.AlmostEquals(value1, value2, timing_precision)); }
public void TestSliderMultiplier(float multiplier, float expectedSpacing) { AddStep($"set speed multiplier = {multiplier}", () => editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); createGrid(); AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); }
private void assertMenuInCentre(Func <Drawable> getBoxFunc) => AddAssert("menu in centre of box", () => Precision.AlmostEquals(contextMenuContainer.CurrentMenu.ScreenSpaceDrawQuad.TopLeft, getBoxFunc().ScreenSpaceDrawQuad.Centre));
private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}", () => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
/// <summary> /// Creates a piecewise-linear approximation of a circular arc curve. /// </summary> /// <returns>A list of vectors representing the piecewise-linear approximation.</returns> public static List <Vector2> ApproximateCircularArc(List <Vector2> controlPoints) { Vector2 a = controlPoints[0]; Vector2 b = controlPoints[1]; Vector2 c = controlPoints[2]; double aSq = (b - c).LengthSquared; double bSq = (a - c).LengthSquared; double cSq = (a - b).LengthSquared; // If we have a degenerate triangle where a side-length is almost zero, then give up and fall // back to a more numerically stable method. if (Precision.AlmostEquals(aSq, 0) || Precision.AlmostEquals(bSq, 0) || Precision.AlmostEquals(cSq, 0)) { return(new List <Vector2>()); } double s = aSq * (bSq + cSq - aSq); double t = bSq * (aSq + cSq - bSq); double u = cSq * (aSq + bSq - cSq); double sum = s + t + u; // If we have a degenerate triangle with an almost-zero size, then give up and fall // back to a more numerically stable method. if (Precision.AlmostEquals(sum, 0)) { return(new List <Vector2>()); } Vector2 centre = (s * a + t * b + u * c) / sum; Vector2 dA = a - centre; Vector2 dC = c - centre; double r = dA.Length; double thetaStart = Math.Atan2(dA.Y, dA.X); double thetaEnd = Math.Atan2(dC.Y, dC.X); while (thetaEnd < thetaStart) { thetaEnd += 2 * Math.PI; } double dir = 1; double thetaRange = thetaEnd - thetaStart; // Decide in which direction to draw the circle, depending on which side of // AC B lies. Vector2 orthoAtoC = c - a; orthoAtoC = new Vector2(orthoAtoC.Y, -orthoAtoC.X); if (Vector2.Dot(orthoAtoC, b - a) < 0) { dir = -dir; thetaRange = 2 * Math.PI - thetaRange; } // We select the amount of points for the approximation by requiring the discrete curvature // to be smaller than the provided tolerance. The exact angle required to meet the tolerance // is: 2 * Math.Acos(1 - TOLERANCE / r) // The special case is required for extremely short sliders where the radius is smaller than // the tolerance. This is a pathological rather than a realistic case. int amountPoints = 2 * r <= circular_arc_tolerance ? 2 : Math.Max(2, (int)Math.Ceiling(thetaRange / (2 * Math.Acos(1 - circular_arc_tolerance / r)))); List <Vector2> output = new List <Vector2>(amountPoints); for (int i = 0; i < amountPoints; ++i) { double fract = (double)i / (amountPoints - 1); double theta = thetaStart + dir * fract * thetaRange; Vector2 o = new Vector2(Math.Cos(theta), Math.Sin(theta)) * r; output.Add(centre + o); } return(output); }