Attribute VB_Name = "Terrain"
Option Explicit

'Models and instances
Public Terrain_Model_AdaptiveGround As Long
Public Terrain_Instance_AdaptiveGround(0 To 3) As Long 'Divided in 4 parts to save memory and allow faster editing
Public Terrain_Model_WaterSurface As Long
Public Terrain_Model_WaterSurface_Simple As Long
Public Terrain_Instance_WaterSurface As Long

'Data used for sampling and rendering
Public Terrain_USize As Single
Public Terrain_VSize As Single
Public Terrain_UOffset As Single
Public Terrain_VOffset As Single
Public Terrain_Map_Width As Long
Public Terrain_Map_Height As Long

'Road map generation
Public Terrain_WorldCamera As Long
Public Instance_RoadSpline As Long
Public BoneFrame_RoadSpline As Long
Public BonesPerRoadSpline As Long

'Draw surfaces
Public Terrain_DrawSurface_HeightMap As Long 'The height map in red and blend map in green, blue and alpha
Public Terrain_DrawSurface_RoadMap As Long 'UV coordinates for roads
Public Terrain_CPUSurface_RoadMap As Long 'For sampling friction and placing items
Public Terrain_DrawSurface_ObstacleMap As Long 'Obstacles
    Public Terrain_ObstacleMapHasChanged As Boolean
    Const DTState_Reset As Long = 0
    'DTState 1 to 20 is used for iterations
    Const DTState_Complete As Long = 21
    Public Terrain_ObstacleMapState As Long

'CPU surfaces for faster reading and physics
Public Terrain_CPUSurface_HeightMap As Long 'Just like Terrain_DrawSurface_HeightMap but stored on the CPU's memory
Public Terrain_CPUSurface_ObstacleMap As Long 'Just like Terrain_DrawSurface_ObstacleMap but stored on the CPU's memory

'Height values
Public Terrain_WaterLevel As Single

'Physics
Public Terrain_HeightField_CollisionShape As Long
Public Terrain_HeightField_RigidBody As Long
Public Terrain_EndPlanes_RigidBody(0 To 3) As Long

'These constants must be adapted to Model_AdaptiveGround.dmf
Public Const Terrain_GroundModelFilename As String = "Model_AdaptiveGround"
Public Const Terrain_SmallestGridSize As Single = 1.2 'What size do you want on the grid's finest resolution
Public Const Terrain_PositionRounding As Single = Terrain_SmallestGridSize * 4 'A size that can be used to round the position when following the camera
Public Const Terrain_GroundModelRadius As Single = 76 - 12 'The radius minus some padding for rounding

'This is game specific
Public Const Terrain_TextureScale As Single = 4 'The length until the textures are repeated again
Const RoadWidth As Single = 8
Const MinimumRoadHeight As Single = 0.5
Const MaximumRoadHeight As Single = 2.5

Public Sub Terrain_Init(RandomSeed As Integer, ReflectionMap As Long, RefractionMap As Long, DiffuseGMap As Long, DiffuseBMap As Long, DiffuseAMap As Long, SideMap As Long, TileAtlas As Long, RoadTexture As Long, NewWaterLevel As Single)
    Dim I As Long
    
    'Load the water surface in smaller jobs by precompiling the shaders
    LoadMaterialShader ("M_WaterAbove")
    DoEvents
    LoadMaterialShader ("M_WaterUnder")
    DoEvents
    Terrain_Model_WaterSurface = LoadModel("Model_WaterSurface")
    DoEvents
    Terrain_Model_WaterSurface_Simple = LoadModel("Model_WaterSurface_Simple")
    DoEvents
    If Terrain_Model_WaterSurface > -1 Then
        Terrain_Instance_WaterSurface = frmMain.DGE.Instance_Create(Terrain_Model_WaterSurface): RE
        frmMain.DGE.Instance_SetXAxis Terrain_Instance_WaterSurface, Terrain_GroundModelRadius, 0, 0: RE
        frmMain.DGE.Instance_SetZAxis Terrain_Instance_WaterSurface, 0, 0, Terrain_GroundModelRadius: RE
        frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_WaterSurface, 0, ReflectionMap: RE
        frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_WaterSurface, 1, RefractionMap: RE
        frmMain.DGE.Instance_SetVisibility Terrain_Instance_WaterSurface, 1, False: RE 'Don't cast shadows
    End If
    
    'Load the ground
    Terrain_Model_AdaptiveGround = LoadModel(Terrain_GroundModelFilename)
    DoEvents
    If Terrain_Model_AdaptiveGround > -1 Then
        'Disable camera frustum culling because the vertex shader is moving vertices out of bounds.
        InsertStringToEngine "None": frmMain.DGE.Model_SetCullingUsingName_InSB Terrain_Model_AdaptiveGround: RE
        
        'Create instances in 4 directions so that the model can take much less memory
        For I = 0 To 3
            'Create using the model
            Terrain_Instance_AdaptiveGround(I) = frmMain.DGE.Instance_Create(Terrain_Model_AdaptiveGround): RE
            
            'Assign textures
            frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_AdaptiveGround(I), 2, DiffuseGMap: RE
            frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_AdaptiveGround(I), 3, DiffuseBMap: RE
            frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_AdaptiveGround(I), 4, DiffuseAMap: RE
            frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_AdaptiveGround(I), 5, SideMap: RE
            frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_AdaptiveGround(I), 6, TileAtlas: RE
            frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_AdaptiveGround(I), 7, RoadTexture: RE
        Next I
        frmMain.DGE.Instance_SetXAxis Terrain_Instance_AdaptiveGround(0), Terrain_SmallestGridSize, 0, 0
        frmMain.DGE.Instance_SetZAxis Terrain_Instance_AdaptiveGround(0), 0, 0, Terrain_SmallestGridSize
        frmMain.DGE.Instance_SetXAxis Terrain_Instance_AdaptiveGround(1), 0, 0, Terrain_SmallestGridSize
        frmMain.DGE.Instance_SetZAxis Terrain_Instance_AdaptiveGround(1), -Terrain_SmallestGridSize, 0, 0
        frmMain.DGE.Instance_SetXAxis Terrain_Instance_AdaptiveGround(2), -Terrain_SmallestGridSize, 0, 0
        frmMain.DGE.Instance_SetZAxis Terrain_Instance_AdaptiveGround(2), 0, 0, -Terrain_SmallestGridSize
        frmMain.DGE.Instance_SetXAxis Terrain_Instance_AdaptiveGround(3), 0, 0, -Terrain_SmallestGridSize
        frmMain.DGE.Instance_SetZAxis Terrain_Instance_AdaptiveGround(3), Terrain_SmallestGridSize, 0, 0
        
        'Create infinite planes
        Dim StaticPlaneShape As Long
        StaticPlaneShape = frmMain.DGE.CollisionShape_Create_StaticPlane(0, 1, 0, 0): RE
        Terrain_EndPlanes_RigidBody(0) = frmMain.DGE.RigidBody_Create_Static(StaticPlaneShape, 0, 0, 0, 1, 0, 0, 0, 1, 0): RE
        Terrain_EndPlanes_RigidBody(1) = frmMain.DGE.RigidBody_Create_Static(StaticPlaneShape, 0, 0, 0, 1, 0, 0, 0, 1, 0): RE
        Terrain_EndPlanes_RigidBody(2) = frmMain.DGE.RigidBody_Create_Static(StaticPlaneShape, 0, 0, 0, 1, 0, 0, 0, 1, 0): RE
        Terrain_EndPlanes_RigidBody(3) = frmMain.DGE.RigidBody_Create_Static(StaticPlaneShape, 0, 0, 0, 1, 0, 0, 0, 1, 0): RE
        
        'Create the world map camera
        Terrain_WorldCamera = frmMain.DGE.Camera_Create: RE
        
        'Load the road spline
        Dim Model_RoadTextureMap As Long
        Model_RoadTextureMap = LoadModel("Model_RoadTextureMap")
        Instance_RoadSpline = frmMain.DGE.Instance_Create(Model_RoadTextureMap): RE
        BoneFrame_RoadSpline = frmMain.DGE.BoneFrame_CreateFromDefaultPose(Model_RoadTextureMap): RE
        frmMain.DGE.Instance_AssignBoneFrame Instance_RoadSpline, BoneFrame_RoadSpline: RE
        BonesPerRoadSpline = frmMain.DGE.Model_GetNumberOfBones(Model_RoadTextureMap): RE
        SetVisibilityForAllChannels Instance_RoadSpline, False
        frmMain.DGE.Instance_SetUserDefinedData Instance_RoadSpline, 0, 0, 1, 0, 0: RE
        
        'Create the height map
        Terrain_GenerateRandomMap 512, 512, RandomSeed
        
        'Set water level
        Terrain_WaterLevel = NewWaterLevel
    End If
