[Solved] (converted) VB.NET version of Flights Demo looks screwy! :)

Jul 4, 2014 at 7:01 PM
Edited Jul 4, 2014 at 7:03 PM
Experts,

I like this Helix library a LOT. So much that I converted one of the demos (Flights) as-is to VB.NET via InstantVB & my modest C# abilities. I got the C# version to compile in a separate project after moving files around & such, so I know what things should look like:

Image

So when I converted it, I was a little surprised to see:

Image

I'm going through line-by-line, and i honestly can't figure out where the error is. When stepping through FlightVisual3D(), all the variables seem sane... Nothing shows up in the grid I/O section to the left, in fields, rows, or pull-downs, but I don't mind.

My end goal: I just want to supply a method with points A & B, and have this (modified) demo draw a line between those 2 points. The fact that the line 'arcs' over the surface is exceptionally desirable...

Could someone tell me why this is barfing? I'm hoping one of you can tell just by looking at the output above...

It seems to me that the likely culprit is FlightVisual3D, which draws the line & 2 spheres. In hopes that one of you experts can see immediately the issue, here is FlightVisual3D():
Imports System
Imports System.Windows.Media
Imports System.Windows.Media.Media3D
Imports HelixToolkit.Wpf

Namespace toag.graphics

    ' http://www.sasems.port.se/Emissioncalc.cfm
    ' http://www.partow.net/miscellaneous/airportdatabase/

    Public Class FlightVisual3D
        Inherits ModelVisual3D

        Public Shared EarthRadius As Double = 6371 ' km
        Public Shared DefaultCruisingAltitude As Double = 300 ' km  // real value is 12.5...
        Public Shared DefaultTakeoffLength As Double = 200
        
        Public Shared DefaultCruisingSpeed As Double = 890 ' km/h
        Public Property CabinFactor() As Double

        ''' <summary>
        ''' Gets the estimated CO2 emission per passenger for the given cabin factor.
        ''' </summary>
        Public ReadOnly Property CO2() As Double
            Get
                Return 24 + Distance * 0.0535 ' km
            End Get
        End Property

        Public Sub New(ByVal p1 As Point3D, ByVal p2 As Point3D)
            Dim tube = New TubeVisual3D()
            ' tube.Material = MaterialHelper.CreateMaterial(Color.FromArgb(80, 255, 255, 255)); // Materials.Yellow;
            tube.Fill = New SolidColorBrush(Color.FromArgb(80, 255, 255, 255))
            Children.Add(tube)
            Children.Add(New SphereVisual3D() With {.Center = p1, .Radius = 100, .Material = Materials.Green})
            Children.Add(New SphereVisual3D() With {.Center = p2, .Radius = 100, .Material = Materials.Red})

            Dim lat1, lon1, lat2, lon2 As Double
            PointToLatLon(p1, lat1, lon1)
            PointToLatLon(p2, lat2, lon2)
            [From] = String.Format("{0:0.00} {1:0.00}", lat1, lon1)
            [To] = String.Format("{0:0.00} {1:0.00}", lat2, lon2)

            CruisingSpeed = DefaultCruisingSpeed
            Dim cruisingRadius As Double = EarthRadius + DefaultCruisingAltitude
            Dim takeoffLength As Double = DefaultTakeoffLength
            Dim groundRadius As Double = EarthRadius
            Const tubeDiameter As Double = 60

            Dim o = New Point3D(0, 0, 0)
            Dim v1 = p1 - o
            Dim v2 = p2 - o
            Dim z = Vector3D.CrossProduct(v1, v2)
            Dim x = v1
            Dim y = Vector3D.CrossProduct(x, z)
            x.Normalize()
            y.Normalize()
            Dim v2X As Double = Vector3D.DotProduct(v2, x)
            Dim v2Y As Double = Vector3D.DotProduct(v2, y)
            Dim v2A As Double = Math.Atan2(v2Y, v2X)

            Const n As Integer = 100

            Dim pts = New Point3DCollection()

            Dim da As Double = v2A / (n - 1)

            'INSTANT VB NOTE: The variable distance was renamed since Visual Basic does not handle local variables named the same as class members well:
            Dim distance_Renamed As Double = cruisingRadius * Math.Abs(v2A)
            Dim landingLength As Double = takeoffLength

            Dim l As Double = 0
            For i As Integer = 0 To n - 1
                Dim a As Double = i * da
                Dim v As Vector3D = x * Math.Cos(a) + y * Math.Sin(a)
                Dim r As Double = cruisingRadius

                'if (l < takeoffLength)
                '{
                '    r = groundRadius + Math.Sin(Math.PI/2*l/takeoffLength)*(cruisingRadius - groundRadius);
                '}
                'if (l > distance - landingLength)
                '{
                '    r = groundRadius + Math.Sin(Math.PI/2*(distance - l)/takeoffLength)*(cruisingRadius - groundRadius);
                '}

                r = groundRadius + Math.Sin(CLng(Math.PI * i) \ (n - 1)) * (cruisingRadius - groundRadius)

                Dim p = o + v * r
                '  Children.Add(new SphereVisual3D() { Center = p, Radius = 60, Material = Materials.Gray});

                pts.Add(p)
                l += Math.Abs(cruisingRadius * da)
            Next i
            tube.Diameter = tubeDiameter
            tube.ThetaDiv = 16
            tube.Path = pts
            Distance = distance_Renamed
        End Sub

        Public Property [From] As String
        Public Property [To] As String

        Public ReadOnly Property Time() As String
            Get
                Dim totalHours As Double = Distance / CruisingSpeed
                Dim hours As Double = CInt(Math.Truncate(totalHours))
                Dim minutes = CInt(Math.Truncate(Math.Round((totalHours - hours) * 60)))
                Return String.Format("{0}h{1:00}m", hours, minutes)
            End Get
        End Property

        Public Property CruisingSpeed() As Double
        Public Property Distance() As Double

        Public Shared Sub PointToLatLon(ByVal pt As Point3D, <System.Runtime.InteropServices.Out()> ByRef lat As Double, <System.Runtime.InteropServices.Out()> ByRef lon As Double)
            lon = Math.Atan2(pt.Y, pt.X) * 180 / Math.PI
            lon += 180
            If lon > 180 Then
                lon -= 360
            End If
            If lon < -180 Then
                lon += 360
            End If
            Dim a As Double = Math.Sqrt(pt.X * pt.X + pt.Y * pt.Y)
            lat = Math.Atan2(pt.Z, a) * 180 / Math.PI
        End Sub

        Public Shared Function LatLonToPoint(ByVal latitude As Double, ByVal longitude As Double) As Point3D
            longitude -= 180
            latitude = latitude / 180 * Math.PI
            longitude = longitude / 180 * Math.PI
            Return New Point3D(EarthRadius * Math.Cos(latitude) * Math.Cos(longitude), EarthRadius * Math.Cos(latitude) * Math.Sin(longitude), EarthRadius * Math.Sin(latitude))
        End Function

        Public Overrides Function ToString() As String
            Return String.Format("Distance: {0:0} km, Time: {1}", Distance, Time)
        End Function
    End Class
End Namespace
Thank you SO much in advance!

pat
:)
Jul 4, 2014 at 10:51 PM
Update: well, the error in the math is definitely coming from the New() sub in FlightVisual3D...

I added a bit of debugging after the pts collection (Dim pts = New Point3DCollection) was completed:

[...]
            pts.Add(p)
            l += Math.Abs(cruisingRadius * da)
        Next i
        tube.Diameter = tubeDiameter
        tube.ThetaDiv = 16
        tube.Path = pts
        Dim count As Integer = 1
        For Each pt In pts
            log.DebugEx("[{3}] pt.X: {0} Y: {1} Z: {2}", pt.X, pt.Y, pt.Z, count)
            count += 1
        Next
[...]

Using this, I created the following flight:

Image

I'm still figuring out the coordinate system used, but by going through the output; 100 points in the collection, defining the tube's path:

[2014-07-04 18:26:57.809002][DEBUG] [1] pt.X: 1370.08319161887 Y: -275.654794803274 Z: 6215.82878481586 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.810002][DEBUG] [2] pt.X: 1356.17882894272 Y: -271.360435069487 Z: 6219.06612749908 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.811002][DEBUG] [3] pt.X: 1342.26704035452 Y: -267.064589470649 Z: 6222.26941697551 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.811002][DEBUG] [4] pt.X: 1328.34790202985 Y: -262.767281529153 Z: 6225.43863570517 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[...] things go fine, with X, Y, and Z gradually changing (at ground level, though), then:
[2014-07-04 18:26:57.824003][DEBUG] [31] pt.X: 950.032355774276 Y: -146.273353461398 Z: 6298.06983361165 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.825003][DEBUG] [32] pt.X: 935.938504042294 Y: -141.944678989743 Z: 6300.27871008557 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.825003][DEBUG] [33] pt.X: 958.366032652662 Y: -143.068023744017 Z: 6552.17831581306 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.826003][DEBUG] [34] pt.X: 943.70315769276 Y: -138.566240638758 Z: 6554.40297367252 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[...] and the other anomaly (a bump in the tube, what the mouse is pointing to in the pic above) is 2/3 of the way through:
[2014-07-04 18:26:57.845004][DEBUG] [62] pt.X: 531.338798828905 Y: -12.29843297621 Z: 6602.08318817452 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.845004][DEBUG] [63] pt.X: 516.558770422004 Y: -7.78466762429962 Z: 6603.26290782227 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.846004][DEBUG] [64] pt.X: 503.317423762062 Y: -3.28090808341213 Z: 6624.69592610124 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[2014-07-04 18:26:57.847004][DEBUG] [65] pt.X: 488.486396361165 Y: 1.24678476874656 Z: 6625.80672769894 [CallingMethod: vASA.toag.graphics.FlightVisual3D..ctor][LineNumber: 118]
[...]

Any help narrowing down which calculation(s) are suspect would be greatly appreciated...

TIA!
Jul 5, 2014 at 3:14 AM
[Solved]

You know what they say: "If you want to find an answer, ask a bunch of people"

Ends up this line is the culprit:

r = groundRadius + Math.Sin(Math.PI * i \ (n - 1)) * (cruisingRadius - groundRadius)

in VB.NET, there are more math operators, and while a forward slash (/) is floating point division, a backslash () is for integer division, returning an integer.

So the correct translation would be:

r = groundRadius + Math.Sin(Math.PI * i / (n - 1)) * (cruisingRadius - groundRadius)

And being that the errors were where they were, I kind of figured it has something to do with the radians conversion in the Haversine base calculations.

For those playing at home, this is the version of the New() sub on FlightVisual3D that works the way the C# one does:
Public Sub New(ByVal p1 As Point3D, ByVal p2 As Point3D)
        Dim tube = New TubeVisual3D
        tube.Fill = New SolidColorBrush(Color.FromArgb(80, 255, 125, 125))
        Children.Add(tube)
        Children.Add(New SphereVisual3D() With {.Center = p1, .Radius = 100, .Material = Materials.Green})
        Children.Add(New SphereVisual3D() With {.Center = p2, .Radius = 100, .Material = Materials.Red})
        Dim lat1, lon1, lat2, lon2 As Double
        PointToLatLon(p1, lat1, lon1)
        PointToLatLon(p2, lat2, lon2)
        [From] = String.Format("{0:0.00} {1:0.00}", lat1, lon1)
        [To] = String.Format("{0:0.00} {1:0.00}", lat2, lon2)
        CruisingSpeed = DefaultCruisingSpeed
        Dim cruisingRadius As Double = EarthRadius + DefaultCruisingAltitude
        Dim takeoffLength As Double = DefaultTakeoffLength
        Dim groundRadius As Double = EarthRadius
        Const tubeDiameter As Double = 60
        Dim o = New Point3D(0, 0, 0)
        Dim v1 = p1 - o
        Dim v2 = p2 - o
        Dim z = Vector3D.CrossProduct(v1, v2)
        Dim x = v1
        Dim y = Vector3D.CrossProduct(x, z)
        x.Normalize()
        y.Normalize()
        Dim v2X As Double = Vector3D.DotProduct(v2, x)
        Dim v2Y As Double = Vector3D.DotProduct(v2, y)
        Dim v2A As Double = Math.Atan2(v2Y, v2X)
        Const n As Integer = 100
        Dim pts = New Point3DCollection
        Dim da As Double = v2A / (n - 1)
        Dim distance_Renamed As Double = cruisingRadius * Math.Abs(v2A)
        Dim landingLength As Double = takeoffLength
        Dim l As Double = 0
        For i As Integer = 0 To n - 1
            Dim a As Double = i * da
            Dim v As Vector3D = x * Math.Cos(a) + y * Math.Sin(a)
            Dim r As Double = cruisingRadius
            r = groundRadius + Math.Sin(Math.PI * i / (n - 1)) * (cruisingRadius - groundRadius)
            Dim p = o + v * r
            pts.Add(p)
            l += Math.Abs(cruisingRadius * da)
        Next i
        tube.Diameter = tubeDiameter
        tube.ThetaDiv = 16
        tube.Path = pts
        Distance = distance_Renamed
End Sub
...the _Renamed variables is just InstantVB's way of saying we need to name variables in a less confusing way...

It never fails: As soon as you ask a forum, the answer pops up... :)
Marked as answer by BogusException on 7/5/2014 at 6:58 AM