Jump to content

Terrain Building API in Leadwerks 5 Beta

Josh

534 views

An often-requested feature for terrain building commands in Leadwerks 5 is being implemented. Here is my script to create a terrain. This creates a 256 x 256 terrain with one terrain point every meter, and a maximum height of +/- 50 meters:

--Create terrain
local terrain = CreateTerrain(world,256,256)
terrain:SetScale(256,100,256)

Here is what it looks like:

Image1.thumb.jpg.4e6c698dfc0f85c631f615a51ccd1199.jpg

A single material layer is then added to the terrain.

--Add a material layer
local mtl = LoadMaterial("Materials/Dirt/dirt01.mat")
local layerID = terrain:AddLayer(mtl)

We don't have to do anything else to make the material appear because by default the entire terrain is set to use the first layer, if a material is available there:

Image2.thumb.jpg.342d92d0b0c09fa2f18a3e87eec63fe0.jpg

Next we will raise a few terrain points.

--Modify terrain height
for x=-5,5 do
	for y=-5,5 do
		h = (1 - (math.sqrt(x*x + y*y)) / 5) * 20
		terrain:SetElevation(127 + x, 127 + y, h)
	end
end

And then we will update the normals for that whole section, all at once. Notice that we specify a larger grid for the normals update, because the terrain points next to the ones we modified will have their normals affected by the change in height of the neighboring pixel.

--Update normals of modified and neighboring points
terrain:UpdateNormals(127 - 6, 127 - 6, 13, 13)

Now we have a small hill.

Image3.thumb.jpg.2ab2154a298af8047b78d78e1c479a37.jpg

Next let's add another layer and apply it to terrain points that are on the side of the hill we just created:

--Add another layer
mtl = LoadMaterial("Materials/Rough-rockface1.json")
rockLayerID = terrain:AddLayer(mtl)

--Apply layer to sides of hill
for x=-5,5 do
	for y=-5,5 do
		slope = terrain:GetSlope(127 + x, 127 + y)
		alpha = math.min(slope / 15, 1.0)
		terrain:SetMaterial(rockLayerID, 127 + x, 127 + y, alpha)
	end
end

We could improve the appearance by giving it a more gradual change in the rock layer alpha, but it's okay for now.

Image4.thumb.jpg.2b4ad77c892f266a83c990b309fbbc4a.jpg

This gives you an idea of the basic terrain building API in Leadwerks 5, and it will serve as the foundation for more advanced terrain features. This will be included in the next beta.

  • Like 4


6 Comments


Recommended Comments

Yes, you can manipulate the terrain in real-time with no problems. Collision and everything will work just fine.

  • Like 1

Share this comment


Link to comment

And what about Navmesh ? This should be updated too, the problem was regarding performances on bigger maps....

  • Like 1

Share this comment


Link to comment
1 hour ago, Marcousik said:

And what about Navmesh ? This should be updated too, the problem was regarding performances on bigger maps....

A static cache will be used for the terrain and other static objects, instead of reprocessing everything every time something changes.

Share this comment


Link to comment
2 hours ago, Josh said:

A static cache will be used for the terrain and other static objects, instead of reprocessing everything every time something changes.

Does this mean you're not planning on implementing a dynamic navmesh, like for doors that open to let characters through?  Or is that something different?

Share this comment


Link to comment

Objects are separated into static and dynamic. When a dynamic object moves only the dynamic objects gets recalculated. The static data stays the same.

Shadow maps work with the same principle, so this is actually convenient because static objects can be marked as such for both purposes.

  • Like 1

Share this comment


Link to comment

Here is the code for terrain.lua

--Get the primary display
local displaylist = ListDisplays()
local display = displaylist[1];
if display == nil then RuntimeError("Primary display not found.") end
local displayscale = display:GetScale()

--Create a window
local window = CreateWindow(display, "Terrain", 0, 0, math.min(1280 * displayscale.x, display.size.x), math.min(720 * displayscale.y, display.size.y), WINDOW_TITLEBAR + WINDOW_CENTER)

--Create a rendering framebuffer
local framebuffer = CreateFramebuffer(window);

--Create a world
local world = CreateWorld()

--Create a camera
local camera = CreateCamera(world)
--camera:SetClearColor(0.25)
camera:Turn(35,0,0)
camera:Move(0,12,-33)