End Sub

Public Sub Terrain_ResetTextureBlending(NewGreen As Single, NewBlue As Single, NewAlpha As Single)
    PostEffects_Apply2ArgPostEffect Terrain_DrawSurface_HeightMap, PostEffect_Lerp, Terrain_DrawSurface_HeightMap, MakeVector4(0, NewGreen, NewBlue, NewAlpha), MakeVector4(1, NewGreen, NewBlue, NewAlpha)
End Sub

Public Sub Terrain_GenerateRandomMap(MapWidth As Long, MapHeight As Long, Seed As Integer)
    Dim I As Long
    Dim J As Long
    Dim JMod As Long
    Dim S As Long
    Dim HalfPixelOffsetU As Boolean
    Dim HalfPixelOffsetV As Boolean
    
    'Reset the random generator
    Random_Reset Seed
    
    'Delete any old stuff
    If Terrain_HeightField_RigidBody > 0 Then
        'The shapes must be deleted before it's rigid body and the connected CPU surface
        frmMain.DGE.RigidBody_Delete Terrain_HeightField_RigidBody: Terrain_HeightField_RigidBody = 0: RE
    End If
    If Terrain_HeightField_CollisionShape > 0 Then
        frmMain.DGE.CollisionShape_Delete Terrain_HeightField_CollisionShape: Terrain_HeightField_CollisionShape = 0: RE
    End If
    If Terrain_CPUSurface_HeightMap > 0 Then
        frmMain.DGE.CPUSurface_Delete Terrain_CPUSurface_HeightMap: Terrain_CPUSurface_HeightMap = 0: RE
    End If
    If Terrain_DrawSurface_HeightMap > 0 Then
        frmMain.DGE.DrawSurface_Delete Terrain_DrawSurface_HeightMap: Terrain_DrawSurface_HeightMap = 0: RE
    End If
    If Terrain_CPUSurface_RoadMap > 0 Then
        frmMain.DGE.CPUSurface_Delete Terrain_CPUSurface_RoadMap: Terrain_CPUSurface_RoadMap = 0: RE
    End If
    If Terrain_DrawSurface_RoadMap > 0 Then
        frmMain.DGE.DrawSurface_Delete Terrain_DrawSurface_RoadMap: Terrain_DrawSurface_RoadMap = 0: RE
    End If
    If Terrain_CPUSurface_ObstacleMap > 0 Then
        frmMain.DGE.CPUSurface_Delete Terrain_CPUSurface_ObstacleMap: Terrain_CPUSurface_ObstacleMap = 0: RE
    End If
    If Terrain_DrawSurface_ObstacleMap > 0 Then
        frmMain.DGE.DrawSurface_Delete Terrain_DrawSurface_ObstacleMap: Terrain_DrawSurface_ObstacleMap = 0: RE
    End If
    
    'Allocate draw surfaces
    Terrain_Map_Width = MapWidth
    Terrain_Map_Height = MapHeight
    HalfPixelOffsetU = Terrain_Map_Width Mod 2 = 0
    HalfPixelOffsetV = Terrain_Map_Height Mod 2 = 0
    Terrain_DrawSurface_HeightMap = frmMain.DGE.DrawSurface_CreateFixed(Terrain_Map_Width, Terrain_Map_Height, 0, True): RE
    Terrain_DrawSurface_RoadMap = frmMain.DGE.DrawSurface_CreateFixed(Terrain_Map_Width, Terrain_Map_Height, 0, False): RE
    Terrain_DrawSurface_ObstacleMap = frmMain.DGE.DrawSurface_CreateFixed(Terrain_Map_Width, Terrain_Map_Height, 0, True): RE
    
    'Let the player start in the middle of the map and sample from the center of each pixel to avoid non deterministic results.
    If HalfPixelOffsetU Then
        Terrain_UOffset = 0.5 + (0.5 / Terrain_Map_Width)
    Else
        Terrain_UOffset = 0.5
    End If
    If HalfPixelOffsetV Then
        Terrain_VOffset = 0.5 + (0.5 / Terrain_Map_Height)
    Else
        Terrain_VOffset = 0.5
    End If
    
    'Reset obstacle map
    frmMain.DGE.DrawSurface_FillWithColor Terrain_DrawSurface_ObstacleMap, -1, 10000, 0, 0: RE
    
    'Calculate how large the level is
    Terrain_USize = Terrain_Map_Width * Terrain_SmallestGridSize
    Terrain_VSize = Terrain_Map_Height * Terrain_SmallestGridSize
    
    'Temporary draw surfaces
    Dim Terrain_DrawSurface_RoadMask As Long
    Dim Terrain_DrawSurface_RoadHeight As Long
    Dim Terrain_DrawSurface_RoadMapBufferRG As Long
    Dim Terrain_DrawSurface_RoadMapBufferBA As Long
    Terrain_DrawSurface_RoadMapBufferRG = frmMain.DGE.DrawSurface_CreateFixed(Terrain_Map_Width, Terrain_Map_Height, 1, False): RE
    Terrain_DrawSurface_RoadMapBufferBA = frmMain.DGE.DrawSurface_CreateFixed(Terrain_Map_Width, Terrain_Map_Height, 1, True): RE
    
    'Create a flat ocean floor
    frmMain.DGE.DrawSurface_FillWithColor Terrain_DrawSurface_HeightMap, -1, 0.5, 0.5, 0: RE
    
    'Draw hills
    For I = 1 To MapWidth * MapHeight * 0.03
        Terrain_DrawBlob Random_GetFloat(-1, 1), Random_GetFloat(-1, 1), Random_GetNumber(6, 20), MakeVector3(Random_GetFloat(16, 20), Random_GetFloat(0.1, 0.9), Random_GetFloat(0.1, 0.9)), 0.1
    Next I
    
    'Simulate rain using circular erosion
    PostEffects_ApplySimplePostEffect Terrain_DrawSurface_HeightMap, PostEffect_ErodeHeightMap, Terrain_DrawSurface_HeightMap
    
    'Place the world camera
    frmMain.DGE.Camera_Place Terrain_WorldCamera, Terrain_MapUToWorldX(0.5), 10, -Terrain_MapVToWorldZ(0.5), Terrain_MapUToWorldX(0.5), 0, -Terrain_MapVToWorldZ(0.5), 0, 0, 1: RE
    frmMain.DGE.Camera_Place Terrain_WorldCamera, Terrain_MapUToWorldX(0.5) * 2, 10, -Terrain_MapVToWorldZ(0.5) * 2, Terrain_MapUToWorldX(0.5) * 2, 0, -Terrain_MapVToWorldZ(0.5) * 2, 0, 0, 1: RE
    frmMain.DGE.Camera_SetOrthogonal Terrain_WorldCamera, True: RE
    frmMain.DGE.Camera_SetHalfWidth Terrain_WorldCamera, Terrain_USize * 0.5: RE
    frmMain.DGE.Camera_SetHalfHeight Terrain_WorldCamera, Terrain_VSize * 0.5: RE
    
    'Clear the road map buffers
    frmMain.DGE.DrawSurface_FillWithColor Terrain_DrawSurface_RoadMapBufferRG, -1, -1, -1, -1: RE
    frmMain.DGE.DrawSurface_FillWithColor Terrain_DrawSurface_RoadMapBufferBA, -1, -1, -1, -1: RE
    frmMain.DGE.DrawSurface_ClearDepthBuffer Terrain_DrawSurface_RoadMapBufferRG: RE
    frmMain.DGE.DrawSurface_ClearDepthBuffer Terrain_DrawSurface_RoadMapBufferBA: RE
    
    Dim Position As Vector2
    Dim Angle As Single
    Dim Direction As Vector2
    Dim Bend As Single
    Dim R As Single
    Dim T As Single
    
    'Draw large roads
    RubberBand_GeneratePath MakeVector2((-Terrain_Map_Width / 3) * Terrain_SmallestGridSize, (-Terrain_Map_Height * 0.4) * Terrain_SmallestGridSize), MakeVector2((Terrain_Map_Width * 0.4) * Terrain_SmallestGridSize, (Terrain_Map_Height / 3) * Terrain_SmallestGridSize), 32, Int(((Min_Long(MapWidth, MapHeight) * Terrain_SmallestGridSize) ^ 2) * 0.0003), True
    RubberBand_Unloop 10
    RubberBand_Smooth 20, 0.2
    
    For I = 0 To RubberBand_NumberOfPoints - 1
        'Generate bone transformations
        For S = 0 To BonesPerRoadSpline - 1
            R = S / (BonesPerRoadSpline - 1)
            T = I + R
            Position = RubberBand_GetPointFromPath(T)
            Direction = RubberBand_GetDirectionFromPath(T)
            frmMain.DGE.BoneFrame_SetPosition BoneFrame_RoadSpline, S, Position.X, 0, Position.Y
            frmMain.DGE.BoneFrame_SetYAxis BoneFrame_RoadSpline, S, 0, 1, 0
            frmMain.DGE.BoneFrame_SetZAxis BoneFrame_RoadSpline, S, Direction.X * RoadWidth, 0, Direction.Y * RoadWidth
        Next S
        
        'Render the instance
        frmMain.DGE.Camera_RenderInstance Terrain_WorldCamera, Terrain_DrawSurface_RoadMapBufferBA, Instance_RoadSpline, 0, 0, 2, True, 1, 1, 1, 1, -1: RE
    Next I
    
    'Draw small roads
    For I = 2 To RubberBand_NumberOfPoints - 1
        If RubberBand_NumberOfPoints >= 10 Then
            For J = I + 5 To I + RubberBand_NumberOfPoints - 5
                JMod = J Mod RubberBand_NumberOfPoints
                If JMod > I Then
                    Dim TA As Single
                    Dim TD As Single
                    Dim PosA As Vector2
                    Dim PosB As Vector2
                    Dim PosC As Vector2
                    Dim PosD As Vector2
                    Dim RoadLength As Single
                    TA = I + Random_GetFloat(0, 1)
                    TD = JMod + Random_GetFloat(0, 1)
                    PosA = RubberBand_GetPointFromPath(TA)
                    PosD = RubberBand_GetPointFromPath(TD)
                    RoadLength = DistVector2(PosA, PosD)
                    If RoadLength < RubberBand_WantedLineLength * 1.4 Then
                        Dim DirA As Vector2
                            Dim DirA_R1 As Vector2
                            Dim DirA_R2 As Vector2
                        Dim DirD As Vector2
                            Dim DirD_R1 As Vector2
                            Dim DirD_R2 As Vector2
                        Dim DirEdge As Vector2
                        DirEdge = NormalVector2(ToVector2(PosA, PosD)) 'Edge direction from A to D
                        DirA = RubberBand_GetDirectionFromPath(TA)
                        DirD = RubberBand_GetDirectionFromPath(TD)
                        DirA_R1 = MakeVector2(DirA.Y, -DirA.X)
                        DirA_R2 = MakeVector2(-DirA.Y, DirA.X)
                        DirD_R1 = MakeVector2(DirD.Y, -DirD.X)
                        DirD_R2 = MakeVector2(-DirD.Y, DirD.X)
                        
                        Dim CanBuild As Boolean
                        CanBuild = True
                        
                        If DotProduct2(DirEdge, DirA_R1) > 0.85 Then
                            DirA = DirA_R1
                        ElseIf DotProduct2(DirEdge, DirA_R2) > 0.85 Then
                            DirA = DirA_R2
                        Else
                            CanBuild = False
                        End If
                        If DotProduct2(DirEdge, DirD_R1) > 0.85 Then
                            DirD = DirD_R1
                        ElseIf DotProduct2(DirEdge, DirD_R2) > 0.85 Then
                            DirD = DirD_R2
                        Else
                            CanBuild = False
                        End If
                        
                        If CanBuild Then
                            PosB = AddVector2(PosA, MulVector2(DirA, RubberBand_WantedLineLength * 0.4))
                            PosC = SubVector2(PosD, MulVector2(DirD, RubberBand_WantedLineLength * 0.4))
                            
                            'Generate bone transformations
                            For S = 0 To BonesPerRoadSpline - 1
                                R = S / (BonesPerRoadSpline - 1)
                                
                                Position = Vec2_Bezier4(PosA, PosB, PosC, PosD, R)
                                Direction = NormalVector2(ToVector2(Vec2_Bezier4(PosA, PosB, PosC, PosD, R - 0.001), Vec2_Bezier4(PosA, PosB, PosC, PosD, R + 0.001)))
                                frmMain.DGE.BoneFrame_SetPosition BoneFrame_RoadSpline, S, Position.X, 0, Position.Y
                                frmMain.DGE.BoneFrame_SetYAxis BoneFrame_RoadSpline, S, 0, 1, 0
                                frmMain.DGE.BoneFrame_SetZAxis BoneFrame_RoadSpline, S, Direction.X * RoadWidth, 0, Direction.Y * RoadWidth
                            Next S
                            
                            'Render the instance
                            frmMain.DGE.Camera_RenderInstance Terrain_WorldCamera, Terrain_DrawSurface_RoadMapBufferRG, Instance_RoadSpline, 0, 0, 2, True, 1, 1, 1, 1, -1: RE
                            
                            I = I + 2
                        End If
                    End If
                End If
            Next J
        End If
    Next I
    
    'Copy the result from the temporary buffers to the draw surface that will be used later
    PostEffects_Mix2Surfaces_SeparatedChannels Terrain_DrawSurface_RoadMapBufferRG, MakeVector4(1, 1, 0, 0), Terrain_DrawSurface_RoadMapBufferBA, MakeVector4(0, 0, 1, 1), Terrain_DrawSurface_RoadMap
    
    'Reuse the temporary road map buffers
    Terrain_DrawSurface_RoadMask = Terrain_DrawSurface_RoadMapBufferRG: Terrain_DrawSurface_RoadMapBufferRG = 0 'Reuse
    Terrain_DrawSurface_RoadHeight = Terrain_DrawSurface_RoadMapBufferBA: Terrain_DrawSurface_RoadMapBufferBA = 0 'Reuse
    
    'Generate the blend map to fill in the missing alpha channel that was used for transparency when drawing the terrain
    PostEffects_ApplySimplePostEffect Terrain_DrawSurface_HeightMap, PostEffect_GenerateBlendMap, Terrain_DrawSurface_HeightMap
    
    'Simulate wind using blur
    PostEffects_ApplyDiffuseBlur Terrain_DrawSurface_HeightMap, 0.8
    
    'Get the clamped height map and apply blur
    frmMain.DGE.PostEffect_GiveInputSurface 0, Terrain_DrawSurface_HeightMap, 0: RE 'Original image
    frmMain.DGE.PostEffect_GiveInputVector 0, MinimumRoadHeight, 0, 0, 0: RE 'Minimum
    frmMain.DGE.PostEffect_GiveInputVector 1, MaximumRoadHeight, 1, 1, 1: RE 'Maximum
    frmMain.DGE.PostEffect_RenderShader Terrain_DrawSurface_RoadHeight, PostEffect_Clamp
    PostEffects_ApplyDiffuseBlur Terrain_DrawSurface_RoadHeight, 5
    
    'Get the road presence
    frmMain.DGE.PostEffect_GiveInputSurface 0, Terrain_DrawSurface_RoadMap, 0: RE 'Road image
    frmMain.DGE.PostEffect_GiveInputVector 0, LargeRoadUCoordRatio + 0.2, SmallRoadUCoordRatio + 0.1, 0, 0: RE 'Tell the shader how wide the roads are
    frmMain.DGE.PostEffect_RenderShader Terrain_DrawSurface_RoadMask, PostEffect_RoadPresence: RE
    
    'Mix the masks using the red channel in Terrain_DrawSurface_RoadMask
    frmMain.DGE.PostEffect_GiveInputSurface 0, Terrain_DrawSurface_HeightMap, 0: RE
    frmMain.DGE.PostEffect_GiveInputSurface 1, Terrain_DrawSurface_RoadHeight, 0: RE
    frmMain.DGE.PostEffect_GiveInputSurface 2, Terrain_DrawSurface_RoadMask, 0: RE
    frmMain.DGE.PostEffect_RenderShader Terrain_DrawSurface_HeightMap, PostEffect_Blend2_Mask
    
    'Delete temporary road buffers
    frmMain.DGE.DrawSurface_Delete Terrain_DrawSurface_RoadMask: RE: Terrain_DrawSurface_RoadMask = 0 'Delete
    frmMain.DGE.DrawSurface_Delete Terrain_DrawSurface_RoadHeight: RE: Terrain_DrawSurface_RoadHeight = 0 'Delete
    
    'Make copies of the result on the CPU's memory
    Terrain_CPUSurface_HeightMap = frmMain.DGE.CPUSurface_CreateFixed(Terrain_Map_Width, Terrain_Map_Height, 0, 0, 0, 0): RE
    Terrain_CopyHeightMapToCPU
    Terrain_CPUSurface_RoadMap = frmMain.DGE.CPUSurface_CreateFixed(Terrain_Map_Width, Terrain_Map_Height, 0, 0, 0, 0): RE
    Terrain_CopyRoadMapToCPU
    
    'Allocate for later use
    Terrain_CPUSurface_ObstacleMap = frmMain.DGE.CPUSurface_CreateFixed(Terrain_Map_Width, Terrain_Map_Height, -1, 10000, 0, 0): RE
    
    'Reset the scheduler's data
    Terrain_ObstacleMapState = DTState_Complete
    Terrain_ObstacleMapHasChanged = True
    
    'Give information about how to use the map to the shaders.
    For I = 0 To 3
        'Give the height map
        frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_AdaptiveGround(I), 0, Terrain_DrawSurface_HeightMap: RE
        'Give the road map
        frmMain.DGE.Instance_SetTextureOverride Terrain_Instance_AdaptiveGround(I), 1, Terrain_DrawSurface_RoadMap: RE
        'Give arguments
        frmMain.DGE.Instance_SetUserDefinedData Terrain_Instance_AdaptiveGround(I), 0, Terrain_USize, Terrain_VSize, Terrain_UOffset, Terrain_VOffset: RE
        frmMain.DGE.Instance_SetUserDefinedData Terrain_Instance_AdaptiveGround(I), 1, Terrain_TextureScale, Terrain_TextureScale, 1 / Terrain_SmallestGridSize, 0: RE
    Next I
    
    'Create the height field for the bullet physics engine
    Terrain_HeightField_CollisionShape = frmMain.DGE.CollisionShape_Create_HeightField_UsingCPUSurface(Terrain_CPUSurface_HeightMap, 1, 0, 20, False, True): RE
    frmMain.DGE.CollisionShape_SetLocalScaling Terrain_HeightField_CollisionShape, Terrain_SmallestGridSize, 1, Terrain_SmallestGridSize: RE
    If HalfPixelOffsetU Then
        If HalfPixelOffsetV Then
            Terrain_HeightField_RigidBody = frmMain.DGE.RigidBody_Create_Static(Terrain_HeightField_CollisionShape, Terrain_SmallestGridSize / -2, 0, Terrain_SmallestGridSize / -2, 1, 0, 0, 0, 1, 0): RE
        Else
            Terrain_HeightField_RigidBody = frmMain.DGE.RigidBody_Create_Static(Terrain_HeightField_CollisionShape, Terrain_SmallestGridSize / -2, 0, 0, 1, 0, 0, 0, 1, 0): RE
        End If
    Else
        If HalfPixelOffsetV Then
            Terrain_HeightField_RigidBody = frmMain.DGE.RigidBody_Create_Static(Terrain_HeightField_CollisionShape, 0, 0, Terrain_SmallestGridSize / -2, 1, 0, 0, 0, 1, 0): RE
        Else
            Terrain_HeightField_RigidBody = frmMain.DGE.RigidBody_Create_Static(Terrain_HeightField_CollisionShape, 0, 0, 0, 1, 0, 0, 0, 1, 0): RE
        End If
    End If
    frmMain.DGE.RigidBody_SetRestitution Terrain_HeightField_RigidBody, DefaultRestitution: RE
    
    'Place the infinite planes at the ends of the heightfield
    Dim AABBMin As Vector3
    Dim AABBMax As Vector3
    frmMain.DGE.RigidBody_GetBoundingBoxMinimum_OutV3 Terrain_HeightField_RigidBody: AABBMin = GetVector3FromMatrixBuffer: RE
    frmMain.DGE.RigidBody_GetBoundingBoxMaximum_OutV3 Terrain_HeightField_RigidBody: AABBMax = GetVector3FromMatrixBuffer: RE
    frmMain.DGE.RigidBody_SetTransformation Terrain_EndPlanes_RigidBody(0), AABBMin.X, 0, 0, 0, 1, 0, 1, 0, 0: RE
    frmMain.DGE.RigidBody_SetTransformation Terrain_EndPlanes_RigidBody(1), AABBMax.X, 0, 0, 0, -1, 0, -1, 0, 0: RE
    frmMain.DGE.RigidBody_SetTransformation Terrain_EndPlanes_RigidBody(2), 0, 0, AABBMin.Z, -1, 0, 0, 0, 0, 1: RE
    frmMain.DGE.RigidBody_SetTransformation Terrain_EndPlanes_RigidBody(3), 0, 0, AABBMax.Z, -1, 0, 0, 0, 0, -1: RE
    
