Circle math, field of view and raycast visibility (Post #7)

We'll dive a bit more into circles, I'll post the full code at the end.

  1. A point following a circle's circumference
  2. Field of view
  3. Ray casting through that field of view

A point following a circle's circumference

The math are the following simple and straightforward

The sine of an angle is Y/R and the cosine of an angle is X/R

So for the implementation in VB.NET

AngleMidR += 1 'Increasing degrees
AngleQ = (AngleMidR * Math.PI) / 180 'AngleMidR (degrees) translated into radians

CircMidRY = CircMidY + CircRadius * Math.Sin(AngleQ)
CircMidRX = CircMidX + CircRadius * Math.Cos(AngleQ)

 Where CircMid is the middle point of the circle and CircMidR is the point with R distance from it.

I cut the frames on this one to create the .gif (that's why it's so rough)


Field of View

For calculating field of view we'll assign an angle as the middle of the vision and widen our field based on the span from it.

Let's say our Camera faces at Z degrees (where 0 is the middle right part of the horizontal axis in a clockwise direction), and our field of view has an arc vision span of a 70 degrees.

If we use the math on the first example of this post we could create the following representation of that arc.

Crude representation using drawline

Now let's step a simple way to translate our calculations to our grid system and mark all the cells that are inside our field of view.

  1. Find the points on the arc horizon (furthest grid cells to show)
  2. Cast an arc from starting point to all the points on an arc with increasing radius (I'll explain on the next image)
  3. Assign a value on all these points

 

A different way and less crude would be to use Math.atan2, loop through all the grid cells, find their static angle from our starting point and compare it to our current camera angle.

Ray-cast visibility

To distinguish visible tiles from non visible tiles, we stop the ray-casting when we find a blocked tile while we cast our arc.

These gif files really don't do justice to the real outcome
As you can see on the image below when our arc finds a blocked tile it adds it to it's field of view and the remaining on the ray line till max radius are left outside.

Code

I added a FOV integer on our gridworld array from the previous posts to hold the value for the cells inside our arc. The code is highly un-optimized and it can improve greatly.

Our drawing code (SoftRender("DRAW"))

For X = 0 To MaxGridX
                For Y = 0 To MaxGridY
                    QRect = New Rectangle(GridWorld(X, Y).X, GridWorld(X, Y).Y, GridWidth, GridWidth)
                    If GridWorld(X, Y).Blocked = 0 Then
                        Gs.FillRectangle(Brushes.LightGray, QRect)
                    Else
                        Gs.FillRectangle(Brushes.DarkSlateGray, QRect)
                    End If
                    If GridWorld(X, Y).FOV = 1 Then
                        Gs.FillRectangle(Brushes.Blue, QRect)
                    End If
                    Gs.DrawRectangle(Pens.DarkGray, QRect)
                Next
            Next

Our calls to our new sub (Add these to a refreshing event or even inside the draw function)

CircleAlgo("PROJECT_ON_GRID")
CircleAlgo("FIELD_OF_VIEW")
CircleAlgo("DRAW_ON_CANVAS_FOV")

And the rest of the code

    Public CircMidXG As Integer
    Public CircMidYG As Integer
    Public CircMidX As Integer = 400
    Public CircMidY As Integer = 200
    Public CircMidRX As Integer
    Public CircMidRY As Integer
    Public AngleMidR As Integer
    Public CircRadius As Integer = 100

    Public ArcList(70) As Point

    Public Sub CircleAlgo(ByVal Func As String)
        Dim AngleQ As Single
        Dim FieldofViewL As Single = ((AngleMidR - 35) * Math.PI) / 180
        Dim FieldofViewR As Single = ((AngleMidR + 35) * Math.PI) / 180
        Dim X, Y, Z, Q As Integer
'Here I blocked some tiles for testing (the ones you see in the last image
        GridWorld(37, 11).Blocked = 1
        GridWorld(37, 12).Blocked = 1
        GridWorld(38, 12).Blocked = 1
        GridWorld(38, 13).Blocked = 1
        GridWorld(39, 13).Blocked = 1
        GridWorld(39, 14).Blocked = 1
        GridWorld(39, 15).Blocked = 1
        GridWorld(40, 15).Blocked = 1
        GridWorld(41, 15).Blocked = 1

        If Func = "POINT_ON_CIRCLE" Then
            AngleMidR += 2 'Increasing degrees
            AngleQ = (AngleMidR * Math.PI) / 180 'AngleMidR (degrees) translated into radians
            CircMidRY = CircMidY + CircRadius * Math.Sin(AngleQ)
            CircMidRX = CircMidX + CircRadius * Math.Cos(AngleQ)
        ElseIf Func = "DRAW_ON_CANVAS" Then
            QRect = New Rectangle(CircMidX - CircRadius, CircMidY - CircRadius, CircRadius * 2, CircRadius * 2)
            Gs.DrawEllipse(Pens.Black, QRect)
            'Draw Circle Middle point
            QRect = New Rectangle(CircMidX, CircMidY, 4, 4)
            Gs.FillRectangle(Brushes.Black, QRect)
            'Draw Point on the circle circumference
            QRect = New Rectangle(CircMidRX, CircMidRY, 4, 4)
            Gs.FillRectangle(Brushes.Red, QRect)
        ElseIf Func = "FIELD_OF_VIEW" Then
            If AngleMidR < 360 Then
                AngleMidR += 2 'Field of view middle point angle
            Else
                AngleMidR = 0
            End If
            AngleQ = (AngleMidR * Math.PI) / 180 'AngleMidR (degrees) translated into radians
            FieldofViewL = ((AngleMidR - 35) * Math.PI) / 180 'Field of View Left Edge (degrees) translated into radians
            FieldofViewR = ((AngleMidR + 35) * Math.PI) / 180 'Field of View Right Edge (degrees) translated into radians
            CircMidRY = CircMidY + CircRadius * Math.Sin(AngleQ)
            CircMidRX = CircMidX + CircRadius * Math.Cos(AngleQ)
            Gs.DrawLine(Pens.DarkViolet, CircMidX, CircMidY, CircMidRX, CircMidRY)
            CircMidRY = CircMidY + CircRadius * Math.Sin(FieldofViewL)
            CircMidRX = CircMidX + CircRadius * Math.Cos(FieldofViewL)
            Gs.DrawLine(Pens.Blue, CircMidX, CircMidY, CircMidRX, CircMidRY)
            CircMidRY = CircMidY + CircRadius * Math.Sin(FieldofViewR)
            CircMidRX = CircMidX + CircRadius * Math.Cos(FieldofViewR)
            Gs.DrawLine(Pens.Blue, CircMidX, CircMidY, CircMidRX, CircMidRY)
            ClearArc()
            Dim Blockfound As Integer
            For Z = 0 To 70
                Blockfound = 0
                For Q = 0 To CircRadius
                    ArcList(Z).Y = (CircMidY + Q * Math.Sin(((AngleMidR - 35 + Z) * Math.PI) / 180)) \ GridWidth
                    ArcList(Z).X = (CircMidX + Q * Math.Cos(((AngleMidR - 35 + Z) * Math.PI) / 180)) \ GridWidth
                    If Blockfound = 1 Then
                        GridWorld(ArcList(Z).X, ArcList(Z).Y).FOV = 1
                        Exit For
                    End If
                    If GridWorld(ArcList(Z).X, ArcList(Z).Y).Blocked = True Then
                        Blockfound = 1
                    Else
                        GridWorld(ArcList(Z).X, ArcList(Z).Y).FOV = 1
                    End If
                Next
            Next
        ElseIf Func = "DRAW_ON_CANVAS_FOV" Then
            QRect = New Rectangle(CircMidX - CircRadius, CircMidY - CircRadius, CircRadius * 2, CircRadius * 2)
            Gs.DrawEllipse(Pens.Black, QRect)
            'Draw Circle Middle point
            QRect = New Rectangle(CircMidX, CircMidY, 4, 4)
            Gs.FillRectangle(Brushes.Black, QRect)
        ElseIf Func = "PROJECT_ON_GRID" Then
            ShowGrid = 1
            CircMidXG = 32
            CircMidYG = 18
            CircMidX = GridWorld(CircMidXG, CircMidYG).X + GridWidth / 2
            CircMidY = GridWorld(CircMidXG, CircMidYG).Y + GridWidth / 2
            CircRadius = GridWidth * 14
        End If
    End Sub


    Public Sub ClearArc()
        Dim X, Y As Integer
        For X = 0 To MaxGridX
            For Y = 0 To MaxGridY
                GridWorld(X, Y).FOV = 0
            Next
        Next
    End Sub

Ray casting can be really useful on 3d grids, when you have to lower the number of blocks drawn on large maps



Comments