--Create a light
local light = CreateLight(world,LIGHT_DIRECTIONAL)
light:SetRotation(45,-55,0)
light:SetColor(4,4,4,1)

--Create terrain
local terrain = CreateTerrain(world,256,256)
terrain:SetScale(256,200,256)

--Add a material layer
local mtl = LoadMaterial("Materials/Dirt/dirt01.mat")
local layerID = terrain:AddLayer(mtl)
terrain:SetMaterial(layerID, 0, 0, 256, 256, 1)


--PERLIN NOISE

	local p = {}

	local permutation = {151,160,137,91,90,15,
	  131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
	  190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
	  88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
	  77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
	  102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
	  135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
	  5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
	  223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
	  129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
	  251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
	  49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
	  138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
	}

	for i = 0, 255 do
	  p[i] = permutation[i + 1]
	  p[256 + i] = permutation[i + 1]
	end

	local function fade(t)
	  return t * t * t * (t * (t * 6 - 15) + 10)
	end

	local function lerp(t, a, b)
	  return a + t * (b - a)
	end

	local function grad(hash, x, y, z)
	  local h, u, v = hash % 16
	  if h < 8 then u = x else u = y end
	  if h < 4 then v = y elseif h == 12 or h == 14 then v = x else v = z end
	  local r
	  if h % 2 == 0 then r = u else r = -u end
	  if h % 4 == 0 then r = r + v else r = r - v end
	  return r
	end

	local function perlin(x, y, z)
	  y = y or 0
	  z = z or 0
	  local X = math.floor(x % 255)
	  local Y = math.floor(y % 255)
	  local Z = math.floor(z % 255)
	  x = x - math.floor(x)
	  y = y - math.floor(y)
	  z = z - math.floor(z)
	  local u = fade(x)
	  local v = fade(y)
	  local w = fade(z)
	  A   = p[X     ] + Y
	  AA  = p[A     ] + Z
	  AB  = p[A + 1] + Z
	  B   = p[X + 1] + Y
	  BA  = p[B     ] + Z
	  BB  = p[B + 1] + Z
	  return lerp(w, lerp(v, lerp(u, grad(p[AA      ], x    , y     , z     ),
																	 grad(p[BA      ], x - 1, y     , z     )),
													 lerp(u, grad(p[AB      ], x    , y - 1, z      ),
																	 grad(p[BB      ], x - 1, y - 1, z      ))),
									 lerp(v, lerp(u, grad(p[AA + 1], x      , y     , z - 1), 
																	 grad(p[BA + 1], x - 1, y       , z - 1)),
													 lerp(u, grad(p[AB + 1], x      , y - 1, z - 1),
																	 grad(p[BB + 1], x - 1, y - 1, z - 1))))
	end
	
	 function fbm(x, y, z, octaves, lacunarity, gain)
	  octaves = octaves or 8
	  lacunarity = lacunarity or 2
	  gain = gain or 0.5
	  local amplitude = 1.0
	  local frequency = 1.0
	  local sum = 0.0
	  for i = 0, octaves do
			sum = sum + amplitude * perlin(x * frequency, y * frequency, z * frequency)
			amplitude = amplitude * gain
			frequency = frequency * lacunarity
	  end
	  return sum
	end



--- ### This should match terrain created
local terrainsize=256
local octaves=12  		-- more is finer noise but slower
local lacunarity=1.9		-- size of noise
local gain=0.6		-- height gain
for i=-127,127 do
    for j=-127,127 do
		local noise=fbm(i/terrainsize, 11, j/terrainsize, octaves, lacunarity, gain)
		terrain:SetElevation(127+i,127+j, 15+noise*20)
	end
end

--Update normals of modified and neighboring points
terrain:UpdateNormals(0, 0, 256, 256)

--Add another layer
mtl = LoadMaterial("Materials/Rough-rockface1.json")
rockLayerID = terrain:AddLayer(mtl)

--Apply layer to sides of hill
for x=-127,127 do
	for y=-127,127 do
		slope = terrain:GetSlope(127 + x, 127 + y)
		alpha = math.min(slope / 15, 1.0)
		terrain:SetMaterial(rockLayerID, 127 + x, 127 + y, alpha)
	end
end

--Main loop
while window:Closed() == false do

	world:Update()
	world:Render(framebuffer)

end

 

  • Like 1

Share this comment


Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...