End Sub

Public Sub Terrain_CopyHeightMapToCPU()
    frmMain.DGE.CPUSurface_CopyRectFromDrawSurface Terrain_DrawSurface_HeightMap, Terrain_CPUSurface_HeightMap, 0, 0, 0, 0, Terrain_Map_Width, Terrain_Map_Height: RE
End Sub

Public Sub Terrain_CopyObstacleMapToCPU()
    frmMain.DGE.CPUSurface_CopyRectFromDrawSurface Terrain_DrawSurface_ObstacleMap, Terrain_CPUSurface_ObstacleMap, 0, 0, 0, 0, Terrain_Map_Width, Terrain_Map_Height: RE
End Sub

Public Sub Terrain_CopyRoadMapToCPU()
    frmMain.DGE.CPUSurface_CopyRectFromDrawSurface Terrain_DrawSurface_RoadMap, Terrain_CPUSurface_RoadMap, 0, 0, 0, 0, Terrain_Map_Width, Terrain_Map_Height: RE
End Sub

Public Sub Terrain_FollowCamera(Pos As Vector3)
    Dim I As Long
    
    'Move terrain
    For I = 0 To 3
        frmMain.DGE.Instance_SetPosition Terrain_Instance_AdaptiveGround(I), Int((Pos.X / Terrain_PositionRounding) + 0.5) * Terrain_PositionRounding, 0, Int((Pos.Z / Terrain_PositionRounding) + 0.5) * Terrain_PositionRounding: RE
    Next I
    
    'Move water surface
    frmMain.DGE.Instance_SetPosition Terrain_Instance_WaterSurface, Pos.X, Terrain_WaterLevel, Pos.Z: RE
