rem This sample program mainly demonstrates the new hull and domain shaders as a means to 
rem achieve high detail displacement mapping through distance-dependent hardware tessellation.
rem Take note that hardware tessellation is a new feature introduced with shader model 5 and as 
rem such can only be run in the actual DX11 mode (ie. on DX11 capable hardware).
rem There are also some GUI elements that use the GUI sprite layer and manual sprite texcoord 
rem setting to be found in GUI.dba.
rem
rem                                                   - Joel Sjöqvist ("Rudolpho") 2014-07-23

rem Screen size (change these as appropriate; note that they should match a valid screen resolution on your system though)
#constant SCREEN_WIDTH      1600
#constant SCREEN_HEIGHT     900

rem Ensure that the DX11Constants.dba file is properly included (if it isn't we'll get unexpected
rem behaviour as non-set constants will default to zeroes with DBPro's dynamic typing)
INCLUDEGUARD DX11_CONSTANTS


rem This structure maintains the individual shader technique and constant buffer
rem used by each individually adjustable type of displacement mapped mesh 
rem (the floor, the pillars, the pyramid and the planet).
rem The constant buffer's contents will be updated with user input from GUI components
rem on a per-frame basis (see the main loop).
type sDisplacementData
    technique               as dword
    cb                      as dword
endtype

rem This type contains all the GUI elements used for a single separate object type used in the example
rem (ie. the floor, the cylindrical pillars, the pyramid and the planet, which can have individual displacement 
rem settings altered through the use of these GUI components)
type sDisplacementGui
    gcbEnableDisplacement   as dword
    gcbWireframe            as dword
    gsMaxTessDistance       as dword
    gsMinTessDistance       as dword
    gsMinTessAmount         as dword
    gsMaxTessAmount         as dword
    gsStrength              as dword
    glLabel                 as dword
endtype



rem Start the DX11 engine; as stated above we *need* to use the DX11 feature level for this
rem example to work
dx11 init SCREEN_WIDTH, SCREEN_HEIGHT, false, "DX11 Plugin Example: Dynamic Displacement Mapping", DXVERSION_11_0

rem Main camera setup
cam = dx11 get main camera()
dx11 set camera backdrop cam, 0xff301060
dx11 set camera range cam, 0.01, 1000.0
dx11 set camera position cam, 0, 3, -52
dx11 set camera lookat cam, 0, 3, 0

rem Create a vertex layout to use with our tessellated meshes.
rem The default layout would do but we don't need the COLOR member of it
rem so we make a custom layout without that element to reduce the overhead of 
rem sending unused data to the GPU.
vertLayout = dx11 create vertex layout()
dx11 add vertex layout element vertLayout, "POSITION",  DXGI_FORMAT_R32G32B32_FLOAT
dx11 add vertex layout element vertLayout, "NORMAL",    DXGI_FORMAT_R32G32B32_FLOAT
dx11 add vertex layout element vertLayout, "TEXCOORD",  DXGI_FORMAT_R32G32_FLOAT
dx11 finalize vertex layout vertLayout

rem Load shaders. We'll use a second vertex shader (and set the hull and domain shaders to null)
rem for rendering the same meshes without dynamic tessellation and displacement mapping.
vs = dx11 load vertex shader("fx/DisplacementMapping.fx",   "VS", "vs_5_0")
hs = dx11 load hull shader("fx/DisplacementMapping.fx",     "HS") ` The hull and domain shaders can only be compiled with
ds = dx11 load domain shader("fx/DisplacementMapping.fx",   "DS") ` shader model 5 so they don't need any profile arguments
ps = dx11 load pixel shader("fx/DisplacementMapping.fx",    "PS", "ps_5_0")
vsNoTess = dx11 load vertex shader("fx/DisplacementMapping.fx", "VS_NoTess", "vs_5_0")

rem Create shader techniques and set up individual displacement data settings buffers for the 
rem four different object-type groups
ddFloor     as sDisplacementData
ddPillar    as sDisplacementData
ddPyramid   as sDisplacementData
ddPlanet    as sDisplacementData

ddFloor.technique   = dx11 create shader technique(vertLayout, vs, ps, nullptr, hs, ds)
ddFloor.cb          = dx11 create cbuffer(20, false)
dx11 add constant buffer ddFloor.technique, ddFloor.cb
ddPillar.technique  = dx11 create shader technique(vertLayout, vs, ps, nullptr, hs, ds)
ddPillar.cb         = dx11 create cbuffer(20, false)
dx11 add constant buffer ddPillar.technique, ddPillar.cb
ddPyramid.technique = dx11 create shader technique(vertLayout, vs, ps, nullptr, hs, ds)
ddPyramid.cb        = dx11 create cbuffer(20, false)
dx11 add constant buffer ddPyramid.technique, ddPyramid.cb
ddPlanet.technique  = dx11 create shader technique(vertLayout, vs, ps, nullptr, hs, ds)
ddPlanet.cb         = dx11 create cbuffer(20, false)
dx11 add constant buffer ddPlanet.technique, ddPlanet.cb

rem This is a separate constant buffer that will be updated on a per-frame basis
rem and is shared between all shader techniques. It contains the current camera position
rem in world space, which is used by the (tessellation) vertex and domain shaders to 
rem compare how far individual vertices are from the camera.
rem While the technique-specific constant buffers are also technically updated on a 
rem per-frame basis, the DX11 engine will internally keep track of its contents and 
rem only update the buffer to the GPU if it has actually changed (which it won't 
rem in most frames since each individual displacement mapping setting will only be 
rem updated on GUI events). For this reason it should be more efficient to keep 
rem the data in separate buffers that can be independantly updated, as well as for the
rem reason that the camera buffer can be shared for all shader techniques, while the 
rem individual displacement mapping settings buffers cannot.
cbCamera = dx11 create cbuffer(12)
dx11 add constant buffer ddFloor.technique,     cbCamera
dx11 add constant buffer ddPillar.technique,    cbCamera
dx11 add constant buffer ddPyramid.technique,   cbCamera
dx11 add constant buffer ddPlanet.technique,    cbCamera

rem Create a rasterizer state for rendering in wireframe mode
rsWireframe = dx11 create rasterizer state()
dx11 set rasterizer state fill mode rsWireframe, D3D11_FILL_WIREFRAME
rem We also need a reference to the default rasterizer state for switching between 
rem that and the wireframe one
rsDefault = dx11 get default rasterizer state()
dx11 set rasterizer state ddFloor.technique,    rsDefault
dx11 set rasterizer state ddPillar.technique,   rsDefault
dx11 set rasterizer state ddPyramid.technique,  rsDefault
dx11 set rasterizer state ddPlanet.technique,   rsDefault

rem Load textures
rem The displacement (bump / height) maps are grayscale images (only the red channel is used
rem by the shader) that describe how much ("height") to displace each tessellated vertex along
rem its normal direction.
texCobblestone_Diffuse      = dx11 load image("media/Cobblestone_Diffuse.jpg")
texCobblestone_Displacement = dx11 load image("media/Cobblestone_Displacement.jpg")
texPillar_Diffuse           = dx11 load image("media/Stone_Diffuse.jpg")
texPillar_Displacement      = dx11 load image("media/Stone_Displacement.jpg")
texPyramid_Diffuse          = dx11 load image("media/Pyramid_Diffuse.png")
texPyramid_Displacement     = dx11 load image("media/Pyramid_Displacement.png")
texPlanet_Diffuse           = dx11 load image("media/Planet_Diffuse.png")
texPlanet_Displacement      = dx11 load image("media/Planet_Displacement.png")

rem Load and prepare objects for use with our displacement mapping shader
rem *** Floor object ***
rem The hardware can only tessellate by up to 32 times per edge. This won't 
rem be enough to make a four-vertex plane look good with displacement mapping, so 
rem we'll create a 64x64 tile plane instead (this means each tile can be tessellated 
rem into 32x32 sub-tiles by the shader).
rem This is a common thing to keep in mind with hardware tessellation; the source 
rem objects must have high enough base resolution for making the subdivided mesh 
rem look appropriate.
objFloor = CreateGridPlane(vertLayout, 200, 200, 64, 64, 8.0, 8.0)
dx11 set object texture objFloor, texCobblestone_Diffuse,       0
dx11 set object texture objFloor, texCobblestone_Displacement,  1
PrepareForDisplacement(objFloor)
dx11 set object shader technique objFloor, ddFloor.technique
dx11 set object position objFloor, 0, 0, 0

