ProblemYou want to solve any triangle given any three known parts. Examples might include the lengths of any two sides and the measure of the angle between them, or the measures of two angles and the length of the side between them. SolutionSample code folder: Chapter 06\AnyTriangle Create a triangle class to handle the details of calculating all the remaining parts of a triangle given any combination of three of its parts. Also create a separate utility function to calculate any triangle's area given the lengths of its three sides. DiscussionThe triangle class, presented below, allows the remaining elements of any triangle to be calculated given the measures of any three of its sides and angles. The only combination that won't work, of course, is when three angles are given, as these pin down the shape of a triangle but not its size. Here is the code for the TRiangle class: Imports System.Math Public Class Triangle Private StoredSideA As Double Private StoredSideB As Double Private StoredSideC As Double Private StoredAngleA As Double Private StoredAngleB As Double Private StoredAngleC As Double ' ----- The GivenParts variable indicates which parts ' the user has already supplied. Uppercase letters ' (A, B, C) indicate sides; lowercase letters ' (a, b, c) are angles. Private GivenParts As String = "" Public Overrides Function ToString() As String ' ----- Show the details of the triangle. Return String.Format( _ "SideA={0}, SideB={1}, SideC={2}, " & _ "AngleA={3}, AngleB={4}, AngleC={5}", _ StoredSideA, StoredSideB, StoredSideC, _ StoredAngleA, StoredAngleB, StoredAngleC) End Function Public Property SideA() As Double Get If (GivenParts.Length >= 3) Then _ Return StoredSideA Else NotYet() End Get Set(ByVal Value As Double) If (Value < 0) Then _ Throw New ArgumentOutOfRangeException( _ "Negative side length (A) not allowed.") CheckIt("A") StoredSideA = Value Resolve() End Set End Property Public Property SideB() As Double Get If (GivenParts.Length >= 3) Then _ Return StoredSideB Else NotYet() End Get Set(ByVal Value As Double) If (Value < 0) Then _ Throw New ArgumentOutOfRangeException( _ "Negative side length (B) not allowed.") CheckIt("B") StoredSideB = Value Resolve() End Set End Property Public Property SideC() As Double Get If (GivenParts.Length >= 3) Then _ Return StoredSideC Else NotYet() End Get Set(ByVal Value As Double) If (Value < 0) Then _ Throw New ArgumentOutOfRangeException( _ "Negative side length (C) not allowed.") CheckIt("C") StoredSideC = Value Resolve() End Set End Property Public Property AngleA() As Double Get If (GivenParts.Length >= 3) Then _ Return StoredAngleA Else NotYet() End Get Set(ByVal Value As Double) If (Value < 0) Or (Value > Math.PI) Then _ Throw New Exception( _ "Angle (A) must range from 0 to PI.") CheckIt("a") StoredAngleA = Value Resolve() End Set End Property Public Property AngleB() As Double Get If (GivenParts.Length >= 3) Then _ Return StoredAngleB Else NotYet() End Get Set(ByVal Value As Double) If (Value < 0) Or (Value > Math.PI) Then _ Throw New Exception( _ "Angle (B) must range from 0 to PI.") CheckIt("b") StoredAngleB = Value Resolve() End Set End Property Public Property AngleC() As Double Get If (GivenParts.Length >= 3) Then _ Return StoredAngleC Else NotYet() End Get Set(ByVal Value As Double) If (Value < 0) Or (Value > Math.PI) Then _ Throw New Exception( _ "Angle (C) must range from 0 to PI.") CheckIt("c") StoredAngleC = Value Resolve() End Set End Property Private Sub CheckIt(ByVal whatToCheck As String) ' ----- Make sure it is OK to adjust a component. If (GivenParts.Length >= 3) Then Throw New Exception( _ "Triangle is immutable once defined by three parts.") If (GivenParts.IndexOf(whatToCheck) >= 0) Then _ Throw New Exception( _ "Triangle component cannot be modified once set.") ' ---- Mark this part as modified. GivenParts &= whatToCheck End Sub Private Sub NotYet() ' ----- The user tried to access components before ' anything was calculated. Throw New Exception( _ "Triangle has not yet been completely defined.") End Sub Private Sub Resolve() ' ----- Calculate the missing angles and sides of ' the triangle. Dim sinRatio As Double Dim inSort() As Char ' ----- Wait for the triangle to be completely defined. If (GivenParts.Length < 3) Then Return ' ----- Sort the known parts list. inSort = GivenParts.ToCharArray() Array.Sort(inSort) GivenParts = New String(inSort) ' ----- Time to resolve. In all cases, the goal is to ' get three known sides. Then, the ResolveABC() ' method can work on getting the missing angles. Select Case GivenParts Case "ABC" ResolveABC() Case "ABa" sinRatio = Sin(StoredAngleA) / StoredSideA StoredAngleB = Asin(StoredSideB * sinRatio) StoredAngleC = PI - StoredAngleA - StoredAngleB StoredSideC = Sin(StoredAngleC) / sinRatio Case "ABb" sinRatio = Sin(StoredAngleB) / StoredSideB StoredAngleA = Asin(StoredSideA * sinRatio) StoredAngleC = PI - StoredAngleA - StoredAngleB StoredSideC = Sin(StoredAngleC) / sinRatio Case "ABc" StoredSideC = Sqrt(StoredSideA ^ 2 + _ StoredSideB ^ 2 - 2 * StoredSideA * _ StoredSideB * Cos(StoredAngleC)) Case "ACa" sinRatio = Sin(StoredAngleA) / StoredSideA StoredAngleC = Asin(StoredSideC * sinRatio) StoredAngleB = PI - StoredAngleA - StoredAngleC StoredSideB = Sin(StoredAngleB) / sinRatio Case "ACb" StoredSideB = Sqrt(StoredSideA ^ 2 + _ StoredSideC ^ 2 - 2 * StoredSideA * _ StoredSideC * Cos(StoredAngleB)) Case "ACc" sinRatio = Sin(StoredAngleC) / StoredSideC StoredAngleA = Asin(StoredSideA * sinRatio) StoredAngleB = PI - StoredAngleA - StoredAngleC StoredSideB = Sin(StoredAngleB) / sinRatio Case "Aab" sinRatio = Sin(StoredAngleA) / StoredSideA StoredSideB = Sin(StoredAngleB) / sinRatio StoredAngleC = PI - StoredAngleA - StoredAngleB StoredSideC = Sin(StoredAngleC) / sinRatio Case "Aac" sinRatio = Sin(StoredAngleA) / StoredSideA StoredSideC = Sin(StoredAngleC) / sinRatio StoredAngleB = PI - StoredAngleA - StoredAngleC StoredSideB = Sin(StoredAngleB) / sinRatio Case "Abc" StoredAngleA = PI - StoredAngleB - StoredAngleC sinRatio = Sin(StoredAngleA) / StoredSideA StoredSideB = Sin(StoredAngleB) / sinRatio StoredSideC = Sin(StoredAngleC) / sinRatio Case "BCa" StoredSideA = Sqrt(StoredSideB ^ 2 + _ StoredSideC ^ 2 - 2 * StoredSideB * _ StoredSideC * Cos(StoredAngleA)) Case "BCb" sinRatio = Sin(StoredAngleB) / StoredSideB StoredAngleC = Asin(StoredSideC * sinRatio) StoredAngleA = PI - StoredAngleB - StoredAngleC StoredSideA = Sin(StoredAngleA) / sinRatio Case "BCc" sinRatio = Sin(StoredAngleC) / StoredSideC StoredAngleB = Asin(StoredSideB * sinRatio) StoredAngleA = PI - StoredAngleB - StoredAngleC StoredSideA = Sin(StoredAngleA) / sinRatio Case "Bab" StoredAngleC = PI - StoredAngleA - StoredAngleB sinRatio = Sin(StoredAngleB) / StoredSideB StoredSideA = Sin(StoredAngleA) / sinRatio StoredSideC = Sin(StoredAngleC) / sinRatio Case "Bac" StoredAngleB = PI - StoredAngleA - StoredAngleC sinRatio = Sin(StoredAngleB) / StoredSideB StoredSideA = Sin(StoredAngleA) / sinRatio StoredSideC = Sin(StoredAngleC) / sinRatio Case "Bbc" StoredAngleA = PI - StoredAngleB - StoredAngleC sinRatio = Sin(StoredAngleB) / StoredSideB StoredSideA = Sin(StoredAngleA) / sinRatio StoredSideC = Sin(StoredAngleC) / sinRatio Case "Cab" StoredAngleC = PI - StoredAngleA - StoredAngleB sinRatio = Sin(StoredAngleC) / StoredSideC StoredSideA = Sin(StoredAngleA) / sinRatio StoredSideB = Sin(StoredAngleB) / sinRatio Case "Cac" StoredAngleB = PI - StoredAngleA - StoredAngleC sinRatio = Sin(StoredAngleC) / StoredSideC StoredSideA = Sin(StoredAngleA) / sinRatio StoredSideB = Sin(StoredAngleB) / sinRatio Case "Cbc" StoredAngleA = PI - StoredAngleB - StoredAngleC sinRatio = Sin(StoredAngleC) / StoredSideC StoredSideA = Sin(StoredAngleA) / sinRatio StoredSideB = Sin(StoredAngleB) / sinRatio Case "abc" Throw New Exception("Cannot resolve " & _ "triangle with only angles specified.") Case Else Throw New Exception( _ "Undefined combination of triangle parts.") End Select ResolveABC() End Sub Private Sub ResolveABC() ' ----- All three sides are known. Calculate the angles. LengthCheck(StoredSideA, StoredSideB, StoredSideC) StoredAngleC = Acos((StoredSideA ^ 2 + _ StoredSideB ^ 2 - StoredSideC ^ 2) / _ (2 * StoredSideA * StoredSideB)) StoredAngleB = Acos((StoredSideA ^ 2 + _ StoredSideC ^ 2 - StoredSideB ^ 2) / _ (2 * StoredSideA * StoredSideC)) StoredAngleA = PI - StoredAngleB - StoredAngleC End Sub Private Sub LengthCheck(ByVal A As Double, _ ByVal B As Double, ByVal C As Double) ' ----- Make sure that one of the sides isn't ' too long for the other two. If (A >= B) AndAlso (A >= C) AndAlso _ (A <= (B + C)) Then Return If (B >= A) AndAlso (B >= C) AndAlso _ (B <= (A + C)) Then Return If (C >= A) AndAlso (C >= B) AndAlso _ (C <= (A + B)) Then Return Throw New Exception( _ "One side is too long for the others.") End Sub End Class Exceptions are thrown if the triangle "doesn't make sense." For example, if the sum of two sides is less than the length of the third, or if three angles are given, the triangle is impossible, or at least the data is insufficient to completely define the triangle. To find the area of any triangle, you could include a shared function within the TRiangle class, but for the sake of demonstration (and because it can be useful in a wider variety of computational situations) we've chosen to create a triangleArea() function separate from the class. This makes it easy to find the area of any triangle given the lengths of its three sides, whether or not you're solving triangles using the triangle class: Public Function TriangleArea(ByVal sideA As Double, _ ByVal sideB As Double, _ ByVal sideC As Double) As Double ' ----- Calculate the area of a triangle. Dim sumHalfSides As Double Dim deltaA As Double Dim deltaB As Double Dim deltaC As Double sumHalfSides = (sideA + sideB + sideC) / 2 deltaA = sumHalfSides - sideA deltaB = sumHalfSides - sideB deltaC = sumHalfSides - sideC Return Math.Sqrt(sumHalfSides * deltaA * deltaB * deltaC) End Function The following code demonstrates the use of the TRiangle class by solving for a triangle that has two sides of length 4 and 5, with a 75°; angle between the two sides. The RadPerDeg constant (see Recipe 6.10) converts 75°to radians at compile time rather than at runtime (to be consistent with all other angular measurements in Visual Basic 2005, radians are always assumed in all the procedures in this book that involve angles): Const RadPerDeg As Double = Math.PI / 180 Dim testTriangle As New Triangle Dim area As Double ' ----- Build a triangle with sides of 4 and 5, and an ' angle between them of 75 degrees. testTriangle.SideA = 4 testTriangle.SideB = 5 testTriangle.AngleC = 75 * RadPerDeg ' ----- The triangle is already resolved. Calculate area. area = TriangleArea(testTriangle.SideA, _ testTriangle.SideB, testTriangle.SideC) MsgBox(testTriangle.ToString & vbNewLine & _ "Area = " & area.ToString) A ToString() function is included in the TRiangle class to provide a default format for presenting the triangle's parts in a single string. The solved triangle for our example is shown in Figure 6-18. Figure 6-18. Solving a triangle with the Triangle class |