End Sub

Public Sub Terrain_AnimateWater(AnimationTime As Single)
    Dim WaveOffsetU1 As Single
    Dim WaveOffsetV1 As Single
    Dim WaveOffsetU2 As Single
    Dim WaveOffsetV2 As Single
    Dim WaveScale As Single
    Dim WaveIntensity As Single
    WaveScale = 8
    WaveIntensity = 0.5
    WaveOffsetU1 = AnimationTime
    WaveOffsetV1 = 0
    WaveOffsetU2 = -AnimationTime
    WaveOffsetV2 = WaveScale / 2
    frmMain.DGE.Instance_SetUserDefinedData Terrain_Instance_WaterSurface, 0, WaveOffsetU1, WaveOffsetV1, WaveOffsetU2, WaveOffsetV2: RE
    frmMain.DGE.Instance_SetUserDefinedData Terrain_Instance_WaterSurface, 1, WaveScale, WaveIntensity, 0, 0: RE
End Sub

'This method is sampling the height map using a copy on the CPU's memory.
Public Function Terrain_GetHeightMapNN(X As Single, Z As Single) As Vector4
    Dim U As Single
    Dim V As Single
    Dim PX As Single
    Dim PY As Single
    
    'Convert from world space position to pixel coordinates
    U = ((X / Terrain_USize) + Terrain_UOffset)
    V = ((Z / Terrain_VSize) + Terrain_VOffset)
    PX = U * (Terrain_Map_Width - 1)
    PY = V * (Terrain_Map_Height - 1)
    
    'Sample the nearest neighbour
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, Int(PX + 0.5), Int(PY + 0.5): RE: Terrain_GetHeightMapNN = GetVector4FromMatrixBuffer
End Function