rem *** Pillar object ***
objPillar = dx11 load object("media/Pillar.obj", vertLayout)
dx11 set object texture objPillar, texPillar_Diffuse,       0
dx11 set object texture objPillar, texPillar_Displacement,  1
PrepareForDisplacement(objPillar)
dx11 set object shader technique objPillar, ddPillar.technique
rem Instanced objects can be individually tessellated based on distance to the camera; let's instance some "pillars"
rem (which are actually just cylinders with 8 height segments) around the floor plane.
dim Pillars(35) as dword
    Pillars(0) = objPillar
for p = 1 to 35
    Pillars(p) = dx11 instance object(objPillar)
next p
pillarId = 0
for x = 0 to 1
    for z = 0 to 9
        dx11 set object position Pillars(pillarId), 180.0 * x - 90, 0.0, 20 * z - 90
        inc pillarId
    next z
next x
for x = 0 to 7
    for z = 0 to 1
        dx11 set object position Pillars(pillarId), 20.0 * x - 70, 0.0, 180.0 * z - 90
        inc pillarId
    next z
next x

rem *** Pyramid object ***
rem As mentioned above, since the hardware can only tessellate an edge up to 32 times, we need to have semi-high 
rem resolution on our input meshes for good results. This pyramid object is perhaps a bit over-the-top subdivided
rem to begin with however. Note how the tessellation level transitions become much less noticible as the camera moves 
rem towards / away from the pyramid compared to the much lower resolution pillars however.
rem Oh, and I made the model myself so that's why the UV mapping on it blows. The displacement wouldn't be so bad at 
rem the edges if the textures wrapped around properly instead of just starting anew for each side face.
objPyramid = dx11 load object("media/Pyramid.obj", vertLayout)
dx11 set object texcoord scaling objPyramid, 8.0, 8.0
dx11 set object texture objPyramid, texPyramid_Diffuse,       0
dx11 set object texture objPyramid, texPyramid_Displacement,  1
PrepareForDisplacement(objPyramid)
dx11 set object shader technique objPyramid, ddPyramid.technique
dx11 set object position objPyramid, 0, 0, 50

