;
; ------------------------------------------------------------
;
;   PureBasic - Terrain : Physic
;
;    (c) Fantaisie Software
;
; ------------------------------------------------------------
;

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

#TerrainMiniX = 0
#TerrainMiniY = 0
#TerrainMaxiX = 0
#TerrainMaxiY = 0
#PlayerSpeed = 60
#CameraSpeed = 10

Define.f KeyX, KeyY, MouseX, MouseY, TimeSinceLastFrame
Declare InitBlendMaps()

Structure Vector3
  x.f
  y.f
  z.f
EndStructure

Structure s_Key
  Up.i
  Down.i
  Left.i
  Right.i
  StrafeLeft.i
  StrafeRight.i
  Jump.i
EndStructure

Structure s_Entity
  Entity.i
  EntityBody.i
  BodyOffsetY.f 
  elapsedTime.f
  Key.s_Key
  MainNode.i  
  SightNode.i
  CameraNode.i  
  ForwardNode.i
  StrafeNode.i
EndStructure

Structure s_Camera
  Camera.i
  Tightness.f
  CameraNode.i 
  TargetNode.i
EndStructure    

Macro GetNodePosition(Position, Node)
  Position\x = NodeX(Node)  
  Position\y = NodeY(Node)  
  Position\z = NodeZ(Node)  
EndMacro

Macro SubVector3(V, V1, V2)
  V\x = V1\x - V2\x
  V\y = V1\y - V2\y
  V\z = V1\z - V2\z
EndMacro

;-Declare
Declare HandleEntity(*Entity.s_Entity)
Declare CameraTrack(*Camera.s_Camera, *Entity.s_Entity)
Declare OnGround(*Entity.s_Entity)


Define Robot.s_Entity
Define Camera.s_Camera