'This method is sampling the road map directly from the graphics card.
Public Function Terrain_GetRoadMapNN(X As Single, Z As Single) As Vector4
    Dim U As Single
    Dim V As Single
    Dim PX As Single
    Dim PY As Single
    
    'Convert from world space position to pixel coordinates
    U = ((X / Terrain_USize) + Terrain_UOffset)
    V = ((Z / Terrain_VSize) + Terrain_VOffset)
    PX = U * (Terrain_Map_Width - 1)
    PY = V * (Terrain_Map_Height - 1)
    
    'Sample the nearest neighbour
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_RoadMap, Int(PX + 0.5), Int(PY + 0.5): RE: Terrain_GetRoadMapNN = GetVector4FromMatrixBuffer
End Function

'This method is sampling the heightmap using a copy on the CPU's memory.
Public Function Terrain_GetHeightLinear(X As Single, Z As Single) As Single
    Dim U As Single
    Dim V As Single
    Dim PX As Single
    Dim PY As Single
    Dim RPX As Integer
    Dim RPY As Integer
    Dim MPX As Single
    Dim MPY As Single
    Dim Sample(0 To 3) As Single
    Dim HeightValue As Single
    
    'Convert from world space position to pixel coordinates
    U = ((X / Terrain_USize) + Terrain_UOffset)
    V = ((Z / Terrain_VSize) + Terrain_VOffset)
    PX = U * (Terrain_Map_Width - 1)
    PY = V * (Terrain_Map_Height - 1)
    
    'Round and get the remainders for sampling and interpolation
    RPX = Int(PX)
    RPY = Int(PY)
    MPX = PX - RPX
    MPY = PY - RPY
    
    'Sample 4 pixels and take the red channel
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, RPX, RPY: RE: Sample(0) = frmMain.DGE.GetX1
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, RPX, RPY + 1: RE: Sample(1) = frmMain.DGE.GetX1
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, RPX + 1, RPY: RE: Sample(2) = frmMain.DGE.GetX1
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, RPX + 1, RPY + 1: RE: Sample(3) = frmMain.DGE.GetX1
    
    'Use linear interpolation on the 4 height values
    HeightValue = Lerp(Lerp(Sample(0), Sample(1), MPY), Lerp(Sample(2), Sample(3), MPY), MPX)
    
    'Convert from heightmap value to world space height
    Terrain_GetHeightLinear = HeightValue
