rem Ensure that DX11Constants.dba is included
INCLUDEGUARD DX11_CONSTANTS

rem You can change these to alter the size of the terrain in world space. 
#constant TERRAIN_WIDTH     10000.0
#constant TERRAIN_DEPTH     10000.0

rem Initialize the DX11 plugin; since we don't have any DX11-specific features here (instancing was introduced with DX10)
rem we might as well set the feature level to DXVERSION_10_0 in order to support older hardware
dx11 init 1600, 900, false, "Hardware instancing example", DXVERSION_10_0

rem Set appropriate culling sectors for our scene; it will span roughly 10000x500x10000 world units
rem (the default sectors are created to accomodate a 3000x600x3000 area).
dx11 set world sectors 20, 1, 20, 500, 500, 500

rem Create vertex layout
vertLayout = dx11 create vertex layout()
dx11 add vertex layout element vertLayout, "POSITION",   DXGI_FORMAT_R32G32B32_FLOAT
dx11 add vertex layout element vertLayout, "COLOR",      DXGI_FORMAT_B8G8R8A8_UNORM
dx11 add vertex layout element vertLayout, "TEXCOORD",   DXGI_FORMAT_R32G32_FLOAT
dx11 finalize vertex layout vertLayout

rem Create a simple terrain-style height field.
rem Try decreasing the across / down settings if it runs too slowly - a proper
rem terrain should be broken into several separatedly cullable limbs rather 
rem than using this approach, but this is just a simple example after all.
terrainAcross   = 512
terrainDown     = 512
terrainHeight#   = 45.0 / 255.0         ` Maximum height of the terrain is 45.0, which is given by a full 255 red channel value of the height map
rem Don't change the below vars - they are all derived from the above variables
vertsAcross     = terrainAcross + 1
vertsDown       = terrainDown + 1
uStep#          = 1.0 / terrainAcross
vStep#          = 1.0 / terrainDown
xStart#         = -TERRAIN_WIDTH / 2.0
yStart#         = -TERRAIN_DEPTH / 2.0
xStep#          = TERRAIN_WIDTH / terrainAcross
yStep#          = TERRAIN_DEPTH / terrainDown
terrainIndices  = terrainAcross * terrainDown * 6

rem Create a custom mesh and fill it out from the above terrain specification
terrainMesh = dx11 create mesh(vertsAcross * vertsDown, terrainIndices, vertLayout)

rem Load a height map
pmHeightMap = dx11 load pixelmap("media/heightmap.png")