rem *** Planet object ***
objPlanet = dx11 load object("media/Planet.obj", vertLayout)
dx11 set object texture objPlanet, texPlanet_Diffuse,       0
dx11 set object texture objPlanet, texPlanet_Displacement,  1
PrepareForDisplacement(objPlanet)
dx11 set object shader technique objPlanet, ddPlanet.technique
dx11 set object position objPlanet, 96, 120, 200


rem Initiate the GUI system (see GUI.dba for implementation details)
InitGUI()

rem Create GUI components
guiFloor    as sDisplacementGui
guiPillar   as sDisplacementGui
guiPyramid  as sDisplacementGui
guiPlanet   as sDisplacementGui

xPos = 4
yPos = 4
guiFloor.glLabel                    = dx11 create label("[c=0xffc0ffa0]Floor displacement settings", xPos, yPos)
guiFloor.gcbEnableDisplacement      = guiCreateCheckbox(xPos + 16, yPos + 20, "Enable displacement mapping")
guiFloor.gsMinTessAmount            = guiCreateSlider(xPos + 16, yPos + 50, 300, 20, "Minimum tessellation", 1, 32, 1)
guiFloor.gsMaxTessAmount            = guiCreateSlider(xPos + 16, yPos + 80, 300, 20, "Maximum tessellation", 1, 32, 16)
guiFloor.gsMaxTessDistance          = guiCreateSlider(xPos + 16, yPos + 110, 300, 20, "Maximum tessellation distance from camera", 1, 200, 8)
guiFloor.gsMinTessDistance          = guiCreateSlider(xPos + 16, yPos + 140, 300, 20, "Minimum tessellation distance from camera", 1, 200, 100)
guiFloor.gsStrength                 = guiCreateSlider(xPos + 16, yPos + 170, 300, 20, "Displacement strength", -100, 100, 24)
guiFloor.gcbWireframe               = guiCreateCheckbox(xPos + 16, yPos + 200, "Wireframe")
guiCheckbox_SetChecked(guiFloor.gcbEnableDisplacement, true)

xPos = SCREEN_WIDTH - 324
yPos = SCREEN_HEIGHT - 264
guiPillar.glLabel                   = dx11 create label("[c=0xff40c020]Pillar displacement settings", xPos, yPos)
guiPillar.gcbEnableDisplacement     = guiCreateCheckbox(xPos + 16, yPos + 20, "Enable displacement mapping")
guiPillar.gsMinTessAmount           = guiCreateSlider(xPos + 16, yPos + 50, 300, 20, "Minimum tessellation", 1, 32, 1)
guiPillar.gsMaxTessAmount           = guiCreateSlider(xPos + 16, yPos + 80, 300, 20, "Maximum tessellation", 1, 32, 4)
guiPillar.gsMaxTessDistance         = guiCreateSlider(xPos + 16, yPos + 110, 300, 20, "Maximum tessellation distance from camera", 1, 200, 50)
guiPillar.gsMinTessDistance         = guiCreateSlider(xPos + 16, yPos + 140, 300, 20, "Minimum tessellation distance from camera", 1, 200, 90)
guiPillar.gsStrength                = guiCreateSlider(xPos + 16, yPos + 170, 300, 20, "Displacement strength", -100, 100, 64)
guiPillar.gcbWireframe              = guiCreateCheckbox(xPos + 16, yPos + 200, "Wireframe")
guiCheckbox_SetChecked(guiPillar.gcbEnableDisplacement, true)

xPos = SCREEN_WIDTH - 324
yPos = 4
guiPyramid.glLabel                  = dx11 create label("[c=0xffff8040]Pyramid displacement settings", xPos, yPos)
guiPyramid.gcbEnableDisplacement    = guiCreateCheckbox(xPos + 16, yPos + 20, "Enable displacement mapping")
guiPyramid.gsMinTessAmount          = guiCreateSlider(xPos + 16, yPos + 50, 300, 20, "Minimum tessellation", 1, 32, 1)
guiPyramid.gsMaxTessAmount          = guiCreateSlider(xPos + 16, yPos + 80, 300, 20, "Maximum tessellation", 1, 32,24)
guiPyramid.gsMaxTessDistance        = guiCreateSlider(xPos + 16, yPos + 110, 300, 20, "Maximum tessellation distance from camera", 1, 200, 10)
guiPyramid.gsMinTessDistance        = guiCreateSlider(xPos + 16, yPos + 140, 300, 20, "Minimum tessellation distance from camera", 1, 200, 120)
guiPyramid.gsStrength               = guiCreateSlider(xPos + 16, yPos + 170, 300, 20, "Displacement strength", -100, 100, 31)
guiPyramid.gcbWireframe             = guiCreateCheckbox(xPos + 16, yPos + 200, "Wireframe")
guiCheckbox_SetChecked(guiPyramid.gcbEnableDisplacement, true)