End Function

'This method is sampling the heightmap using a copy on the CPU's memory.
Public Function Terrain_GetRoughness(X As Single, Z As Single) As Single
    Dim U As Single
    Dim V As Single
    Dim PX As Single
    Dim PY As Single
    Dim RPX As Integer
    Dim RPY As Integer
    Dim Sample(0 To 3) As Single
    Dim HeightValue As Single
    
    'Convert from world space position to pixel coordinates
    U = ((X / Terrain_USize) + Terrain_UOffset)
    V = ((Z / Terrain_VSize) + Terrain_VOffset)
    PX = U * (Terrain_Map_Width - 1)
    PY = V * (Terrain_Map_Height - 1)
    
    'Round for sampling
    RPX = Int(PX)
    RPY = Int(PY)
    
    'Sample 4 pixels and take the red channel
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, RPX, RPY: RE: Sample(0) = frmMain.DGE.GetX1
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, RPX, RPY + 1: RE: Sample(1) = frmMain.DGE.GetX1
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, RPX + 1, RPY: RE: Sample(2) = frmMain.DGE.GetX1
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, RPX + 1, RPY + 1: RE: Sample(3) = frmMain.DGE.GetX1
    
    'Return the tile's difference between it's lowest and highest point
    Terrain_GetRoughness = (Max_Float(Max_Float(Sample(0), Sample(1)), Max_Float(Sample(2), Sample(3))) - Min_Float(Min_Float(Sample(0), Sample(1)), Min_Float(Sample(2), Sample(3))))
