private void AnchorsOnAnchorsChanged(object sender, DependencyPropertyChangedEventArgs e) { if (_ignoreAnchorsChange) { return; } var anchor = (Anchor)sender; // Correct the anchor change if it resulted in a speed limit violation if (PrevOverSpeedLimit(anchor) || NextOverSpeedLimit(anchor)) { _ignoreAnchorsChange = true; Graph.IgnoreAnchorUpdates = true; // Use binary search to find the closest value to the limit const double d = 0.001; switch (e.NewValue) { case double newDouble: var oldDouble = (double)e.OldValue; // Test if the old value is also a illegal speed violation anchor.SetValue(e.Property, oldDouble); if (PrevOverSpeedLimit(anchor) || NextOverSpeedLimit(anchor)) { anchor.SetValue(e.Property, newDouble); break; } anchor.SetValue(e.Property, BinarySearchUtil.DoubleBinarySearch( oldDouble, newDouble, d, mid => { anchor.SetValue(e.Property, mid); return(!PrevOverSpeedLimit(anchor) && !NextOverSpeedLimit(anchor)); })); break; case Vector2 newVector2: if (ViewModel.GraphModeSetting == SlideratorVm.GraphMode.Position && anchor.PreviousAnchor != null) { // List of bounds. X represents the minimum Y value and Y represents the maximum Y value // I use Vector2 here because it has useful math methods var bounds = new List <Vector2>(); if (anchor.PreviousAnchor != null) { var maxSpeed = InterpolatorHelper.GetBiggestDerivative(anchor.Interpolator); if (Math.Abs(newVector2.X - anchor.PreviousAnchor.Pos.X) < Precision.DOUBLE_EPSILON) { bounds.Add(new Vector2(anchor.PreviousAnchor.Pos.Y)); } else { bounds.Add(new Vector2(anchor.PreviousAnchor.Pos.Y) + new Vector2(Precision.DOUBLE_EPSILON).PerpendicularRight + new Vector2(ViewModel.VelocityLimit * ViewModel.SvGraphMultiplier * (newVector2.X - anchor.PreviousAnchor.Pos.X) / maxSpeed) .PerpendicularLeft); } } if (anchor.NextAnchor != null) { var maxSpeed = InterpolatorHelper.GetBiggestDerivative(anchor.NextAnchor.Interpolator); if (Math.Abs(newVector2.X - anchor.NextAnchor.Pos.X) < Precision.DOUBLE_EPSILON) { bounds.Add(new Vector2(anchor.NextAnchor.Pos.Y)); } else { bounds.Add(new Vector2(anchor.NextAnchor.Pos.Y) + new Vector2(Precision.DOUBLE_EPSILON).PerpendicularRight + new Vector2(ViewModel.VelocityLimit * ViewModel.SvGraphMultiplier * (newVector2.X - anchor.NextAnchor.Pos.X) / maxSpeed) .PerpendicularRight); } } // Clamp the new Y value between all the bounds var newY = bounds.Aggregate(newVector2.Y, (current, bound) => MathHelper.Clamp(current, bound.X, bound.Y)); // Break if the resulting value is not inside all the bounds if (!bounds.All(b => newY >= b.X && newY <= b.Y)) { break; } anchor.SetValue(e.Property, new Vector2(newVector2.X, newY)); } break; } _ignoreAnchorsChange = false; Graph.IgnoreAnchorUpdates = false; } if (ViewModel.PixelLength < HitObjectElement.MaxPixelLength) { AnimateProgress(GraphHitObjectElement); } UpdatePointsOfInterest(); UpdateVelocity(); }
private void GenerateDendrites() { double leftovers = 0; //double totalDendriteToAddLength = 0; foreach (var neuron in _slider.Where(n => n.Terminal != null)) { // Find angles for the neuron and the terminal to point the dendrites towards var dir = Math.Sign(neuron.Terminal.Nucleus.PathPosition - neuron.Nucleus.PathPosition); dir = dir == 0 ? 1 : dir; // Let dir not be zero var dendriteDir1 = dir * NearbyNonZeroDiff(neuron.Nucleus.SegmentIndex).Normalized(); var dendriteDir2 = -dir *NearbyNonZeroDiff(neuron.Terminal.Nucleus.SegmentIndex).Normalized(); // Do an even split of dendrites between this neuron and the terminal var dendriteToAdd = neuron.DendriteLength + leftovers; // Find the time at which the position function goes in between the neuron and the terminal var width = neuron.Terminal.Time - neuron.Time; var axonWidth = neuron.AxonLenth / Velocity; var middleTime = BinarySearchUtil.DoubleBinarySearch(neuron.Time, neuron.Terminal.Time, 0.01, d => PositionFunction(d) <= (neuron.Nucleus.PathPosition + neuron.Terminal.Nucleus.PathPosition) / 2); // Calculate the distribution of dendrites to let the axon pass through the middle at the same time as the position funciton does var leftPortion = MathHelper.Clamp((2 * (middleTime - neuron.Time) - axonWidth) / (2 * (width - axonWidth)), 0, 1); var rightPortion = 1 - leftPortion; var dendriteToAddLeft = dendriteToAdd * leftPortion; var dendriteToAddRight = dendriteToAdd * rightPortion; //totalDendriteToAddLength += dendriteToAddLeft; //totalDendriteToAddLength += dendriteToAddRight; // Get the speeds at the times of the dendrites to give the dendrites appriopriate lengths to the speed at the time var speedLeft = GetSpeedAtTime(neuron.Time + dendriteToAddLeft / Velocity / 2, 0.01); var speedRight = GetSpeedAtTime(neuron.Terminal.Time - dendriteToAddRight / Velocity / 2, 0.01); //Console.WriteLine($@"Adding {dendriteToAddLeft + dendriteToAddRight} length to dendrites!"); //var beforeLength = neuron.Dendrites.Sum(d => d.Length) + neuron.Terminal.Dendrites.Sum(d => d.Length); dendriteToAddRight += AddDendriteLength(neuron, dendriteToAddLeft, dendriteDir1, MinDendriteLength, 4 * Math.Pow(speedLeft * 2, 2)); leftovers = AddDendriteLength(neuron.Terminal, dendriteToAddRight, dendriteDir2, MinDendriteLength, 4 * Math.Pow(speedRight * 2, 2)); //var afterLength = neuron.Dendrites.Sum(d => d.Length) + neuron.Terminal.Dendrites.Sum(d => d.Length); //Console.WriteLine($@"Actually added {afterLength - beforeLength} length to dendrites and got {leftovers} leftover!"); } /*double actualLength = 0; * double wantedLength = 0; * double axonLength = 0; * double dendriteLength = 0; * foreach (var n in _slider) { * actualLength += n.AxonLenth + n.Dendrites.Sum(d => d.Length); * wantedLength += n.WantedLength; * axonLength += n.AxonLenth; * dendriteLength += n.DendriteLength; * } * Console.WriteLine("Path length: " + actualLength); * Console.WriteLine("Wanted length: " + wantedLength); * Console.WriteLine("Axon length: " + axonLength); * Console.WriteLine("Dendrite length: " + dendriteLength); * Console.WriteLine("DendriteToAdd length: " + totalDendriteToAddLength);*/ }