-1

I am making a gravity/solar system simulation and when the simulation runs I am only getting about 5 fps. Here is the relevant part of my code:

Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles picSpace.Paint
    earth.displayX = Math.Round(earth.positionX)
    earth.displayY = Math.Round(earth.positionY)
    e.Graphics.FillEllipse(Brushes.Blue, earth.displayX - 5, earth.displayY - 5, 10, 10)
    e.Graphics.FillEllipse(Brushes.Yellow, sunX - 10, sunY - 10, 20, 20)
    distance()
    position()
End Sub

Sub distance()
    dX = sunX - earth.positionX
    dY = sunY - earth.positionY
    If (earth.positionX >= sunX) And (earth.positionY <= sunY) Then
        dX *= -1
    Else
        If (earth.positionX >= sunX) And (earth.positionY >= sunY) Then
            dX *= -1
            dY *= -1
        Else
            If (earth.positionX <= sunX) And (earth.positionY >= sunY) Then
                dY *= -1
            Else
                If (earth.positionX <= sunX) And (earth.positionY <= sunY) Then
                    'do nothing
                End If
            End If
        End If
    End If

    d = Math.Sqrt((((dY) * 1000000) ^ 2) + (((dX) * 1000000) ^ 2))
    d = d * 1000
End Sub

Sub position()
    If first = False Then
        earth.positionX += ((((earth.oldVelocityX + earth.velocityX) / 2) * simulationSpeed) / 1000000000)
        earth.positionY += ((((earth.oldVelocityY + earth.velocityY) / 2) * simulationSpeed) / 1000000000)
        orbit(0, counter) = earth.positionX
        orbit(1, counter) = earth.positionY
        counter += 1

        ReDim Preserve orbit(1, counter)
        lblPositionX.Text = "X: " & Math.Truncate(earth.positionX)
        lblPositionY.Text = "Y: " & Math.Truncate(earth.positionY)
    End If
    F = (earth.mass * sunMass * G) / (d ^ 2)
    theta = Math.Atan(dX / dY)
    If (earth.positionX >= sunX) And (earth.positionY <= sunY) Then
        earth.forceX = F * Math.Sin(theta) * -1
        earth.forceY = F * Math.Cos(theta)
    Else
        If (earth.positionX >= sunX) And (earth.positionY >= sunY) Then
            earth.forceX = F * Math.Sin(theta) * -1
            earth.forceY = F * Math.Cos(theta) * -1
        Else
            If (earth.positionX <= sunX) And (earth.positionY >= sunY) Then
                earth.forceX = F * Math.Sin(theta)
                earth.forceY = F * Math.Cos(theta) * -1
            Else
                If (earth.positionX <= sunX) And (earth.positionY <= sunY) Then
                    earth.forceX = F * Math.Sin(theta)
                    earth.forceY = F * Math.Cos(theta)
                End If
            End If
        End If
    End If
    a = F / earth.mass
    earth.accelerationX = earth.forceX / earth.mass
    earth.accelerationY = earth.forceY / earth.mass
    earth.oldVelocityX = earth.velocityX
    earth.oldVelocityY = earth.velocityY
    earth.velocityX = earth.oldVelocityX + (earth.accelerationX * simulationSpeed)
    earth.velocityY = earth.oldVelocityY + (earth.accelerationY * simulationSpeed)
    first = False
    Me.Refresh()
End Sub

Originally I had a large portion of the code in a do...loop and the framerate was fine, but I could not interact with any controls while the loop was running. Doing it as shown above lets me interact with controls but the framerate is very choppy. Any help would be greatly appreciated.

Lee
  • 31
  • 2
  • 2
    Not related to your performance issue, but as a general .NET recommendation, always prefer overriding to event handling. In this case, override the `OnPaint` method rather than handling the `Paint` event: http://stackoverflow.com/a/3670912/172769 – Luc Morin Apr 04 '16 at 19:37
  • 2
    I would also say that handling this in `Form.Paint` is not going to be very deterministic, as it can be called too often (or not enough). I'd recommend trying a Timer based calculation. I mean, you can still draw the shapes in Paint, but leave the calculus part, maybe on a background worker triggered every second (or whatever time base you deem best for your simulation) – Luc Morin Apr 04 '16 at 19:44

1 Answers1

1

Calling Refresh from within Paint is very odd. One possible performance issue is that this forces the entire form to repaint including the background. I would suggest 2 things:

Create a timer object, and perform the calculation and updates from within Timer_Tick event.

Then remove the Me.Refresh command from sub position(), so that position() and distance() just do calculations. Add a call to Me.Invalidate() at the beginning and end of the Timer_Tick passing it the rectangle containing the location of the earth. This will force only the old and new locations to be repainted and won't repaint a lot of unchanged background. Your Paint method is then likely to just be the 2 FillEllipse lines.

Stuart Whitehouse
  • 1,421
  • 18
  • 30
  • I've tried it but it doesn't work. The timer tick event looks like this, is this correct? `Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick Me.Invalidate() Distance() Position() Me.Invalidate() End Sub` – Lee Apr 04 '16 at 20:57
  • @Lee - In what way doesn't work? Is the timer enabled and with a fairly short interval? – Stuart Whitehouse Apr 04 '16 at 20:59
  • Yes it has an interval of 20. It is supposed to start simulating when the user clicks. (The initial position of Earth is where the user clicks) However when using the new code nothing happens when you click. – Lee Apr 04 '16 at 21:25
  • To start/stop the timer you need to include Timer1.Enabled = true in the click handler. – Stuart Whitehouse Apr 04 '16 at 21:31
  • I fixed it! I added the me.refresh() back to the end of position() and it works now. Performance is good, thanks a lot for your help! – Lee Apr 05 '16 at 08:27