End Function

'Pixel coordinates
Public Function Terrain_WorldToMapX(ByVal WorldX As Single) As Single
    Terrain_WorldToMapX = ((WorldX / Terrain_USize) + Terrain_UOffset) * (Terrain_Map_Width - 1)
End Function

'Pixel coordinates
Public Function Terrain_WorldToMapZ(ByVal WorldZ As Single) As Single
    Terrain_WorldToMapZ = ((WorldZ / Terrain_VSize) + Terrain_VOffset) * (Terrain_Map_Height - 1)
End Function

'Pixel coordinates
Public Function Terrain_MapToWorldX(ByVal MapX As Single) As Single
    Terrain_MapToWorldX = ((MapX / (Terrain_Map_Width - 1)) - Terrain_UOffset) * Terrain_USize
End Function

'Pixel coordinates
Public Function Terrain_MapToWorldZ(ByVal MapZ As Single) As Single
    Terrain_MapToWorldZ = ((MapZ / (Terrain_Map_Height - 1)) - Terrain_VOffset) * Terrain_VSize
End Function

'UV coordinates
Public Function Terrain_WorldXToMapU(ByVal WorldX As Single) As Single
    Terrain_WorldXToMapU = (WorldX / Terrain_USize) + Terrain_UOffset
End Function

'UV coordinates
Public Function Terrain_WorldZToMapV(ByVal WorldZ As Single) As Single
    Terrain_WorldZToMapV = (WorldZ / Terrain_VSize) + Terrain_VOffset
End Function

'UV coordinates
Public Function Terrain_MapUToWorldX(ByVal MapU As Single) As Single
    Terrain_MapUToWorldX = (MapU - Terrain_UOffset) * Terrain_USize
End Function

'UV coordinates
Public Function Terrain_MapVToWorldZ(ByVal MapV As Single) As Single
    Terrain_MapVToWorldZ = (MapV - Terrain_VOffset) * Terrain_VSize
End Function

Public Sub Terrain_DrawBlob(CenterU As Single, CenterV As Single, Radius As Long, Color As Vector3, Opacity As Single)
    Dim X As Single
    Dim Y As Single
    Dim W As Single
    Dim H As Single
    X = (CenterU * 2) - 1
    Y = (CenterV * 2) - 1
    W = Radius * 2 / Terrain_Map_Width
    H = Radius * 2 / Terrain_Map_Height
    If X - W > -1 And X + W < 1 And Y - H > -1 And Y + H < 1 Then
        frmMain.DGE.Draw_GiveInputColor Color.X, Color.Y, Color.Z, Opacity: RE
        frmMain.DGE.Draw_RenderQuad Terrain_DrawSurface_HeightMap, DrawShader_Circle_Fade_Linear, X, Y, W, 0, 0, H, 1: RE
    End If
End Sub

Public Sub Terrain_Explode(CenterX As Single, CenterY As Single, CenterZ As Single, Radius As Long)
    Dim RoundedCenterX As Long
    Dim RoundedCenterZ As Long
    Dim X As Long
    Dim Z As Long
    Dim OldHeight As Single
    Dim NewHeight As Single
    Dim Intensity As Single
    Dim Dist As Single
    Dim Burned As Boolean
    RoundedCenterX = Int(Terrain_WorldToMapX(CenterX))
    RoundedCenterZ = Int(Terrain_WorldToMapZ(CenterZ))
    Burned = False
    For X = Max_Float(0, CenterX - Radius - 1) To Min_Float(Terrain_Map_Width - 1, RoundedCenterX + Radius)
        For Z = Max_Float(0, CenterZ - Radius - 1) To Min_Float(Terrain_Map_Height - 1, RoundedCenterZ + Radius)
            Dist = Sqr(((X - RoundedCenterX) ^ 2) + ((Z - RoundedCenterZ) ^ 2))
            If Dist < Radius Then
                frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_HeightMap, X, Z: RE: OldHeight = frmMain.DGE.GetX1
                NewHeight = CenterY - (Sqr(Saturate(1 - ((Dist / Radius) ^ 2))) * Radius * Terrain_SmallestGridSize)
                If NewHeight < OldHeight Then
                    frmMain.DGE.DrawSurface_SetPixelColor Terrain_DrawSurface_HeightMap, X, Z, NewHeight, 0, 0, 0.2: RE
                    frmMain.DGE.CPUSurface_SetPixelColor_Warning Terrain_CPUSurface_HeightMap, X, Z, NewHeight, 0, 0, 0.2: RE
                    Particle_Create MakeVector3(Terrain_MapToWorldX(X), OldHeight, Terrain_MapToWorldZ(Z)), MakeVector3((Rnd - 0.5) * 30, 10 + (Rnd * 50), (Rnd - 0.5) * 30), 0, GetRandomStoneParticle, -1
                    Burned = True
                End If
            End If
        Next Z
    Next X
    If Burned Then
        Item_Delete_InsideCircle CenterX, CenterZ, (Radius + 1) * Terrain_SmallestGridSize
        Terrain_ObstacleMapHasChanged = True
    End If
End Sub

Public Sub Terrain_SetObstacle(X As Long, Z As Long, Radius As Single)
    frmMain.DGE.DrawSurface_SetPixelColor Terrain_DrawSurface_ObstacleMap, X, Z, Radius, -Radius, X + 0.5, Z + 0.5: RE
    frmMain.DGE.CPUSurface_SetPixelColor_Warning Terrain_CPUSurface_ObstacleMap, X, Z, Radius, -Radius, X + 0.5, Z + 0.5: RE
    Terrain_ObstacleMapHasChanged = True
End Sub