xPos = 4
yPos = SCREEN_HEIGHT - 264
guiPlanet.glLabel                   = dx11 create label("[c=0xff6090ff]Planet displacement settings", xPos, yPos)
guiPlanet.gcbEnableDisplacement     = guiCreateCheckbox(xPos + 16, yPos + 20, "Enable displacement mapping")
guiPlanet.gsMinTessAmount           = guiCreateSlider(xPos + 16, yPos + 50, 300, 20, "Minimum tessellation", 1, 32, 1)
guiPlanet.gsMaxTessAmount           = guiCreateSlider(xPos + 16, yPos + 80, 300, 20, "Maximum tessellation", 1, 32, 32)
guiPlanet.gsMaxTessDistance         = guiCreateSlider(xPos + 16, yPos + 110, 300, 20, "Maximum tessellation distance from camera", 1, 200, 10)
guiPlanet.gsMinTessDistance         = guiCreateSlider(xPos + 16, yPos + 140, 300, 20, "Minimum tessellation distance from camera", 1, 200, 100)
guiPlanet.gsStrength                = guiCreateSlider(xPos + 16, yPos + 170, 300, 20, "Displacement strength", -100, 100, 64)
guiPlanet.gcbWireframe              = guiCreateCheckbox(xPos + 16, yPos + 200, "Wireframe")
guiCheckbox_SetChecked(guiPlanet.gcbEnableDisplacement, true)