; 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/Models"          , #PB_3DArchive_FileSystem)
    Add3DArchive(#PB_Compiler_Home + "examples/3d/Data/Scripts"         , #PB_3DArchive_FileSystem)
    Add3DArchive(#PB_Compiler_Home + "examples/3d/Data/Textures/nvidia" , #PB_3DArchive_FileSystem)
    Add3DArchive(#PB_Compiler_Home + "examples/3d/Data/Packs/desert.zip", #PB_3DArchive_Zip)
    Parse3DScripts()
    
    WorldShadows(#PB_Shadow_Modulative, #PB_Default, RGB(120, 120, 120))
    
    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)
    CameraBackColor(0, RGB(5, 5, 10))
    
    ;----------------------------------
    ;-terrain definition
    SetupTerrains(LightID(Light), 3000, #PB_Terrain_NormalMapping)
    ;-initialize terrain 
    CreateTerrain(0, 513, 12000, 600, 3, "TerrainPhysic", "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")
    
    ;-Construct 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 
    
    ; enable shadow terrain
    TerrainRenderMode(0, 0)
    
    ;Add Physic Body
    CreateTerrainBody(0, 0.1, 1)
    
    ;Texture
    CreateTexture(1, 256, 256)
    StartDrawing(TextureOutput(1))
    Box(0, 0, 256, 256, $002255)
    DrawingMode(#PB_2DDrawing_Outlined)
    Box(0, 0, 256, 256, $FFFFFF)
    Box(10, 10, 236, 236, $FFFF)
    StopDrawing()
    
    ;Material
    CreateMaterial(0, LoadTexture(0, "r2skin.jpg"))
    CreateMaterial(1, TextureID(1))
    CreateMaterial(2, LoadTexture(2, "Dirt.jpg"))
    CreateMaterial(3, LoadTexture(3, "Wood.jpg"))
    GetScriptMaterial(4, "Scene/GroundBlend")
    
    ;Robot
    LoadMesh   (0, "robot.mesh")
    CreateEntity (0, MeshID(0), #PB_Material_None);
    StartEntityAnimation(0, "Walk")
    
    ;Robot Body
    CreateEntity(1, MeshID(0), #PB_Material_None, 0, 426, 0)
    HideEntity(1, 1)
    
    ;Body
    CreateEntityBody(1, #PB_Entity_CapsuleBody, 1, 0, 0) 
    
    ; Skybox
    SkyBox("desert07.jpg")
    
    ;
    With Robot
      \Entity = 0
      \EntityBody = 1
      \BodyOffsetY = 43 
      
      \Key\Down        = #PB_Key_Down
      \Key\Left        = #PB_Key_Left
      \Key\Right       = #PB_Key_Right
      \Key\Up          = #PB_Key_Up
      \Key\StrafeLeft  = #PB_Key_X
      \Key\StrafeRight = #PB_Key_C
      \Key\Jump        = #PB_Key_Space
      
      \MainNode    = CreateNode(#PB_Any) ; Entity position
      \SightNode   = CreateNode(#PB_Any,  120,  20,  0) ; For cameraLookAt 
      \CameraNode  = CreateNode(#PB_Any, -140, 100,  0) ; Camera position
      \ForwardNode = CreateNode(#PB_Any,    1,   0,  0) ; Direction normalized 
      \StrafeNode  = CreateNode(#PB_Any,    0,   0, -1) ; Direction normalized 
      
      AttachNodeObject(\MainNode, NodeID(\SightNode))
      AttachNodeObject(\MainNode, NodeID(\CameraNode))   
      AttachNodeObject(\MainNode, NodeID(\ForwardNode))     
      AttachNodeObject(\MainNode, NodeID(\StrafeNode))    
      AttachNodeObject(\MainNode, EntityID(\Entity))
    EndWith
    
    ;-Camera
    With Camera  
      \Camera = 0
      \Tightness = 0.035
      ; Camera use 2 nodes
      \CameraNode = CreateNode(#PB_Any, -3000, 700, 0) ; Camera position
      \TargetNode = CreateNode(#PB_Any) ; For cameraLookAt 
      AttachNodeObject(\CameraNode, CameraID(\Camera))
    EndWith  
    
    ;==================================
    ; create material
    Red = GetScriptMaterial(#PB_Any, "Color/Red")
    Blue = GetScriptMaterial(#PB_Any, "Color/Blue")
    Yellow = GetScriptMaterial(#PB_Any, "Color/Yellow")
    Green = GetScriptMaterial(#PB_Any, "Color/Green")
    
    ;==================================
    ; create Sphere
    MeshSphere = CreateSphere(#PB_Any, 10.0)
    For i = 0 To 5
      Entity=CreateEntity(#PB_Any, MeshID(MeshSphere), MaterialID(Green))
      MoveEntity(Entity, Random(2000)-1000, 800, Random(4000)-2000, #PB_Absolute)
      ; create bodies
      CreateEntityBody(Entity, #PB_Entity_SphereBody, 5.0)
    Next
    
    ;==================================
    ; create Cylinder
    MeshCylinder = CreateCylinder(#PB_Any, 10.0, 60)
    For i = 0 To 5
      Entity=CreateEntity(#PB_Any, MeshID(MeshCylinder), MaterialID(Red))
      MoveEntity(Entity, Random(2000)-1000, 800, Random(4000)-2000, #PB_Absolute)
      ; create bodies
      CreateEntityBody(Entity, #PB_Entity_CylinderBody, 10.0, 0, 1)
    Next
    
    ;==================================
    ; create Cube
    MeshCube = CreateCube(#PB_Any, 25.0)
    For i = 0 To 5
      Entity=CreateEntity(#PB_Any, MeshID(MeshCube), MaterialID(Yellow))
      MoveEntity(Entity, Random(2000)-1000, 800, Random(4000)-2000, #PB_Absolute)
      ; create bodies
      CreateEntityBody(Entity, #PB_Entity_BoxBody, 5.0)
    Next
    
    Repeat
      
      Screen3DEvents()   
      
      Robot\elapsedTime = TimeSinceLastFrame 
      
      HandleEntity(@Robot)
      
      CameraTrack(@Camera, @Robot)
      
      TimeSinceLastFrame = RenderWorld(60) * 40 / 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 Clamp(*var.float, min.f, max.f)
  If *var\f < min
    *var\f = min
  ElseIf *var\f > max
    *var\f = max
  EndIf
EndProcedure

Procedure InitBlendMaps()
  minHeight1.f = 70
  fadeDist1.f = 40
  minHeight2.f = 70
  fadeDist2.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 - minHeight1) / fadeDist1
          Clamp(@val, 0, 1)
          SetTerrainTileLayerBlend(0, tx, ty, 1, x, y, val)
          
          val.f = (Height - minHeight2) / fadeDist2
          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 OnGround(*Entity.s_Entity)
  With *Entity
    Result = RayCollide(EntityX(\EntityBody), EntityY(\EntityBody), EntityZ(\EntityBody), EntityX(\EntityBody), EntityY(\EntityBody)-70,  EntityZ(\EntityBody)) 
    ;CreateLine3D(20,EntityX(\EntityBody), EntityY(\EntityBody), EntityZ(\EntityBody), $FFFF, EntityX(\EntityBody), EntityY(\EntityBody)-70,  EntityZ(\EntityBody), $FFFF)  
    Delta.f = EntityY(\EntityBody) - PickY() - \BodyOffsetY

    If Result=-1 Or (Result>-1 And (delta >= 1))
      ProcedureReturn 0
    Else
      ProcedureReturn 1
    EndIf  
  EndWith  
EndProcedure

Procedure HandleEntity(*Entity.s_Entity)
  Protected.Vector3 Forward, Strafe, PosMain, PosDir, PosStrafe
  Protected.f Speed, Speed2, x, y, MouseX, MouseY
  Static Jump.f, MemJump.i, Rot.Vector3, Trans.Vector3, Clic
  
  With *Entity
    GetNodePosition(PosMain, \MainNode)
    GetNodePosition(PosDir, \ForwardNode)
    GetNodePosition(PosStrafe, \StrafeNode)
    SubVector3(Forward, PosDir, PosMain)
    SubVector3(Strafe, PosStrafe, PosMain)
    
    Speed = #PlayerSpeed * \elapsedTime 
    Speed2 = Speed / 2
    
    If ExamineKeyboard()
      
      If KeyboardReleased(#PB_Key_F5)
        WorldDebug(#PB_World_DebugBody)
      ElseIf KeyboardReleased(#PB_Key_F6)
        WorldDebug(#PB_World_DebugEntity)
      ElseIf KeyboardReleased(#PB_Key_F7)
        WorldDebug(#PB_World_DebugNone)
      EndIf
      
      If KeyboardPushed(\Key\Jump) And OnGround(*Entity) 
        Jump = 18
        MemJump = 1
      EndIf  
      
      Rot\x * 0.30
      Rot\y * 0.30
      Rot\z * 0.30
      Trans\x * 0.20
      Trans\y = Jump
      Trans\z * 0.20
      
      If KeyboardPushed(\Key\Up)
        Trans\x + Forward\x * Speed 
        Trans\z + Forward\z * Speed
      ElseIf KeyboardPushed(\Key\Down)
        Trans\x + Forward\x * -Speed2
        Trans\z + Forward\z * -Speed2
      EndIf
      
      If KeyboardPushed(\Key\Left)
        Rot\y + 2 * \elapsedTime
      ElseIf KeyboardPushed(\Key\Right)
        Rot\y - 2 * \elapsedTime
      EndIf 
      
      If KeyboardPushed(\Key\StrafeLeft)
        Trans\x + Strafe\x * Speed2
        Trans\z + Strafe\z * Speed2
      ElseIf KeyboardPushed(\Key\StrafeRight)
        Trans\x + Strafe\x * -Speed2
        Trans\z + Strafe\z * -Speed2
      EndIf 
      
      If OnGround(*Entity) 
        Jump = 0 
      ElseIf MemJump  
        Jump + 20 * \elapsedTime 
        If Jump > 100 
          MemJump = 0
        EndIf  
      Else  
        Jump - 9 
      EndIf  
      
    EndIf
    
    MoveEntity  (\EntityBody, Trans\x, Trans\y, Trans\z)
    RotateEntity(\EntityBody, 0, Rot\y, 0, #PB_Relative)   
    
    MoveNode(\MainNode, EntityX(\EntityBody), EntityY(\EntityBody)-\BodyOffsetY, EntityZ(\EntityBody), #PB_Absolute) 
    RotateNode(\MainNode, 0, EntityYaw(\EntityBody), 0) 
    
  EndWith   
EndProcedure

Procedure CameraTrack(*Camera.s_Camera, *Entity.s_Entity)
  Protected.Vector3 CameraPosition, TargetPosition 
  Protected.f x, y, z
  
  GetNodePosition(CameraPosition, *Entity\CameraNode)
  GetNodePosition(TargetPosition, *Entity\SightNode)
  x = NodeX(*Camera\CameraNode)
  y = NodeY(*Camera\CameraNode)
  z = NodeZ(*Camera\CameraNode)
  x = (CameraPosition\x - x) *  *Camera\Tightness
  y = (CameraPosition\y - y) *  *Camera\Tightness
  z = (CameraPosition\z - z) *  *Camera\Tightness
  MoveNode(*Camera\CameraNode, x, y, z)
  
  x = NodeX(*Camera\TargetNode)
  y = NodeY(*Camera\TargetNode)
  z = NodeZ(*Camera\TargetNode)
  x = (TargetPosition\x - x) *  *Camera\Tightness
  y = (TargetPosition\y - y) *  *Camera\Tightness
  z = (TargetPosition\z - z) *  *Camera\Tightness
  MoveNode(*Camera\TargetNode, x, y, z)
  
  CameraLookAt(*Camera\Camera, NodeX(*Camera\TargetNode), NodeY(*Camera\TargetNode), NodeZ(*Camera\TargetNode))
  
EndProcedure