Public Sub Terrain_ClearObstacle(X As Long, Z As Long)
    frmMain.DGE.DrawSurface_SetPixelColor Terrain_DrawSurface_ObstacleMap, X, Z, -1, 10000, X + 0.5, Z + 0.5: RE
    frmMain.DGE.CPUSurface_SetPixelColor_Warning Terrain_CPUSurface_ObstacleMap, X, Z, -1, 10000, X + 0.5, Z + 0.5: RE
    Terrain_ObstacleMapHasChanged = True
End Sub

Public Function Terrain_GetObstacle(X As Long, Z As Long) As Single
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_ObstacleMap, X, Z: Terrain_GetObstacle = frmMain.DGE.GetX1: RE
End Function

Public Function Terrain_GetObstacleMap(Position As Vector3) As Vector4
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_ObstacleMap, Int((Terrain_WorldXToMapU(Position.X) * Terrain_Map_Width) + 0.5), Int((Terrain_WorldZToMapV(Position.Z) * Terrain_Map_Height) + 0.5): Terrain_GetObstacleMap = GetVector4FromMatrixBuffer: RE
End Function

Public Function Terrain_GetDistanceToClosestObstacle(Position As Vector3) As Single
    Terrain_GetDistanceToClosestObstacle = Terrain_GetObstacleMap(Position).Y
End Function

Public Function Terrain_GetClosestObstacle(Position As Vector3) As Vector3
    Dim ObstacleMap As Vector4
    ObstacleMap = Terrain_GetObstacleMap(Position)
    Terrain_GetClosestObstacle.X = Terrain_MapUToWorldX(ObstacleMap.Z / Terrain_Map_Width)
    Terrain_GetClosestObstacle.Z = Terrain_MapVToWorldZ(ObstacleMap.W / Terrain_Map_Height)
    Terrain_GetClosestObstacle.Y = Terrain_GetHeightLinear(Terrain_GetClosestObstacle.X, Terrain_GetClosestObstacle.Z) + 1
End Function

Public Sub Terrain_Scheduler()
    If Terrain_ObstacleMapHasChanged Then
        If Terrain_ObstacleMapState = DTState_Complete Then
            Task_Obstacle DTState_Reset
        Else
            Task_Obstacle Terrain_ObstacleMapState + 1
        End If
        If Terrain_ObstacleMapState = DTState_Complete Then
            Terrain_ObstacleMapHasChanged = False
        End If
    End If
End Sub

Public Sub Task_Obstacle(TaskIndex As Long)
    Select Case TaskIndex
    Case DTState_Reset
        'Clear the distance transform without touching the red channel using a post effect
        frmMain.DGE.PostEffect_GiveInputSurface 0, Terrain_DrawSurface_ObstacleMap, 0: RE
        frmMain.DGE.PostEffect_GiveInputSurface 1, Terrain_DrawSurface_HeightMap, 0: RE
        frmMain.DGE.PostEffect_RenderShader Terrain_DrawSurface_ObstacleMap, PE_DistanceTransform_Reset: RE
    Case DTState_Complete
        'Load to CPU buffer
        Terrain_CopyObstacleMapToCPU
    Case Else
        'Iterate one step to claculate the distance and inherit the coordinate of the closest obstacle
        'The value in the red channel store the radius or -1
        '   Any radius will give a negative distance transform on it's location
        PostEffects_ApplySimplePostEffect Terrain_DrawSurface_ObstacleMap, PE_DistanceTransform_Iterate, Terrain_DrawSurface_ObstacleMap
    End Select
    Terrain_ObstacleMapState = TaskIndex
End Sub

Public Function Terrain_OnRoad(X As Single, Z As Single) As Boolean
    Dim U As Single
    Dim V As Single
    Dim PX As Single
    Dim PY As Single
    Dim IPX As Long
    Dim IPY As Long
    Dim MPX As Single
    Dim MPY As Single
    
    'Convert from world space position to pixel coordinates
    U = ((X / Terrain_USize) + Terrain_UOffset)
    V = ((Z / Terrain_VSize) + Terrain_VOffset)
    PX = U * (Terrain_Map_Width - 1)
    PY = V * (Terrain_Map_Height - 1)
    IPX = Int(PX)
    IPY = Int(PY)
    MPX = PX - IPX
    MPY = PY - IPY
    
    'Sample 4 pixels
    Dim RoadMapColor_LL As Vector4
    Dim RoadMapColor_LH As Vector4
    Dim RoadMapColor_HL As Vector4
    Dim RoadMapColor_HH As Vector4
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_RoadMap, IPX, IPY: RE: RoadMapColor_LL = GetVector4FromMatrixBuffer
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_RoadMap, IPX, IPY + 1: RE: RoadMapColor_LH = GetVector4FromMatrixBuffer
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_RoadMap, IPX + 1, IPY: RE: RoadMapColor_HL = GetVector4FromMatrixBuffer
    frmMain.DGE.CPUSurface_GetPixelColor_Clamped_OutV4 Terrain_CPUSurface_RoadMap, IPX + 1, IPY + 1: RE: RoadMapColor_HH = GetVector4FromMatrixBuffer
    
    'Use bilinear interpolation
    Dim RoadMapColor As Vector4
    RoadMapColor = LerpVector4(LerpVector4(RoadMapColor_LL, RoadMapColor_HL, MPX), LerpVector4(RoadMapColor_LH, RoadMapColor_HH, MPX), MPY)
    
    'Apply tresholding
    Terrain_OnRoad = False
    
    'Find large roads
    If RoadMapColor_LL.Z > -0.5 And RoadMapColor_LH.Z > -0.5 And RoadMapColor_HL.Z > -0.5 And RoadMapColor_HH.Z > -0.5 Then
        If RoadMapColor.Z > 0.5 - (LargeRoadUCoordRatio / 2) And RoadMapColor.Z < 0.5 + (LargeRoadUCoordRatio / 2) Then
            Terrain_OnRoad = True
            Exit Function
        End If
    End If
    
    'Find small roads
    If RoadMapColor_LL.X > -0.5 And RoadMapColor_LH.X > -0.5 And RoadMapColor_HL.X > -0.5 And RoadMapColor_HH.X > -0.5 Then
        If RoadMapColor.X > 0.5 - (SmallRoadUCoordRatio / 2) And RoadMapColor.X < 0.5 + (SmallRoadUCoordRatio / 2) Then
            Terrain_OnRoad = True
            Exit Function
        End If
    End If
End Function
