protected internal override void Save(XmlWriter writer) { writer.WriteStartElement("line"); writer.WriteAttributeString("start", ManeuveringBoard.FormatXmlVector(Start)); writer.WriteAttributeString("end", ManeuveringBoard.FormatXmlVector(End)); writer.WriteEndElement(); }
internal string GetStatusText() { Vector2 vector = Vector; return("Line: " + ManeuveringBoard.GetAngleString(ManeuveringBoard.SwapBearing(vector.Angle)) + ", " + Board.GetDistanceString(vector.Length)); }
public InterceptForm(UnitShape unit, UnitShape target, UnitSystem unitSystem, bool disableControls) : this() { if (target != null) { if (unit != null) { txtBearing.Text = ManeuveringBoard.GetAngleString(ManeuveringBoard.AngleBetween(unit.Position, target.Position)); txtRange.Text = ManeuveringBoard.GetDistanceString((target.Position - unit.Position).Length, unitSystem); txtBearing.Enabled = txtRange.Enabled = !disableControls; txtSpeed.Select(); } txtCourse.Text = ManeuveringBoard.GetAngleString(target.Direction); txtTargetSpeed.Text = ManeuveringBoard.GetSpeedString(target.Speed, unitSystem); txtCourse.Enabled = txtTargetSpeed.Enabled = !disableControls; } if (unit == null) { radVector.Enabled = radWaypoint.Enabled = btnOK.Enabled = false; } this.unit = unit; this.target = target; this.unitSystem = unitSystem; UpdateSolution(); }
protected internal override void Save(XmlWriter writer) { writer.WriteStartElement("waypoint"); writer.WriteAttributeString("position", ManeuveringBoard.FormatXmlVector(Position)); base.Save(writer); writer.WriteEndElement(); }
protected internal override void Save(XmlWriter writer) { writer.WriteStartElement("unit"); writer.WriteAttributeString("id", GetXmlId()); if (!string.IsNullOrEmpty(Name)) { writer.WriteAttributeString("name", Name); } writer.WriteAttributeString("position", ManeuveringBoard.FormatXmlVector(Position)); writer.WriteAttribute("course", Direction); writer.WriteAttribute("speed", Speed); string typeString = Type.ToString(); writer.WriteAttributeString("type", typeString.Substring(0, 1).ToLowerInvariant() + typeString.Substring(1)); if (TMASolution != null) { TMASolution.Save(writer); } if (Children.Count != 0) { writer.WriteStartElement("children"); foreach (Shape shape in Children) { shape.Save(writer); } writer.WriteEndElement(); } writer.WriteEndElement(); }
protected internal override void Save(XmlWriter writer) { writer.WriteStartElement("circle"); writer.WriteAttributeString("position", ManeuveringBoard.FormatXmlVector(Position)); writer.WriteAttribute("radius", Radius); writer.WriteEndElement(); }
public static LineShape Load(XmlReader reader) { LineShape line = new LineShape(); line.Start = ManeuveringBoard.ParseXmlPoint(reader.GetStringAttribute("start")); line.End = ManeuveringBoard.ParseXmlPoint(reader.GetStringAttribute("end")); return(line); }
public override void Update(Shape shape, BoardPoint dragStart, BoardPoint dragPoint) { UnitShape unit = (UnitShape)shape; Vector2 vector = dragPoint - dragStart; unit.Direction = ManeuveringBoard.SwapBearing(vector.Angle); unit.Speed = vector.Length * (1.0 / ManeuveringBoard.VectorTime); }
public static CircleShape Load(XmlReader reader) { CircleShape circle = new CircleShape(); circle.Position = ManeuveringBoard.ParseXmlPoint(reader.GetStringAttribute("position")); circle.Radius = reader.GetDoubleAttribute("radius"); return(circle); }
internal void SetBoard(ManeuveringBoard board) { Board = board; foreach (Shape descendant in EnumerateDescendants()) { descendant.Board = board; } }
public static Waypoint Load(XmlReader reader) { Waypoint shape = new Waypoint(); LoadPositionalData(shape, reader); shape.Position = ManeuveringBoard.ParseXmlPoint(reader.GetStringAttribute("position")); return(shape); }
public BackgroundScaleForm(double pixels, double meters, UnitSystem unitSystem) : this() { lblPixels.Text = pixels.ToString("0.##") + " pixels represents..."; txtLength.Text = ManeuveringBoard.GetDistanceString(meters, unitSystem); txtLength.Tag = meters; txtLength.WasChanged = false; this.unitSystem = unitSystem; }
public static PointObservation Load(XmlReader reader, Dictionary <Observation, string> observers) { PointObservation shape = new PointObservation(); LoadPositionalData(shape, reader); shape.Position = ManeuveringBoard.ParseXmlPoint(reader.GetStringAttribute("position")); observers.Add(shape, reader.GetAttribute("observer")); return(shape); }
void FillRelativeTo(ChangeTrackingTextBox txtBearing, ChangeTrackingTextBox txtDistance, Point2 relativePoint) { double angle = ManeuveringBoard.AngleBetween(relativePoint, posDataPoint.Value); txtBearing.Text = (angle * MathConst.RadiansToDegrees).ToString("0.##"); txtBearing.Tag = angle; double distance = relativePoint.DistanceTo(posDataPoint.Value); txtDistance.Text = ManeuveringBoard.GetDistanceString(distance, unitSystem); txtDistance.Tag = distance; txtBearing.WasChanged = txtDistance.WasChanged = false; }
public BoardOptionsForm(ManeuveringBoard board) : this() { chkShowAllObservations.Checked = board.ShowAllObservations; cmbUnitSystem.SelectedIndex = (int)board.UnitSystem; btnBackground.BackColor = board.BackColor; btnObservations.BackColor = board.ObservationColor; btnReference.BackColor = board.ReferenceColor; btnScale1.BackColor = board.ScaleColor1; btnScale2.BackColor = board.ScaleColor2; btnSelected.BackColor = board.SelectedColor; btnTMA.BackColor = board.TMAColor; btnUnselected.BackColor = board.UnselectedColor; }
protected static bool TryParseLength(string text, UnitSystem unitSystem, out double meters) { Match m = lengthRe.Match(text); if (!m.Success || !double.TryParse(m.Groups["number"].Value, out meters)) { meters = 0; return(false); } else { LengthUnit unit; switch (m.Groups["unit"].Value.ToLowerInvariant()) { case "ft": unit = LengthUnit.Foot; break; case "km": unit = LengthUnit.Kilometer; break; case "kyd": unit = LengthUnit.Kiloyard; break; case "m": unit = LengthUnit.Meter; break; case "mi": unit = LengthUnit.Mile; break; case "nm": case "nmi": unit = LengthUnit.NauticalMile; break; case "yd": unit = LengthUnit.Yard; break; default: switch (unitSystem) { case UnitSystem.Imperial: unit = LengthUnit.Mile; break; case UnitSystem.Metric: unit = LengthUnit.Kilometer; break; case UnitSystem.NauticalImperial: case UnitSystem.NauticalMetric: unit = LengthUnit.NauticalMile; break; default: unit = LengthUnit.Meter; break; } break; } meters = ManeuveringBoard.ConvertFromUnit(meters, unit); return(true); } }
public static new UnitShape Load(XmlReader reader, Dictionary <Observation, string> observers, Dictionary <string, UnitShape> unitsById) { UnitShape shape = new UnitShape(); shape.Name = reader.GetAttribute("name"); shape.Position = ManeuveringBoard.ParseXmlPoint(reader.GetStringAttribute("position")); shape.Direction = reader.GetDoubleAttribute("course"); shape.Speed = reader.GetDoubleAttribute("speed"); shape.Type = Utility.ParseEnum <UnitShapeType>(reader.GetStringAttribute("type", "unknown"), true); string id = reader.GetAttribute("id"); if (!string.IsNullOrEmpty(id)) { unitsById.Add(id, shape); } if (!reader.IsEmptyElement) { reader.Read(); while (reader.NodeType == XmlNodeType.Element) { if (reader.LocalName == "tmaSolution") { shape.TMASolution = TMASolution.Load(reader); } else if (reader.LocalName == "children" && !reader.IsEmptyElement) { reader.Read(); // move to either the first child or the end element while (reader.NodeType == XmlNodeType.Element) { shape.Children.Add(Shape.Load(reader, observers, unitsById)); } reader.ReadEndElement(); } else { throw new InvalidDataException("Expected element " + reader.LocalName); } } } return(shape); }
protected static bool TryParseSpeed(string text, UnitSystem unitSystem, out double metersPerSecond) { Match m = speedRe.Match(text); if (!m.Success || !double.TryParse(m.Groups["number"].Value, out metersPerSecond)) { metersPerSecond = 0; return(false); } else { SpeedUnit unit; switch (m.Groups["unit"].Value.ToLowerInvariant()) { case "kn": case "kt": case "kts": unit = SpeedUnit.Knots; break; case "kph": unit = SpeedUnit.KilometersPerHour; break; case "mph": unit = SpeedUnit.MilesPerHour; break; case "mps": case "m/s": unit = SpeedUnit.MetersPerSecond; break; default: switch (unitSystem) { case UnitSystem.Imperial: unit = SpeedUnit.MilesPerHour; break; case UnitSystem.Metric: unit = SpeedUnit.KilometersPerHour; break; case UnitSystem.NauticalImperial: case UnitSystem.NauticalMetric: unit = SpeedUnit.Knots; break; default: unit = SpeedUnit.MetersPerSecond; break; } break; } metersPerSecond = ManeuveringBoard.ConvertFromUnit(metersPerSecond, unit); return(true); } }
public ShapeDataForm(Shape shape, UnitSystem unitSystem) : this() { if (shape == null) { throw new ArgumentNullException(); } this.unitSystem = unitSystem; txtName.Text = shape.Name; lblParent.Text = shape.Parent == null ? "<none>" : string.IsNullOrEmpty(shape.Parent.Name) ? "<unnamed shape>" : shape.Parent.Name; UnitShape unit = shape as UnitShape; if (unit != null) { txtSize.Enabled = false; txtDirection.Tag = unit.Direction; txtDirection.Text = (unit.Direction * MathConst.RadiansToDegrees).ToString("0.##"); txtSpeed.Tag = unit.Speed; txtSpeed.Text = ManeuveringBoard.GetSpeedString(unit.Speed, unitSystem); cmbType.SelectedIndex = (int)unit.Type; if (unit.Parent == null) { chkRelative.Enabled = false; } else { chkRelative.Checked = unit.IsMotionRelative; } } else { txtSpeed.Enabled = false; chkRelative.Enabled = false; cmbType.Enabled = false; LineShape line = shape as LineShape; if (line != null) { double angle = ManeuveringBoard.AngleBetween(line.Start, line.End), length = line.Length; txtDirection.Text = (angle * MathConst.RadiansToDegrees).ToString("0.##"); txtDirection.Tag = angle; txtSize.Text = ManeuveringBoard.GetDistanceString(length, unitSystem); txtSize.Tag = length; } else { CircleShape circle = shape as CircleShape; if (circle != null) { txtDirection.Enabled = false; txtSize.Text = ManeuveringBoard.GetDistanceString(circle.Radius, unitSystem); txtSize.Tag = circle.Radius; lblSize.Text = "Radius"; } else { throw new NotImplementedException(); } } } // set these to false, since they may have been set to true by the programmatic changes above directionTextChanged = sizeTextChanged = speedTextChanged = false; }
public PositionalDataForm(PositionalDataShape posData, UnitSystem unitSystem) : this() { this.unitSystem = unitSystem; txtTime.Text = ManeuveringBoard.GetTimeString(posData.Time); txtTime.Focus(); txtTime.SelectAll(); Text = grpObservation.Text = (posData is Waypoint ? "Waypoint" : "Observation") + " Data"; PositionalDataShape previousPosition = null; UnitShape observer = posData is Observation ? ((Observation)posData).Observer : null; for (int index = posData.Parent.Children.IndexOf(posData) - 1; index >= 0; index--) { previousPosition = posData.Parent.Children[index] as PositionalDataShape; if (previousPosition != null && previousPosition.GetType() == posData.GetType() && (!(previousPosition is Observation) || ((Observation)previousPosition).Observer == observer)) { break; } else { previousPosition = null; } } if (previousPosition != null) { previousTime = previousPosition.Time; } if (posData is BearingObservation) { double bearing = ((BearingObservation)posData).Bearing; grpPrevious.Enabled = false; grpObserved.Enabled = false; txtObserverDistance.Enabled = false; txtObserverBearing.Text = (bearing * MathConst.RadiansToDegrees).ToString("0.##"); txtObserverBearing.Tag = bearing; txtObservedBearing.WasChanged = false; // ignore programmatic change } else { this.posDataPoint = posData.Position; FillRelativeTo((UnitShape)posData.Parent, txtObservedBearing, txtObservedDistance, out observedPoint); if (posData is PointObservation) { FillRelativeTo(observer, txtObserverBearing, txtObserverDistance, out observerPoint); } else { grpObserver.Enabled = false; grpObserved.Text = "Relative to Unit"; } if (previousPosition == null) { grpPrevious.Enabled = false; } else { FillRelativeTo(previousPosition, txtPreviousBearing, txtPreviousDistance, out previousPoint); } waypoint = posData is Waypoint; if (waypoint) { waypointTimes = posData.Parent.Children.OfType <Waypoint>().Where(w => w != posData).Select(w => w.Time).ToList(); } } }
bool UpdateSolution() { double?speed = null, time = null, aob = null, radius = null; if (!string.IsNullOrEmpty(txtSpeed.Text.Trim())) { double value; if (!TryParseSpeed(txtSpeed.Text, unitSystem, out value)) { ShowInvalidSpeed(txtSpeed.Text); goto invalidData; } speed = value; } if (!string.IsNullOrEmpty(txtTime.Text.Trim())) { TimeSpan timeSpan; bool relative; if (!TryParseTime(txtTime.Text, out timeSpan, out relative)) { ShowInvalidTime(txtTime.Text, false, false); goto invalidData; } time = timeSpan.TotalSeconds; } if (!string.IsNullOrEmpty(txtAoB.Text.Trim())) { double value; if (!TryParseAngle(txtAoB.Text.Trim(), out value)) { ShowInvalidAngle(txtAoB.Text); goto invalidData; } aob = value; } if (!string.IsNullOrEmpty(txtRadius.Text.Trim())) { double value; if (!TryParseLength(txtRadius.Text, unitSystem, out value)) { ShowInvalidLength(txtRadius.Text); goto invalidData; } if (value != 0) { radius = value; } } if (speed.HasValue && time.HasValue) { lblSolution.Text = "Remove speed or time."; return(false); } if (aob.HasValue && !radius.HasValue) { lblSolution.Text = "Using AoB requires a radius."; return(false); } if (radius.HasValue && !aob.HasValue) { lblSolution.Text = "Using a radius requires AoB."; return(false); } Point2 targetPt; Vector2 targetVel; double targetCourse; if (target != null) { targetPt = target.Position; targetVel = target.GetEffectiveVelocity(); targetCourse = target.Direction; } else { double bearing, range; if (string.IsNullOrEmpty(txtBearing.Text)) { lblSolution.Text = "Enter a target bearing."; return(false); } else if (!TryParseAngle(txtBearing.Text, out bearing)) { ShowInvalidAngle(txtBearing.Text); goto invalidData; } if (string.IsNullOrEmpty(txtRange.Text)) { lblSolution.Text = "Enter a target range."; return(false); } else if (!TryParseLength(txtRange.Text, unitSystem, out range)) { ShowInvalidLength(txtRange.Text); goto invalidData; } targetPt = new Vector2(0, range).Rotate(-bearing).ToPoint(); double targetSpeed; if (string.IsNullOrEmpty(txtTargetSpeed.Text)) { lblSolution.Text = "Enter a target speed."; return(false); } else if (!TryParseSpeed(txtTargetSpeed.Text, unitSystem, out targetSpeed)) { ShowInvalidSpeed(txtTargetSpeed.Text); goto invalidData; } if (targetSpeed == 0) { targetCourse = 0; } else if (string.IsNullOrEmpty(txtCourse.Text)) { lblSolution.Text = "Enter a target course."; return(false); } else if (!TryParseAngle(txtCourse.Text, out targetCourse)) { ShowInvalidAngle(txtCourse.Text); goto invalidData; } targetVel = new Vector2(0, targetSpeed).Rotate(-targetCourse); } // if AoB was specified, then we're actually trying to intercept a single point on the radius circle, so make that are target point // and use the standard point intercept algorithm if (aob.HasValue) { targetPt += new Vector2(0, radius.Value).Rotate(-(targetCourse + aob.Value)); } // if we've already satisfied the intercept criteria... Vector2 o = unit == null ? new Vector2(targetPt) : targetPt - unit.Position; if (o.LengthSqr <= (radius.HasValue && !aob.HasValue ? radius.Value * radius.Value : 0)) { lblSolution.Text = "You are already there."; return(false); } // if the target is not moving, any speed will work, so we'll just arbitrarily head there at 10 units of speed if (targetVel.LengthSqr == 0) { Solution = o.GetNormal(MB.ConvertFromUnit(10, MB.GetAppropriateSpeedUnit(unitSystem))); InterceptPoint = targetPt; lblSolution.Text = ManeuveringBoard.GetAngleString(MB.SwapBearing(o.Angle)) + " (target stationary)"; return(true); } // if we have a single target point (i.e. the target itself, or one point on the radius circle), the intercept formula basically // consists in solving a quadratic formula. if we're at P and the target is at T with velocity V, then we know that the interception // point is at T+V*t, where t is time. if we take the vector from P to the intersection point (T+V*t-P), then the distance is // |T+V*t-P|. dividing by the speed s gives us the time: t = |(T+V*t-P)|/s. so s*t = |T+V*t-P|. squaring both sides, replacing T-P // with the helper O (i.e. translating P to the origin), and expanding the vector, we get (s*t)^2 = (Ox+Vx*t)^2 + (Oy+Vy*t)^2 if (!time.HasValue) // if the user didn't specify the time of intercept... (if they did, the problem's solved already) { if (!speed.HasValue) { // if the user specified no information, there are an infinite number of solutions. we'll choose the one that requires // approximately the lowest intercept speed. if we solve the quadratic equation for speed instead of time, we get // s = sqrt((Ox + Vx*t)^2 + (Oy + Vy*t)^2) / t. we need to minimize this equation where s >= 0 and t >= 0. there's probably // some way to do this analytically, but it seems complicated, so we'll just minimize it numerically. we'll represent the time // TODO: check out the inverse of this function. it may be more sloped and easier to optimize? // TODO: consider a better method of rescaling (to get all the numbers about the same magnitude) Func <double, double> speedFunc = t => { if (t <= 0) { return(double.NaN); } t *= 3600; // we'll scale the time from seconds to hours because the minimizer is a bit more stable when params are around 1.0 double x = o.X + targetVel.X * t, y = o.Y + targetVel.Y * t; return(Math.Sqrt(x * x + y * y) / t); }; Func <double, double> derivative = t => { if (t <= 0) { return(double.NaN); } t *= 3600; double x = o.X + targetVel.X * t, y = o.Y + targetVel.Y * t; return(-(o.X * x + o.Y * y) / (t * t * Math.Sqrt(x * x + y * y))); }; ConstrainedMinimizer minimizer = new ConstrainedMinimizer(new DifferentiableMDFunction(speedFunc, derivative)); // the function tends to be very flat near the minimum, so it's hard to find it exactly. increase the gradient tolerance to // prevent it from failing as often. we're going to be rounding the answer anyway, so it needn't be exact minimizer.GradientTolerance = 1e-6; // constrain the solution to be non-negative minimizer.AddConstraint(new DifferentiableMDFunction(t => - speedFunc(t), t => - derivative(t))); minimizer.SetBounds(0, 0, double.MaxValue); // constrain t to be non-negative try { double[] point = new double[] { 1 }; double minSpeed = minimizer.Minimize(point); // now we want to round the speed up to the next unit, to make it look nicer. we also want to increase the speed by a small // amount because the time seems to increase without bound as the speed nears the minimum. for instance, a difference of // 0.01 kn near the minimum might increase the time by 5 hours. speeds near the minimum also render the math below very // inaccurate due to mismatching precision. so we'll add about a knot to the speed as well as round it up SpeedUnit speedUnit = MB.GetAppropriateSpeedUnit(unitSystem); speed = MB.ConvertFromUnit(Math.Ceiling(MB.ConvertToUnit(minSpeed + 0.5, speedUnit)), speedUnit); // 0.5 m/s ~= 1 kn } catch (MinimumNotFoundException) { lblSolution.Text = "Try setting parameters."; return(false); } } // if know the intercept speed, we take the above equation and factor out time. we end up with: // t^2(Vx^2 + Vy^2 - s^2) + t*(2Ox*Vx + 2Oy*Vy) + Ox^2 + Oy^2 = 0. if we take A=(Vx^2 + Vy^2 - s^2), B=2(Ox*Vx + Oy*Vy), and // C=Ox^2 + Oy^2, then we have the quadratic A*t^2 + B*t + C = 0 which we can solve with the quadratic formula. // t = (-B +/- sqrt(B^2 - 4AC)) / 2A (and we can remove a factor of 2). if the discriminant is negative, there is no solution. // otherwise, we take whichever solution gives the smallest non-negative time double A = targetVel.X * targetVel.X + targetVel.Y * targetVel.Y - speed.Value * speed.Value, B = o.X * targetVel.X + o.Y * targetVel.Y, C = o.X * o.X + o.Y * o.Y; // if A = 0, then the speeds are identical, and we get division by zero solving the quadratic. but if A = 0 then we just have // B*t + C = 0 or t = -C/B. we know B can't be zero because we checked the relevant cases above if (A == 0) { double t = -C / (2 * B); // we have to multiply B by 2 because we removed a factor of two above if (t >= 0) { time = t; } else { goto noSolution; } } else { double discriminant = B * B - A * C; if (discriminant < 0) { goto noSolution; } double root = Math.Sqrt(discriminant), time1 = (-B + root) / A, time2 = (-B - root) / A; if (time1 >= 0 && time1 <= time2) { time = time1; } else if (time2 >= 0) { time = time2; } else { goto noSolution; } } } // now that we know the time of intercept, we can calculate the intercept point and get the velocity we'd need to get there. // the intercept point is T+V*t. the intercept vector is T+V*t-P = O+V*t. this vector has a length equal to the distance, but we // want a length equal to the speed, so divide by time to get speed (e.g. 10 km in 2 hour = 5km/hour). but (O+V*t)/t = O/t+V Solution = o / time.Value + targetVel; InterceptPoint = targetPt + targetVel * time.Value; haveSolution = true; lblSolution.Text = ManeuveringBoard.GetAngleString(MB.SwapBearing(Solution.Angle)) + " at " + MB.GetSpeedString(Solution.Length, unitSystem) + " for " + GetTimeString(time.Value); return(true); noSolution: lblSolution.Text = "No intercept is possible."; return(false); invalidData: lblSolution.Text = "Invalid data."; return(false); }
bool UpdateSolution() { double?speed = null, time = null, aob = null, radius = null; if (!string.IsNullOrEmpty(txtSpeedTDC.Text.Trim())) { double value; if (!TryParseSpeed(txtSpeedTDC.Text, unitSystem, out value)) { ShowInvalidSpeed(txtSpeedTDC.Text); goto invalidData; } speed = value; } Point2 targetPt; Vector2 targetVel; double targetCourse; double ownCourse; double targetAOB; { double bearing, range; if (string.IsNullOrEmpty(txtBearingTDC.Text)) { lblSolutionTDC.Text = "Enter a target true bearing."; return(false); } else if (!TryParseAngle(txtBearingTDC.Text, out bearing)) { ShowInvalidAngle(txtBearingTDC.Text); goto invalidData; } if (string.IsNullOrEmpty(txtRangeTDC.Text)) { lblSolutionTDC.Text = "Enter a target range."; return(false); } else if (!TryParseLength(txtRangeTDC.Text, unitSystem, out range)) { ShowInvalidLength(txtRangeTDC.Text); goto invalidData; } targetPt = new Vector2(0, range).Rotate(-bearing).ToPoint(); double targetSpeed; if (string.IsNullOrEmpty(txtTargetSpeedTDC.Text)) { lblSolutionTDC.Text = "Enter a target speed."; return(false); } else if (!TryParseSpeed(txtTargetSpeedTDC.Text, unitSystem, out targetSpeed)) { ShowInvalidSpeed(txtTargetSpeedTDC.Text); goto invalidData; } if (string.IsNullOrEmpty(txtAoBTDC.Text.Trim())) { lblSolutionTDC.Text = "Enter target AOB."; return(false); } else if (!TryParseAngle(txtAoBTDC.Text.Trim(), out targetAOB)) { ShowInvalidAngle(txtAoBTDC.Text); goto invalidData; } if (string.IsNullOrEmpty(txtCourseTDC.Text)) { lblSolutionTDC.Text = "Enter ownship course."; return(false); } else if (!TryParseAngle(txtCourseTDC.Text, out ownCourse)) { ShowInvalidAngle(txtCourseTDC.Text); goto invalidData; } if (targetSpeed == 0) { targetCourse = 0; } else { targetCourse = ownCourse + (Math.PI - targetAOB); } targetVel = new Vector2(0, targetSpeed).Rotate(-targetCourse); } // if we've already satisfied the intercept criteria... Vector2 o = unit == null ? new Vector2(targetPt) : targetPt - unit.Position; if (o.LengthSqr <= (radius.HasValue && !aob.HasValue ? radius.Value * radius.Value : 0)) { lblSolutionTDC.Text = "You are already at the target."; return(false); } // if the target is not moving, any speed will work, so we'll just arbitrarily head there at 10 units of speed if (targetVel.LengthSqr == 0) { Solution = o.GetNormal(MB.ConvertFromUnit(10, MB.GetAppropriateSpeedUnit(unitSystem))); InterceptPoint = targetPt; lblSolutionTDC.Text = ManeuveringBoard.GetAngleString(MB.SwapBearing(o.Angle)) + " (target stationary)"; return(true); } // if we have a single target point (i.e. the target itself, or one point on the radius circle), the intercept formula basically // consists in solving a quadratic formula. if we're at P and the target is at T with velocity V, then we know that the interception // point is at T+V*t, where t is time. if we take the vector from P to the intersection point (T+V*t-P), then the distance is // |T+V*t-P|. dividing by the speed s gives us the time: t = |(T+V*t-P)|/s. so s*t = |T+V*t-P|. squaring both sides, replacing T-P // with the helper O (i.e. translating P to the origin), and expanding the vector, we get (s*t)^2 = (Ox+Vx*t)^2 + (Oy+Vy*t)^2 if (!time.HasValue) // if the user didn't specify the time of intercept... (if they did, the problem's solved already) { if (!speed.HasValue) { lblSolutionTDC.Text = "Enter torpedo speed."; return(false); } // if know the intercept speed, we take the above equation and factor out time. we end up with: // t^2(Vx^2 + Vy^2 - s^2) + t*(2Ox*Vx + 2Oy*Vy) + Ox^2 + Oy^2 = 0. if we take A=(Vx^2 + Vy^2 - s^2), B=2(Ox*Vx + Oy*Vy), and // C=Ox^2 + Oy^2, then we have the quadratic A*t^2 + B*t + C = 0 which we can solve with the quadratic formula. // t = (-B +/- sqrt(B^2 - 4AC)) / 2A (and we can remove a factor of 2). if the discriminant is negative, there is no solution. // otherwise, we take whichever solution gives the smallest non-negative time double A = targetVel.X * targetVel.X + targetVel.Y * targetVel.Y - speed.Value * speed.Value, B = o.X * targetVel.X + o.Y * targetVel.Y, C = o.X * o.X + o.Y * o.Y; // if A = 0, then the speeds are identical, and we get division by zero solving the quadratic. but if A = 0 then we just have // B*t + C = 0 or t = -C/B. we know B can't be zero because we checked the relevant cases above if (A == 0) { double t = -C / (2 * B); // we have to multiply B by 2 because we removed a factor of two above if (t >= 0) { time = t; } else { goto noSolution; } } else { double discriminant = B * B - A * C; if (discriminant < 0) { goto noSolution; } double root = Math.Sqrt(discriminant), time1 = (-B + root) / A, time2 = (-B - root) / A; if (time1 >= 0 && time1 <= time2) { time = time1; } else if (time2 >= 0) { time = time2; } else { goto noSolution; } } } // now that we know the time of intercept, we can calculate the intercept point and get the velocity we'd need to get there. // the intercept point is T+V*t. the intercept vector is T+V*t-P = O+V*t. this vector has a length equal to the distance, but we // want a length equal to the speed, so divide by time to get speed (e.g. 10 km in 2 hour = 5km/hour). but (O+V*t)/t = O/t+V Solution = o / time.Value + targetVel; InterceptPoint = targetPt + targetVel * time.Value; haveSolution = true; lblSolutionTDC.Text = "Aim at " + ManeuveringBoard.GetAngleString(MB.SwapBearing(Solution.Angle)) + ", torpedo running time " + GetTimeString(time.Value); return(true); noSolution: lblSolutionTDC.Text = "No intercept is possible."; return(false); invalidData: lblSolutionTDC.Text = "Invalid data."; return(false); }
public override string GetStatusText(Shape shape) { UnitShape unit = (UnitShape)shape; return(ManeuveringBoard.GetAngleString(unit.Direction) + ", " + unit.Board.GetSpeedString(unit.Speed)); }