rem Main loop
lastTick    as double integer
currentTick as double integer
currentTick =  dx11 get high resolution tick() - 1
while not dx11 quit()
    rem Update timer
    lastTick    = currentTick
    currentTick = dx11 get high resolution tick()
    delta#      = dx11 get high resolution delta time(lastTick, currentTick)
    
    rem Handle GUI components
    UpdateGUI(true)
    
    rem Control camera using mouse
    mmx = dx11 get mouseMoveX()
    mmy = dx11 get mouseMoveY()
    mc  = dx11 is mouse button down(0) - dx11 is mouse button down(1)
    rem Don't move the camera with the mouse if a GUI component is currently active (ie. a slider is being dragged)
    if (mc <> 0 or dx11 is mouse button down(2)) and gGui_ActiveComponent = -1
        dx11 apply camera angle cam, mmy * 90 * delta#, mmx * 90 * delta#, 0.0
        dx11 move camera cam, mc * 4 * (dx11 is key down(DIK_LSHIFT) * 12 + 1) * delta#
    endif
    
    rem Update the camera cbuffer
    dx11 set cbuffer vector3 cbCamera, 0, dx11 create temp vector3(dx11 get camera position x(cam), dx11 get camera position y(cam), dx11 get camera position z(cam))
    
    rem Update displacement cbuffers based on the current state of the GUI components
    rem Floor settings
    dx11 set cbuffer float ddFloor.cb, 0,   guiSlider_GetValue(guiFloor.gsMaxTessDistance)
    dx11 set cbuffer float ddFloor.cb, 4,   guiSlider_GetValue(guiFloor.gsMinTessDistance)
    dx11 set cbuffer float ddFloor.cb, 8,   guiSlider_GetValue(guiFloor.gsMinTessAmount)
    dx11 set cbuffer float ddFloor.cb, 12,  guiSlider_GetValue(guiFloor.gsMaxTessAmount)
    dx11 set cbuffer float ddFloor.cb, 16,  guiSlider_GetValue(guiFloor.gsStrength) * 0.02
    
    rem Pillar settings
    dx11 set cbuffer float ddPillar.cb, 0,  guiSlider_GetValue(guiPillar.gsMaxTessDistance)
    dx11 set cbuffer float ddPillar.cb, 4,  guiSlider_GetValue(guiPillar.gsMinTessDistance)
    dx11 set cbuffer float ddPillar.cb, 8,  guiSlider_GetValue(guiPillar.gsMinTessAmount)
    dx11 set cbuffer float ddPillar.cb, 12, guiSlider_GetValue(guiPillar.gsMaxTessAmount)
    dx11 set cbuffer float ddPillar.cb, 16, guiSlider_GetValue(guiPillar.gsStrength) * 0.02
    
    rem Pyramid settings
    dx11 set cbuffer float ddPyramid.cb, 0,  guiSlider_GetValue(guiPyramid.gsMaxTessDistance)
    dx11 set cbuffer float ddPyramid.cb, 4,  guiSlider_GetValue(guiPyramid.gsMinTessDistance)
    dx11 set cbuffer float ddPyramid.cb, 8,  guiSlider_GetValue(guiPyramid.gsMinTessAmount)
    dx11 set cbuffer float ddPyramid.cb, 12, guiSlider_GetValue(guiPyramid.gsMaxTessAmount)
    dx11 set cbuffer float ddPyramid.cb, 16, guiSlider_GetValue(guiPyramid.gsStrength) * 0.02
    
    rem Planet settings
    dx11 set cbuffer float ddPlanet.cb, 0,  guiSlider_GetValue(guiPlanet.gsMaxTessDistance)
    dx11 set cbuffer float ddPlanet.cb, 4,  guiSlider_GetValue(guiPlanet.gsMinTessDistance)
    dx11 set cbuffer float ddPlanet.cb, 8,  guiSlider_GetValue(guiPlanet.gsMinTessAmount)
    dx11 set cbuffer float ddPlanet.cb, 12, guiSlider_GetValue(guiPlanet.gsMaxTessAmount)
    dx11 set cbuffer float ddPlanet.cb, 16, guiSlider_GetValue(guiPlanet.gsStrength) * 0.04
    
    rem Handle GUI checkboxes.
    rem We can enable / disable the displacement mapping by changing the shaders of the associated technique.
    rem The vsNoTess vertex shader will forward data properly formatted for the pixel shader; in order to bypass the 
    rem tessellation hull and domain shader stages we set them to null.
    rem We also need to change the primitive topology of the associated object meshes; the hull shader expects 
    rem 6 control points while the normal VS->PS rendering approach expects a triangular topology.
    rem Take note that we don't have to change the index buffers in order to make these changes; the input assembler
    rem will simply skip any indices per face that doesn't fit in the currently set primitive topology.
    
    rem Floor
    rem » Displacement mapping
    if guiCheckbox_IsChecked(guiFloor.gcbEnableDisplacement)
        rem Only update settings if they aren't already set
        if not bIsFloorDisplaced
            dx11 set vertex shader ddFloor.technique, vertLayout, vs
            dx11 set tessellation shaders ddFloor.technique, hs, ds
            dx11 set object topology objFloor, TOPOLOGY_6_CONTROL_POINT_PATCHLIST
            bIsFloorDisplaced = true
        endif
    else
        if bIsFloorDisplaced
            dx11 set vertex shader ddFloor.technique, vertLayout, vsNoTess
            dx11 set tessellation shaders ddFloor.technique, nullptr, nullptr
            dx11 set object topology objFloor, TOPOLOGY_TRIANGLELIST
            bIsFloorDisplaced = false
        endif
    endif
    rem » Wireframe rendering
    if guiCheckbox_IsChecked(guiFloor.gcbWireframe) = true and dx11 get rasterizer state(ddFloor.technique) <> rsWireframe then dx11 set rasterizer state ddFloor.technique, rsWireframe
    if guiCheckbox_IsChecked(guiFloor.gcbWireframe) = false and dx11 get rasterizer state(ddFloor.technique) <> rsDefault then dx11 set rasterizer state ddFloor.technique, rsDefault
    
    rem Pillars
    rem » Displacement mapping
    if guiCheckbox_IsChecked(guiPillar.gcbEnableDisplacement)
        rem Only update settings if they aren't already set
        if not bIsPillarDisplaced
            dx11 set vertex shader ddPillar.technique, vertLayout, vs
            dx11 set tessellation shaders ddPillar.technique, hs, ds
            dx11 set object topology objPillar, TOPOLOGY_6_CONTROL_POINT_PATCHLIST
            bIsPillarDisplaced = true
        endif
    else
        if bIsPillarDisplaced
            dx11 set vertex shader ddPillar.technique, vertLayout, vsNoTess
            dx11 set tessellation shaders ddPillar.technique, nullptr, nullptr
            dx11 set object topology objPillar, TOPOLOGY_TRIANGLELIST
            bIsPillarDisplaced = false
        endif
    endif
    rem » Wireframe rendering
    if guiCheckbox_IsChecked(guiPillar.gcbWireframe) = true and dx11 get rasterizer state(ddPillar.technique) <> rsWireframe then dx11 set rasterizer state ddPillar.technique, rsWireframe
    if guiCheckbox_IsChecked(guiPillar.gcbWireframe) = false and dx11 get rasterizer state(ddPillar.technique) <> rsDefault then dx11 set rasterizer state ddPillar.technique, rsDefault
    
    rem Pyramid
    rem » Displacement mapping
    if guiCheckbox_IsChecked(guiPyramid.gcbEnableDisplacement)
        rem Only update settings if they aren't already set
        if not bIsPyramidDisplaced
            dx11 set vertex shader ddPyramid.technique, vertLayout, vs
            dx11 set tessellation shaders ddPyramid.technique, hs, ds
            dx11 set object topology objPyramid, TOPOLOGY_6_CONTROL_POINT_PATCHLIST
            bIsPyramidDisplaced = true
        endif
    else
        if bIsPyramidDisplaced
            dx11 set vertex shader ddPyramid.technique, vertLayout, vsNoTess
            dx11 set tessellation shaders ddPyramid.technique, nullptr, nullptr
            dx11 set object topology objPyramid, TOPOLOGY_TRIANGLELIST
            bIsPyramidDisplaced = false
        endif
    endif
    rem » Wireframe rendering
    if guiCheckbox_IsChecked(guiPyramid.gcbWireframe) = true and dx11 get rasterizer state(ddPyramid.technique) <> rsWireframe then dx11 set rasterizer state ddPyramid.technique, rsWireframe
    if guiCheckbox_IsChecked(guiPyramid.gcbWireframe) = false and dx11 get rasterizer state(ddPyramid.technique) <> rsDefault then dx11 set rasterizer state ddPyramid.technique, rsDefault
    
    rem Planet
    rem » Displacement mapping
    if guiCheckbox_IsChecked(guiPlanet.gcbEnableDisplacement)
        rem Only update settings if they aren't already set
        if not bIsPlanetDisplaced
            dx11 set vertex shader ddPlanet.technique, vertLayout, vs
            dx11 set tessellation shaders ddPlanet.technique, hs, ds
            dx11 set object topology objPlanet, TOPOLOGY_6_CONTROL_POINT_PATCHLIST
            bIsPlanetDisplaced = true
        endif
    else
        if bIsPlanetDisplaced
            dx11 set vertex shader ddPlanet.technique, vertLayout, vsNoTess
            dx11 set tessellation shaders ddPlanet.technique, nullptr, nullptr
            dx11 set object topology objPlanet, TOPOLOGY_TRIANGLELIST
            bIsPlanetDisplaced = false
        endif
    endif
    rem » Wireframe rendering
    if guiCheckbox_IsChecked(guiPlanet.gcbWireframe) = true and dx11 get rasterizer state(ddPlanet.technique) <> rsWireframe then dx11 set rasterizer state ddPlanet.technique, rsWireframe
    if guiCheckbox_IsChecked(guiPlanet.gcbWireframe) = false and dx11 get rasterizer state(ddPlanet.technique) <> rsDefault then dx11 set rasterizer state ddPlanet.technique, rsDefault
    
    
    rem Draw some informative text to the screen
    dx11 draw text SCREEN_WIDTH / 2, 20, "[c=0xffffff80]Move camera forward by pressing LMB, backward by pressing RMB and look around using the middle mouse button.", true
    dx11 draw text SCREEN_WIDTH / 2, 36, "[c=0xffffff80]Press [c=0xffbebeff][shift][c=0xffffff80] to increase movement speed.", true
    dx11 draw text SCREEN_WIDTH / 2, SCREEN_HEIGHT - 20, "[c=0xffffff80]FPS: " + str$(dx11 get screen fps()), true
    
    dx11 sync
