;
; ------------------------------------------------------------
;
;   PureBasic - Terrain : Terrain Blend change
;
;    (c) Fantaisie Software
;
; ------------------------------------------------------------
;

; Use left button to select an area, then use PageUp or PageDown to change terrain's blend map

IncludeFile #PB_Compiler_Home + "examples/3d/Screen3DRequester.pb"

#CameraSpeed = 1
#TerrainMiniX = 0
#TerrainMiniY = 0
#TerrainMaxiX = 0
#TerrainMaxiY = 0

#NbSommet = 36
#SizeCircle = 70

Define.f KeyX, KeyY, MouseX, MouseY, TimeSinceLastFrame
Define Selected

Declare InitBlendMaps()
Declare Clamp(*var.float, min.f, max.f)
Declare AffichePolygone(Px.f, Py.f, Pz.f, Rayon.f)
Declare DoTerrainModify(tx, ty, wx.f, wy.f, wz.f, mBrushSizeTerrainSpace.f, height.f)

; OpenGL needs to have CG enabled to work (Linux and OS X have OpenGL by default)
;
CompilerIf #PB_Compiler_OS <> #PB_OS_Windows Or Subsystem("OpenGL")
  Flags = #PB_Engine3D_EnableCG
CompilerEndIf

If InitEngine3D(Flags)
  
  InitSprite()
  InitKeyboard()
  InitMouse()
  
  If Screen3DRequester()
    Add3DArchive(#PB_Compiler_Home + "examples/3d/Data/Textures/", #PB_3DArchive_FileSystem)
    Add3DArchive(#PB_Compiler_Home + "examples/3d/Data/Textures/nvidia", #PB_3DArchive_FileSystem)
    Add3DArchive(#PB_Compiler_Home + "examples/3d/Data/GUI", #PB_3DArchive_FileSystem)
    Add3DArchive(#PB_Compiler_Home + "examples/3d/Data/Packs/desert.zip", #PB_3DArchive_Zip)
    Parse3DScripts()
    
    MaterialFilteringMode(#PB_Default, #PB_Material_Anisotropic, 8)
    
    ;- Light
    ;
 
    light = CreateLight(#PB_Any ,RGB(185, 185, 185),4000,1200, 1000,#PB_Light_Directional)
    SetLightColor(light, #PB_Light_SpecularColor, RGB(255*0.4, 255*0.4,255*0.4))
    LightDirection(light, 0.55, -0.3, -0.75)
    
    AmbientColor(RGB(5, 5,5))
    
    ;- Camera
    ;
    CreateCamera(0, 0, 0, 100, 100)
    MoveCamera(0,  2000, 100, 1700, #PB_Absolute)
    CameraRange(0, 1, 0)
    
    ;----------------------------------
    ; terrain definition
    SetupTerrains(LightID(Light), 3000, #PB_Terrain_NormalMapping)
    ; initialize terrain
    CreateTerrain(0, 513, 12000, 600, 3, "TerrainBlend", "dat")
    ; set all texture will be use when terrrain will be constructed
    AddTerrainTexture(0,  0, 100, "dirt_grayrocky_diffusespecular.jpg",  "dirt_grayrocky_normalheight.jpg")
    AddTerrainTexture(0,  1,  30, "grass_green-01_diffusespecular.jpg", "grass_green-01_normalheight.jpg")
    AddTerrainTexture(0,  2, 200, "growth_weirdfungus-03_diffusespecular.jpg", "growth_weirdfungus-03_normalheight.jpg")
    
    ;- define terrains
    For ty = #TerrainMiniY To #TerrainMaxiY
      For tx = #TerrainMiniX To #TerrainMaxiX
        Imported = DefineTerrainTile(0, tx, ty, "terrain513.png", ty % 2, tx % 2)
      Next
    Next
    BuildTerrain(0)
    
    If Imported = #True
      InitBlendMaps()
      UpdateTerrain(0)
      
      ; If enabled, it will save the terrain as a (big) cache for a faster load next time the program is executed
      ; SaveTerrain(0, #False)
    EndIf
    
    ; SkyBox
    ;
    SkyBox("desert07.jpg")
    
    ShowGUI(128, 1)
    
    Repeat
      
      Screen3DEvents()
      
      If ExamineMouse()
        MouseX = -(MouseDeltaX()/10)
        MouseY = -(MouseDeltaY()/10)
        InputEvent3D(MouseX(), MouseY(), MouseButton(#PB_MouseButton_Left))
        
        TerrainMousePick(0, CameraID(0), MouseX(),  MouseY())
        
        If MouseButton(#PB_MouseButton_Left)
          ShowGUI(128, 1)
          AffichePolygone(PickX(), PickY(), PickZ(), 100)
          Selected = #True
        Else
          ShowGUI(128, 0)
          MoveCamera  (0, KeyX, 0, KeyY)
          AffichePolygone(PickX(), PickY(), PickZ(), 0)
          Selected = #False
          If CameraY(0) < TerrainHeight(0, CameraX(0), CameraZ(0)) + 10
            MoveCamera(0, CameraX(0), TerrainHeight(0, CameraX(0), CameraZ(0)) + 10, CameraZ(0), #PB_Absolute)
          EndIf
          RotateCamera(0,  MouseY, MouseX, 0, #PB_Relative)
          
        EndIf
        
      EndIf
            
      If ExamineKeyboard()
        
        If KeyboardPushed(#PB_Key_Left)
          KeyX = -#CameraSpeed
        ElseIf KeyboardPushed(#PB_Key_Right)
          KeyX = #CameraSpeed
        Else
          KeyX = 0
        EndIf
        
        If KeyboardPushed(#PB_Key_Up)
          KeyY = -#CameraSpeed
        ElseIf KeyboardPushed(#PB_Key_Down)
          KeyY = #CameraSpeed
        Else
          KeyY = 0
        EndIf
        
        Modify = 0
        If KeyboardPushed(#PB_Key_PageUp)
          Modify = 1
        ElseIf  KeyboardPushed(#PB_Key_PageDown)
          Modify = -1
        EndIf
      EndIf
      
      If Modify And Selected
        
        For ty = #TerrainMiniY To #TerrainMaxiY
          For tx = #TerrainMiniX To #TerrainMaxiX
            DoTerrainModify(tx, ty, PickX(), PickY(), PickZ(), 0.02, TimeSinceLastFrame * Modify)
          Next
        Next
      EndIf
      
      TimeSinceLastFrame = RenderWorld() / 1000
      FlipBuffers()
      
    Until KeyboardPushed(#PB_Key_Escape)
    
    End
    
  EndIf
Else
  CompilerIf #PB_Compiler_OS <> #PB_OS_Windows Or Subsystem("OpenGL")
    ;
    ; Terrain on Linux/OSX and Windows with OpenGL needs CG toolkit from nvidia
    ; It can be freely downloaded and installed from this site: https://developer.nvidia.com/cg-toolkit-download
    ;
    MessageRequester("Error","Can't initialize engine3D (Please ensures than CG Toolkit from nvidia is correcly installed)")
  CompilerElse
    MessageRequester("Error","Can't initialize engine3D")
  CompilerEndIf
EndIf

Procedure InitBlendMaps()
  minHeight0.f = 70
  fadeDist0.f = 40
  minHeight1.f = 70
  fadeDist1.f = 15
  For ty = #TerrainMiniY To #TerrainMaxiY
    For tx = #TerrainMiniX To #TerrainMaxiX
      Size = TerrainTileLayerMapSize(0, tx, ty)
      For y = 0 To Size-1
        For x = 0 To Size-1
          Height.f = TerrainTileHeightAtPosition(0, tx, ty, 1, x, y)
          val.f = (Height - minHeight0) / fadeDist0
          Clamp(@val, 0, 1)
          ;SetTerrainTileLayerBlend(0, tx, ty, 1, x, y, val)
          val.f = (Height - minHeight1) / fadeDist1
          Clamp(@val, 0, 1)
          SetTerrainTileLayerBlend(0, tx, ty, 2, x, y, val)
        Next
      Next
      ;UpdateTerrainTileLayerBlend(0, tx, ty, 1)
      UpdateTerrainTileLayerBlend(0, tx, ty, 2)
    Next
  Next
EndProcedure

Procedure Clamp(*var.float, min.f, max.f)
  If *var\f < min
    *var\f = min
  ElseIf *var\f > max
    *var\f = max
  EndIf
EndProcedure

Procedure.f Min(a.f, b.f)
  If a < b
    ProcedureReturn a
  Else
    ProcedureReturn b
  EndIf
EndProcedure

Procedure.f Max(a.f, b.f)
  If a > b
    ProcedureReturn a
  Else
    ProcedureReturn b
  EndIf
EndProcedure

Procedure DoTerrainModify(tx, ty, wx.f, wy.f, wz.f, mBrushSizeTerrainSpace.f, TimeElapsed.f)
  imgSize.f = TerrainTileLayerMapSize(0, tx, ty)
  Pointx.f = TerrainTilePointX(0, tx, ty, wx, wy, wz)
  Pointy.f = TerrainTilePointY(0, tx, ty, wx, wy, wz)
  
  startx = (Pointx - mBrushSizeTerrainSpace) * imgSize
  starty = (Pointy - mBrushSizeTerrainSpace) * imgSize
  endx   = (Pointx + mBrushSizeTerrainSpace) * imgSize
  endy   = (Pointy + mBrushSizeTerrainSpace) * imgSize
  
  startx = Max(startx, 0)
  starty = Max(starty, 0)
  endx   = Min(endx, imgSize)
  endy   = Min(endy, imgSize)
  
  For y = starty To endy
    For x = startx To endx
      
      tsXdist.f = (x / imgSize) - Pointx
      tsYdist.f = (y / imgSize) - Pointy
      
      weight.f = Min(1.0,	Sqr(tsYdist * tsYdist + tsXdist * tsXdist) / (0.5 * mBrushSizeTerrainSpace))
      weight = 1.0 - (weight * weight)
      paint.f = weight * TimeElapsed
      imgY = imgSize - y
      val.f = GetTerrainTileLayerBlend(0, tx, ty, 1, x, imgY) + paint
      Clamp(@val, 0, 1)
      SetTerrainTileLayerBlend(0, tx, ty, 1, x, imgY, val)
      
    Next x
  Next y
  UpdateTerrainTileLayerBlend(0, tx, ty, 1)
EndProcedure

Procedure AffichePolygone(Px.f, Py.f, Pz.f, Rayon.f)
  Define.l i, Color = RGB(255, 255, 0)
  Define.f Angle, Sx, Sy, Sz, Sx1, Sy1, Sz1, Py1, Delta = 2
  
  For i = 0 To #NbSommet-2
    Sx = Cos(Angle) * Rayon
    Sz = Sin(Angle) * Rayon
    Angle + 2.0 * #PI / #NbSommet
    Sx1 = Cos(Angle) * Rayon
    Sz1 = Sin(Angle) * Rayon
    Py = TerrainHeight(0, Px + Sx, Pz + Sz) + Delta
    Py1 = TerrainHeight(0, Px + Sx1, Pz + Sz1) + Delta
    CreateLine3D(i, Px + Sx, Py, Pz + Sz, Color, Px + Sx1, Py1, Pz + Sz1, Color)
  Next i
  Py = TerrainHeight(0, Px + Cos(0) * Rayon, Pz + Sin(0) * Rayon) + Delta
  CreateLine3D(i, Px + Sx1, Py1, Pz + Sz1, Color, Px + Cos(0) * Rayon, Py, Pz + Sin(0) * Rayon, Color)
EndProcedure