rem Write vertex data
dx11 lock mesh vertexdata terrainMesh
rem Store offsets to semantics in the vertex layout of the mesh; this is
rem faster to do once than for a few hundred thousand individual vertices.
offsetPos   = dx11 get mesh vertexdata semantic offset(terrainMesh, "POSITION")
offsetCol   = dx11 get mesh vertexdata semantic offset(terrainMesh, "COLOR")
offsetUV    = dx11 get mesh vertexdata semantic offset(terrainMesh, "TEXCOORD")
rem We will also need two vectors for setting the position and uv elements of the vertex data.
rem We can use temporary vectors for this; they will be automatically deleted 
rem at the next call to DX11 SYNC, but we are done using them once we start
rem calling that function so that should be all fine. And it saves us the two 
rem extra lines of manually deleting permanent vectors... ;-)
vecPos = dx11 create temp vector3()
vecUV  = dx11 create temp vector2()
for y = 0 to vertsDown - 1
    for x = 0 to vertsAcross - 1
        v = y * vertsAcross + x
        rem Using the height-per-pixel-value defined above
        height# = terrainHeight# * (dx11 get pixelmap pixel(pmHeightMap, x, y) && 0xff)
        dx11 set vector3 vecPos, xStart# + xStep# * x, height#, yStart# + yStep# * y
        dx11 set vector2 vecUV,  uStep# * x, vStep# * y
        rem The terrain shader uses the alpha channel of the vertex colour to blend between
        rem the two textures; basically the texture will be darker the higher the alpha value
        alpha = (height# > 3.0) * (height# / (terrainHeight# * 255)) * 0xff
        rem Set the vertex data
        dx11 set mesh vertexdata vector3 terrainMesh, v, offsetPos, vecPos
        dx11 set mesh vertexdata vector2 terrainMesh, v, offsetUV,  vecUV
        dx11 set mesh vertexdata dword terrainMesh, v, offsetCol, (alpha << 24)
    next x
next y
rem The vertex data must be unlocked once we are done editing it.
dx11 unlock mesh vertexdata terrainMesh

rem Write index data
dx11 lock mesh indexdata terrainMesh
indexCount = 0
for y = 0 to terrainDown - 1
    for x = 0 to terrainAcross - 1
        dx11 set mesh indexdata terrainMesh, indexCount,     y * vertsAcross + x
        dx11 set mesh indexdata terrainMesh, indexCount + 1, (y + 1) * vertsAcross + x
        dx11 set mesh indexdata terrainMesh, indexCount + 2, y * vertsAcross + x + 1
        dx11 set mesh indexdata terrainMesh, indexCount + 3, (y + 1) * vertsAcross + x
        dx11 set mesh indexdata terrainMesh, indexCount + 4, (y + 1) * vertsAcross + x + 1
        dx11 set mesh indexdata terrainMesh, indexCount + 5, y * vertsAcross + x + 1
        inc indexCount, 6
    next x
next y
rem Just as for the vertex data, we should unlock the index data when we are done with it
dx11 unlock mesh indexdata terrainMesh

rem Create an object from our terrain mesh
terrain = dx11 create object(terrainMesh)
rem We don't need the mesh any longer from here so we can delete it
dx11 delete mesh terrainMesh
rem We will keep the height map in order to look up tile heights for the terrain mesh to determine an 
rem interpolated height at a certain set of X/Z coordinates, so we should not delete that one.

rem Load terrain textures
texGrass1 = dx11 load image("media/Grass01.png")
texGrass2 = dx11 load image("media/Grass02.png")

rem Set up terrain shader technique
rem Take note that we cannot use shader version 5 in DX10 compatibility mode. Version 4 is the highest supported by DX10 cards.
terrainVS = dx11 load vertex shader("shaders/Terrain.fx", "VS", "vs_4_0")
terrainPS = dx11 load pixel shader("shaders/Terrain.fx", "PS", "ps_4_0")
terrainShaderTechnique = dx11 create shader technique(vertLayout, terrainVS, terrainPS)

rem Set data for the terrain object
dx11 set object texture terrain, texGrass1, 0
dx11 set object texture terrain, texGrass2, 1
dx11 set object shader technique terrain, terrainShaderTechnique
rem Scale the texture coordinates so that the texture will repeat 160 times over the terrain
dx11 set object texcoord scaling terrain, 160.0, 160.0
rem The terrain is completely opaque and should thus be rendered before any transparent objects
dx11 set object sort mode terrain, DX11_SORTMODE_OPAQUE
rem The terrain is also very large and should therefore be placed in the catch-all sector 
rem so as to not get culled just because the sector where it's pivot lies is
rem currently out of view.
dx11 set object to catch all sector terrain, true
rem All objects are initially positioned at (0, 0, 0) when created, but just to be clear....
dx11 set object position terrain, 0.0, 0.0, 0.0


rem Load some trees
texTrunk    = dx11 load image("media/Trunk001.jpg")
texLeaves   = dx11 load image("media/Leaves001.png")

dim Tree(10000) as dword
Tree(0) = dx11 load object("media/Tree101.x", vertLayout)
Tree(1) = dx11 load object("media/Tree102.x", vertLayout)
Tree(2) = dx11 load object("media/Tree103.x", vertLayout)

rem We need to create a blend state in order to make the leaves, grass, etc. transparent
bsFoliage = dx11 create blend state(1)  ` We don't need different blend states for different render targets, but that can be set here if required
dx11 enable blending                    bsFoliage, true
dx11 set source blend                   bsFoliage, D3D11_BLEND_SRC_ALPHA
dx11 set destination blend              bsFoliage, D3D11_BLEND_INV_SRC_ALPHA
dx11 set blend operation                bsFoliage, D3D11_BLEND_OP_ADD
dx11 set source blend alpha             bsFoliage, D3D11_BLEND_ONE
dx11 set destination blend alpha        bsFoliage, D3D11_BLEND_ZERO
dx11 set blend operation alpha          bsFoliage, D3D11_BLEND_OP_ADD
dx11 set blend state colour write mask  bsFoliage, D3D11_COLOR_WRITE_ENABLE_ALL

rem We'll also create a custom rasterizer state, again for the foliage, in order to not cull backfaces
rsFoliage = dx11 create rasterizer state()
dx11 set rasterizer state fill mode                         rsFoliage, D3D11_FILL_SOLID
dx11 set rasterizer state cull mode                         rsFoliage, D3D11_CULL_NONE
dx11 set rasterizer state clockwise front face              rsFoliage, false
dx11 set rasterizer state depth clipping enabled            rsFoliage, true
dx11 set rasterizer state depth bias                        rsFoliage, 0
dx11 set rasterizer state max depth bias                    rsFoliage, 0.0
dx11 set rasterizer state slope scaled depth bias           rsFoliage, 0.0
dx11 set rasterizer state antialiased line enabled          rsFoliage, false
dx11 set rasterizer state alpha line antialiasing enabled   rsFoliage, false


rem Create two shader techniques, one for the tree trunks and one for the foliage, using the above blend state
foliageVShader      = dx11 load vertex shader("shaders/Foliage.fx", "VS", "vs_4_0")
foliagePShader      = dx11 load pixel shader("shaders/Foliage.fx", "PS", "ps_4_0")
trunkTechnique      = dx11 create shader technique(vertLayout, dx11 get default vertex shader(), dx11 get default pixel shader())
foliageTechnique    = dx11 create shader technique(vertLayout, foliageVShader, foliagePShader)
dx11 set blend state        foliageTechnique, bsFoliage
dx11 set rasterizer state   foliageTechnique, rsFoliage

rem Associate shader techniques with limbs for the trees.
rem Limbs don't use an incremental id from which they are accessible off of the parent object like in the standard DBPro library, but
rem they can be enumerated to an array like so:
dim Limb(0) as dword
for o = 0 to 2
    obj = Tree(o)
    rem Unfortunately it seems to be a limitation in DBPro that a modifyable array has to be provided as the first parameter to a function, 
    rem so that's why this function call looks a bit wrong.
    dx11 enumerate object limbs Limb(), obj
    
    rem Now for these particular tree objects, there is an empty root limb, then the trunk and finally the leaves limb, so the trunk will
    rem be at index 1 in the limb array and the leaves at index 2.
    dx11 set limb shader technique Limb(1), trunkTechnique
    dx11 set limb shader technique Limb(2), foliageTechnique
    rem We have to set the textures as well since these are currently not imported along with the 3d object
    dx11 set limb texture Limb(1), texTrunk
    dx11 set limb texture Limb(2), texLeaves
    rem Set sorting modes for the limbs
    dx11 set limb sort mode Limb(1), DX11_SORTMODE_OPAQUE
    dx11 set limb sort mode Limb(2), DX11_SORTMODE_TRANSPARENT_FULL
next o

rem Now that our source trees are loaded, we can create instances from them.
rem All instances are rendered in the same draw call; this is efficient but it can cause some issues like object sorting being wrong.
rem In essence, since all instances are drawn in the same call, they can only be properly sorted along with their fellow instance objects.
rem If this causes visual artifacts it might be preferable to clone the source object and create several instance groups for objects that 
rem are close together instead. We won't bother with that here though and will just go ahead and create some ten thousand tree instances to spread 
rem out over the terrain.
rem If the program runs too slowly, you can try to reduce this number a bit along with the grass count. I get ~100 FPS on my desktop with these settings.
for o = 0 to 9999
    if o > 2
        rem Randomly chose a source tree to instance
        Tree(o) = dx11 instance object(Tree(rnd(2)))
    endif
    
    rem Find a random X and Z position for the tree on the terrain
    x# = rnd(TERRAIN_WIDTH * 10) * 0.1 - (TERRAIN_WIDTH * 0.5)
    z# = rnd(TERRAIN_DEPTH * 10) * 0.1 - (TERRAIN_DEPTH * 0.5)
    rem Get the height of the terrain mesh at this particular position.
    rem The DX11 INTERSECT OBJECT function works, however it checks each triangle of the object it is invoked for so it would be
    rem incredibly slow to check the quarter million polygon terrain 10 000 times. Since the terrain is just a height field with
    rem the same tile size but differing heights, we can find the height at a certain position by interpolating between the vertex
    rem heights (which happen to correspond to the pixel values in our height map). This is a fair lot faster.
    y# = GetInterpolatedTerrainHeight(pmHeightMap, terrainHeight#, x#, z#)
    rem Position the tree at the derived coordinates
    dx11 set object position Tree(o), x#, y#, z#
    rem Add some random rotation and scaling to the instance for more diversity
    dx11 set object angle Tree(o), (rnd(150) * 0.1) - 7.5, rnd(3600) * 0.1, 0.0
    rem Take note that the full scale is 1 and not 100.
    scale# = 0.84 + (rnd(320) * 0.001)
    dx11 set object scale Tree(o), scale# * 0.5, scale# * 0.5, scale# * 0.5
    
    rem We want different sorting modes for the trunk (opaque) and leaves (transparent) meshes
    dx11 enumerate object limbs Limb(), Tree(o)
    dx11 set limb sort mode Limb(1), DX11_SORTMODE_OPAQUE
    dx11 set limb sort mode Limb(2), DX11_SORTMODE_TRANSPARENT_FULL
next o

rem Also load and instance some grass objects
dim Grass(40000) as dword
Grass(0) = dx11 load object("media/Grass001.x", vertLayout)
texGrass = dx11 load image("media/Grass001.png")

dx11 set object texture Grass(0), texGrass
rem We'll use the same simple foliage shader technique for drawing the grass as we used for the leaves
dx11 set object shader technique Grass(0), foliageTechnique
dx11 set object sort mode Grass(0), DX11_SORTMODE_TRANSPARENT_FULL

rem Create 40 000 grass instances randomly scattered around the terrain.
rem If the program runs too slowly, you can try to reduce this number a bit.
for g = 0 to 39999
    if g > 0 then Grass(g) = dx11 instance object(Grass(0))
    
    x# = rnd(TERRAIN_WIDTH * 10) * 0.1 - (TERRAIN_WIDTH * 0.5)
    z# = rnd(TERRAIN_DEPTH * 10) * 0.1 - (TERRAIN_DEPTH * 0.5)
    y# = GetInterpolatedTerrainHeight(pmHeightMap, terrainHeight#, x#, z#)
    dx11 set object position Grass(g), x#, y#, z#
    dx11 set object angle Grass(g), (rnd(200) * 0.1) - 10.0, rnd(3600) * 0.1, 0.0
    scale# = 0.84 + (rnd(320) * 0.001)
    dx11 set object scale Grass(g), scale# * 0.2, scale# * 0.2, scale# * 0.2
next g

rem Use the default font to create a label for displaying some statistics
font = dx11 get default font()
statsLabel = dx11 create label(font, "Statistics", 0, 0)

rem Set the initial orientation of the main camera
mainCam = dx11 get main camera()
dx11 set camera position mainCam, 0.0, 40.0, -200.0
dx11 set camera lookat mainCam, 0.0, 0.0, 0.0

rem Main loop
currentTick as double integer
lastTick    as double integer
currentTick = dx11 get high resolution tick() - 1
while not dx11 quit()
    lastTick    = currentTick
    currentTick = dx11 get high resolution tick()
    timeFactor# = dx11 get high resolution delta time(lastTick, currentTick)
    
    rem Move camera using mouse
    moveDir = dx11 is mouse button down(MBTN_LEFT) - dx11 is mouse button down(MBTN_RIGHT)
    dx11 apply camera angle mainCam, timeFactor# * 30 * dx11 get mouseMoveY(), timeFactor# * 30 * dx11 get mouseMoveX(), 0.0
    dx11 move camera mainCam, timeFactor# * 100.0 * moveDir
    rem Keep the camera out of the ground
    groundHeight# = GetInterpolatedTerrainHeight(pmHeightMap, terrainHeight#, dx11 get camera position x(mainCam), dx11 get camera position z(mainCam))
    if dx11 get camera position y(mainCam) < groundHeight# + 5.0 then dx11 apply camera position mainCam, 0.0, groundHeight# + 5.0 - dx11 get camera position y(mainCam), 0.0
    rem Lock the mouse position to the center of the screen
    dx11 set mouse position 800, 450
    
    rem Update the statistics label.
    rem As you can probably see from what is displayed here (unless yours and mine systems are quite different), 
    rem the main thing eating up the frame rate in this application is actually culling all the instances, rather than
    rem actually rendering the ones which pass culling. A more sophisticated culling approach could probably increase 
    rem efficiency a fair bit on that front.
    dx11 set label text statsLabel, "FPS: " + str$(dx11 get screen fps()) + crlf$() + "Rendered instances: " + str$(dx11 get drawn instance count()) + crlf$() + "Draw calls: " + str$(dx11 get draw call count())
    
    rem Update the engine, including rendering the screen camera
    dx11 sync
endwhile

rem All DX11 resources will be automatically released when the plugin is unloaded, as will happen when the 
rem DBPro program terminates.
end


rem Helper function for obtaining an interpolated height value from the terrain
function GetInterpolatedTerrainHeight(pmHeightMap as dword, heightFactor as float, xPos as float, zPos as float)
    rem The terrain object has its center at (0, 0, 0), while the height map has its upper left corner at (0, 0).
    rem We need to offset it in order to determine the vertex positions (which corresponds to the pixels in our
    rem height map) that are close to the given 3D X and Z positions.
    xPos = xPos + (TERRAIN_WIDTH / 2.0)
    zPos = zPos + (TERRAIN_DEPTH / 2.0)
    
    rem Is the given position outside of the terrain?
    if xPos < 0.0 or xPos > TERRAIN_WIDTH or zPos < 0.0 or zPos > TERRAIN_DEPTH then exitfunction 0.0
    
    rem Calculate dimensions of each terrain tile
    tileWidth# = TERRAIN_WIDTH / dx11 get pixelmap width(pmHeightMap)
    tileDepth# = TERRAIN_DEPTH / dx11 get pixelmap height(pmHeightMap)
    
    rem What tile does the current position correspond to?
    tileX   = int(xPos / tileWidth#)
    tileZ   = int(zPos / tileDepth#)
    tileX2  = int(xPos / (tileWidth# * 2)) * 2
    tileZ2  = int(zPos / (tileDepth# * 2)) * 2
    
    rem Alright, time to hold on to those suspenders....
    local fX as float
    local fZ as float
    fX      = (xPos - (tileX * tileWidth#)) / tileWidth#
    fZ      = (zPos - (tileZ * tileDepth#)) / tileDepth#
    height# = 0.0
    if tileX = tileX2 and tileZ = tileZ2
        if (((1 - fX) - fZ) - (fZ - (1 - fX))) > 0.0
            height# = GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ)) * fX) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ)) * fZ)
            exitfunction height#
        else
            height# = GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1)) * (1 - fZ)) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1)) * (1 - fX))
            exitfunction height#
        endif
    endif
    
    if (tileX2 - tileX) = -1 and (tileZ2 - tileZ) = -1
        if (((1 - fX) - fZ) - (fZ - (1 - fX))) > 0.0
            height# = GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ)) * fX) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ)) * fZ);
            exitfunction height#
        else
            height# = GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1)) * (1 - fZ)) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1)) * (1 - fX))
            exitfunction height#
        endif
    endif
    
    if (tileX2 - tileX) = -1 and (tileZ = tileZ2)
        if (((1 - fZ) + fX) - (fZ + (1 - fX))) > 0.0
            height# = GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ)) * (1 - fX)) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX +  1,tileZ + 1) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ)) * fZ)
            exitfunction height#
        else 
            height# = GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1)) * fX) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1)) * (1 - fZ))
            exitfunction height#
        endif
    endif
    
    if tileX = tileX2 and (tileZ2 - tileZ) = -1 
        if (((1  -  fZ)  +  fX)  -  (fZ  +  (1  -  fX))) > 0.0
            height# = GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ)) * (1 - fX)) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ)) * fZ)
            exitfunction height#
        else
            height# = GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX + 1, tileZ + 1) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1)) * fX) + ((GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ) - GetHeightMapHeight(pmHeightMap, heightFactor, tileX, tileZ + 1)) * (1 - fZ))
            exitfunction height#
        endif
    endif
endfunction 0.0

rem Helper function for making the GetInterpolatedTerrainHeight function more readable
function GetHeightMapHeight(pmHeightMap as dword, heightFactor as float, tileX as dword, tileZ as dword)
    if tileX >= dx11 get pixelmap width(pmHeightMap) or tileZ >= dx11 get pixelmap height(pmHeightMap) then exitfunction 0.0
    h# = heightFactor * (dx11 get pixelmap pixel(pmHeightMap, tileX, tileZ) && 0xff)
endfunction h#