endwhile
end


rem Helper function for creating a grid-like plane with an arbitrary number of tiles.
rem This is used for creating the floor plane since each edge can only be tessellated 
rem up to 32 times by the GPU and the floor obviously needs higher resolution than 32x32.
rem Using this method we can create a 64x64 plane and then subdivide each tile up to 32
rem times depending on distance from the camera, which gives ample resolution for displacement 
rem mapping our particular floor object with its set texture tiling.
rem The grid plane is created as a normal triangle list topology mesh.
rem It also assumes the precense of the POSITION, NORMAL and TEXCOORD semantics in the provided
rem vertex layout.
function CreateGridPlane(vertLayout as dword, width as float, depth as float, across as dword, down as dword, uScale as float, vScale as float)
    vertsAcross = across + 1
    vertsDown   = down + 1
    indexCount  = across * down * 6
    uStep#      = uScale / across
    vStep#      = vScale / down
    xStart#     = -width / 2
    zStart#     = -depth / 2
    xStep#      = width / across
    zStep#      = depth / down
    
    mesh = dx11 create mesh(vertsAcross * vertsDown, indexCount, vertLayout)
    
    rem Write vertex data
    dx11 lock mesh vertexdata mesh
    offsetPos       = dx11 get mesh vertexdata semantic offset(mesh, "POSITION")
    offsetNormal    = dx11 get mesh vertexdata semantic offset(mesh, "NORMAL")
    offsetTexcoord  = dx11 get mesh vertexdata semantic offset(mesh, "TEXCOORD")
    for z = 0 to down
        for x = 0 to across
            v = (z * vertsDown) + x
            rem Instead of writing vectors we can just write individual float elements and offset the 
            rem write position by 4 bytes (the size of a 32-bit float) for each element in the vector. 
            rem A vector2 is just 2 floats (8 bytes) next to each other, a vector3 is 3 adjacent floats 
            rem (12 bytes) and a vector4 is 4 floats (16 bytes). The same holds true for a matrix which 
            rem consists of 16 floats (128 bytes). However, the vector / matrix function versions should 
            rem be used with GPU buffers (such as constant buffers) since they are 16-byte aligned and as 
            rem such there may need to be padding inserted between certain elements. The built-in functions
            rem will handle this automatically.
            dx11 set mesh vertexdata float mesh, v, offsetPos,          xStart# + xStep# * x    ` X
            dx11 set mesh vertexdata float mesh, v, offsetPos + 4,      0.0                     ` Y
            dx11 set mesh vertexdata float mesh, v, offsetPos + 8,      zStart# + zStep# * z    ` Z
            dx11 set mesh vertexdata float mesh, v, offsetNormal,       0.0
            dx11 set mesh vertexdata float mesh, v, offsetNormal + 4,   1.0
            dx11 set mesh vertexdata float mesh, v, offsetNormal + 8,   0.0
            dx11 set mesh vertexdata float mesh, v, offsetTexcoord,     uStep# * x
            dx11 set mesh vertexdata float mesh, v, offsetTexcoord + 4, vStep# * z
        next x
    next z
    dx11 unlock mesh vertexdata mesh
    
    // Write index data
    dx11 lock mesh indexdata mesh
    index = 0
    for z = 0 to down - 1
        for x = 0 to across - 1
            dx11 set mesh indexdata mesh, index,     z * vertsAcross + x
            dx11 set mesh indexdata mesh, index + 1, (z + 1) * vertsAcross + x
            dx11 set mesh indexdata mesh, index + 2, z * vertsAcross + x + 1
            dx11 set mesh indexdata mesh, index + 3, (z + 1) * vertsAcross + x
            dx11 set mesh indexdata mesh, index + 4, (z + 1) * vertsAcross + x + 1
            dx11 set mesh indexdata mesh, index + 5, z * vertsAcross + x + 1
            inc index, 6
        next x
    next z
    dx11 unlock mesh indexdata mesh
    
    rem Create and return an object from the mesh
    obj = dx11 create object(mesh)
    dx11 delete mesh mesh
endfunction obj

rem For proper displacement mapping, the 3D / texture artist(s) need to account for this
rem and work with it in mind. In essence smooth surfaces (where the vertex normals are 
rem interpolated between each adjacent vertex) normally work well with it but when we 
rem get so called disjoint vertices, that is several vertices that share the same position 
rem but have different normals (such as at the corner points of a cube / 3D box) there will 
rem be issues with a naïve displacement mapping algorithm that simply displaces along the normals - 
rem if the normals for two vertices at the same position differ and the vertices are displaced the 
rem inevitable outcome will be that those vertices are no longer "connected" (sharing the same position) 
rem and gaps will appear in the mesh. This is clearly undesirable.
rem A simple solution is for the artist to manually move any such shared position vertices so that 
rem they are very close to each other but not at the exact same location and connect them with an edge, 
rem allowing the normals to be interpolated between these normals. If the vertices are close enough the  
rem edge will still appear sharp, although a very small part of it is in fact rounded when seen to the normals.
rem Another problem with such positions sharing normals, or very close positions with vastly different normals, 
rem is that they should have the same displacement value sampled from the displacement map; this may require 
rem the artist to manually map the displacement map to a secondary UV layer (ie. not all texcoords will be shared for the 
rem main texture and the displacement map). This is often necessary even if vertices aren't sharing 
rem a position (which they really shouldn't unless they have disjoint normals as described above) when 
rem it comes to seams and generally in certain areas where the mesh geometry is intricate. This can be seen
rem at the edges of the pyramid in this example program where the corner vertices gets displaced in such a fashion 
rem that the extruded polygons intersect each other (although this can mostly be blamed on the pyramid having a 
rem bad UV mapping, where the texture doesn't wrap around the edges as it should).
rem Since I'm no graphics artist I will use a simple solution to convert existing triangle list models into a 
rem format that alleviates the first described problems above.
rem This is done by 
rem     (1) averaging the normals for any vertices that share the same position, and
rem     (2) selecting a dominant vertex for each vertex so that all shared vertices
rem         will sample the same displacement map texel.
rem The dominant vertices (which can be the same sole vertex for all the cases where
rem there are no shared vertices at a position, so no danger there) can be made available 
rem to the hull shader by setting a topology that defines more indices per face (6 as opposed 
rem to the original 3 will be used here, with the last set of 3 being the dominant vertices
rem corresponding to the first 3). It is also worth noting that if we change the topology back 
rem to use only 3 indices per face, we can keep the same (larger) index buffer; the input assembler 
rem will only send the first 3 vertices per face to the vertex shader, then skip over the remaining 
rem ones until the next face.
rem NOTE THAT THIS FUNCTION EXPECTS THE INPUT MESH TO USE THE TRIANGLELIST TOPOLOGY, ANYTHING ELSE 
rem WILL CAUSE UNDEFINED BEHAVIOUR! (Most any non-custom format meshes will use triangle lists).
function PrepareMeshForDisplacement(mesh as dword)
    rem Lock vertex and index buffers
    dx11 lock mesh vertexdata mesh
    dx11 lock mesh indexdata mesh
    
    rem Vertex data offsets and buffer element counts
    posOffset       = dx11 get mesh vertexdata semantic offset(mesh, "POSITION")
    normalOffset    = dx11 get mesh vertexdata semantic offset(mesh, "NORMAL")
    vertCount       = dx11 get mesh vertex count(mesh)
    indexCount      = dx11 get mesh index count(mesh)
    
    
    rem This will set the index to the first found (arbitrarily chosen to be dominant) 
    rem vertex having the same position as the vertex whose id is the index into the array
    local dim CheckedVert(vertCount - 1) as integer
    rem Initialize to -1 since the default initial value of 0 is a valid vertex id
    for v = 0 to vertCount - 1
        CheckedVert(v) = -1
    next v
    
    rem Step through finding dominant vertices and average normals
    local dim VertList(-1) as dword     ` Temporary list; reset for each inner pass
    normal      = dx11 create temp vector3()
    avgNormal   = dx11 create temp vector3()
    for v = 0 to vertCount - 1
        rem If this vertex has already been checked we need not further process it
        if CheckedVert(v) = -1
            rem The vertex itself should be dominant since it is the first found with the given position
            CheckedVert(v) = v
            array insert at top VertList()
            VertList(0) = v
            rem Look for other vertices sharing this position
            dx# = dx11 get mesh vertexdata float(mesh, v, posOffset)
            dy# = dx11 get mesh vertexdata float(mesh, v, posOffset + 4)
            dz# = dx11 get mesh vertexdata float(mesh, v, posOffset + 8)
            for v2 = v + 1 to vertCount - 1
                x# = dx11 get mesh vertexdata float(mesh, v2, posOffset)
                y# = dx11 get mesh vertexdata float(mesh, v2, posOffset + 4)
                z# = dx11 get mesh vertexdata float(mesh, v2, posOffset + 8)
                rem Match?
                if x# = dx# and y# = dy# and z# = dz#
                    CheckedVert(v2) = v
                    array insert at top VertList()
                    VertList(0) = v2
                endif
            next v2
            
            rem By now all shared vertices (at least 1) should be in VertList.
            rem Compute the average normal and set it for all of these vertices.
            dx11 set vector3 avgNormal, 0.0, 0.0, 0.0  ` Reset
            count = array count(VertList())
            for n = 0 to count
                dx11 get mesh vertexdata vector3 mesh, VertList(n), normalOffset, normal
                avgNormal = dx11 add vector3(avgNormal, normal, avgNormal)
            next n
            avgNormal = dx11 normalize vector3(avgNormal, avgNormal)
            
            rem Set the average normal to all found position-sharing vertices
            for n = 0 to array count(VertList())
                dx11 set mesh vertexdata vector3 mesh, VertList(n), normalOffset, avgNormal
            next n
            
            rem Reset the vertex list for consecutive passes
            empty array VertList()
        endif
    next v
    
    rem Create a new index buffer using the 6 control points patchlist topology.
    rem This basically means that we'll have 6 indices per face instead of the previous 3; 
    rem as such we can store the dominant vertices (currently stored in the CheckedVert array) 
    rem for every vertex in order to ensure that the hull shader can sample the texture coordinates 
    rem from these so that the same displacement mapping coordinates will be sampled for all 
    rem vertices that share a position, thus preventing gaps from appearing in the meshes.
    faceCount = indexCount / 3  ` Note: assuming the provided mesh uses a triangle-style index buffer!
    local dim ibuf(faceCount * 6) as dword
    
    rem Fill out our new index buffer
    for f = 0 to faceCount - 1
        src = f * 3
        dst = f * 6
        
        rem Actual vertices
        ibuf(dst)       = dx11 get mesh indexdata(mesh, src)
        ibuf(dst + 1)   = dx11 get mesh indexdata(mesh, src + 1)
        ibuf(dst + 2)   = dx11 get mesh indexdata(mesh, src + 2)
        
        rem Dominant vertices
        rem (Note that these will be identical to the previous one if this vertex doesn't have a shared position)
        ibuf(dst + 3)   = CheckedVert(ibuf(dst))
        ibuf(dst + 4)   = CheckedVert(ibuf(dst + 1))
        ibuf(dst + 5)   = CheckedVert(ibuf(dst + 2))
    next f
    
    rem Unlock index and vertex data
    dx11 unlock mesh indexdata mesh
    dx11 unlock mesh vertexdata mesh
    
    rem Set our new index buffer (note that the mesh's index data must not be locked when replacing the index buffer)
    dx11 set mesh index buffer mesh, ibuf()
    
    rem Set the primitive topology.
    rem Note that in order to use the hull shader, the input data must have a control point patchlist-type
    rem topology.
    dx11 set mesh topology mesh, TOPOLOGY_6_CONTROL_POINT_PATCHLIST
    
    rem Release temporary arrays
    undim ibuf()
    undim VertList()
    undim CheckedVert()
endfunction

rem This function calls the above function for all meshes in a specific object
function PrepareForDisplacement(obj as dword)
    local dim limb(-1) as dword
    dx11 enumerate object limbs limb(), obj
    for l = 0 to array count(limb())
        for m = 0 to dx11 get limb mesh count(limb(l)) - 1
            PrepareMeshForDisplacement(dx11 get limb mesh(limb(l), m))
        next m
    next l
endfunction
