Terrain Building API in Leadwerks 5 Beta

396 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:

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:

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.

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

--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.

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.

• 4

Can we manipulate the terrain in real time? If so, that reminds me of a feature of the Source engine that was removed sometime after HL2: Episode 1.

Not sure how efficient it would be though.

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

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

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.

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?

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.

Quick perlin noise example, but there is a seam issue you can see:

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

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)

--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```

Join the conversation

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

×   Pasted as rich text.   Paste as plain text instead

Only 75 emoji are allowed.

×   Your previous content has been restored.   Clear editor

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

×
• Blog Entries

• By jen in jen's Blog 0
My small project will be called Foregate, it will be a dark medieval Diablo style single player action RPG.
The graphics will be simple, no PBR, 256x256 map, reasonably low-res models.
Camera style? Top-down-ish I think? Like in Diablo exactly - and because the camera is not directly in-front of the 3 models, I can get away with low-resolution assets - bonus. Also, with top-down view, I won't have to worry about high resolution sky-boxes.
What's my plan for this project?
I plan to make this project as small and as simple as possible, possibly release it as open-source, and have fun with it of course.
My previous experience with game development (1-2 years ago?) was amateurish I think, still is now. I want to give it a go again, this time with experience although my skill in C++ is not really that good? Maybe I can improve it in this project.
More about the game
The content is not set in stone yet but I have a general idea of how the mechanics is going to look and feel - Diablo-ish obviously. It'll have monsters (ancient & mythical probably), loot when killing a monster, gold as in-game currency, visual grid inventory, player stats (level, strength, agility, vitality, energy, &c.).
The game will be single-player. Possibly a coop multiplayer also? I don't have any interest in making massive multi-player.
I started my development yesterday with the basic preparations (setting up project environment, &c.), today I made my first step in developing the core components; worker class, game state, task class.
I have a game state that keeps a single source of truth for the entire application; all game data will be stored in this class as "states".
I also have a "Worker" which will do the processing of tasks in the game.
I also have "object" class, this can be a monster, the player, a weapon, a prop, or an NPC.
So the idea is to have a CQRS type of interaction between the classes and the data. Any action in the game will be interpreted as "Task" for the Worker class. The worker class iterates through the Task. Tasks can be created by any class interfaced with the Worker class trough "addNewTask" and the new tasks can be of a certain type i.e.: ATTACK, IDLE, SAVE_GAME, EXIT_GAME, the new task will also have a payload data and it's processed according to its task type e.g. an ATTACK with payload "{ Damage: 10, Target: MonsterA }" will reduce the health of MonsterA by 10 - the worker class will change the game state; find MonsterA in MonsterState and reduce its health by 10.
I think it's advantageous to have this type of centralized module where all actions are processed; I can do all sorts of procedures during the processes, maybe debug data, filter actions, mutate payloads, and such.
How much time am I going to put into this?
A couple of hours a day for 3 days a week maybe.
So it's all a rough sketch for now and it's heading the right direction. I'll have more to report later on.

This is Forgate Castle, minus the castle, in the map Forgate; the starting location for the player. The fortification will have merchants, and quest givers.

• By jen in jen's Blog 4
I've got a bit of free time on my hands for a while. I plan to take up Leadwerks again and come up with a simple project to have fun with.
I use VSCode at work and I don't have a Windows machine at the moment. Codeblocks is a bit dated now and I'm not sure if it has all the features to fit my workflow so I can't use it.
It's a bit fiddly to build C++ apps on VSCode. There's a prescribed method of setting up your C++ project in the official hubs but I don't think those are necessary in my case, I just need a script that builds the project which I have below.
By the way, VSCode website is here: https://code.visualstudio.com/
Have fun.
Save as build.sh in your project's root folder and run using build.sh or build.sh -r for release. Works for Stable version LW and any previous versions(?) - at least v4.5 (archived). BETA branch (4.6?) doesn't seem to build for me, missing Leadwerks.a file.
Disclaimer: not really fully tested for bigger projects? I've only tested this with sample projects that have no additional project folder structure in its Source so I'm not sure if the script will propagate its search for CPP files thoroughly and process each file successfully in bigger projects. Back-up project files before running the script for the first time please.
Updated: 26/02/2020

• 0
A new update is available for beta testers. This adds a new LOD system to the terrain system, fixes the terrain normals, and adds some new features. The terrain example has been updated ans shows how to apply multiple material layers and save the data.

Terrain in LE4 uses a system of tiles. The tiles are rendered at a different resolution based on distance. This works great for medium sized terrains, but problems arise when we have very large view distances. This is why it is okay to use a 4096x4096 terrain in LE4, but your camera range should only show a portion of the terrain at once. A terrain that size will use 1024 separate tiles, and having them all onscreen at once can cause slowdown just because of the number of objects. That have to be culled and drawn.

Another approach is to progressively divide the terrain up into quadrants starting from the top and working down to the lowest level. When a box is created that is a certain distance from the camera, the algorithm stops subdividing it and draws a tile. The same resolution tile is drawn over and over, but it is stretched to cover different sized areas.

This approach is much better suited to cover very large areas. At the furthest distance, the entire terrain will be drawn with just one single 32x32 patch. Here it is in action with a 2048x2048 terrain, the same size as The Zone:
This example shows how to load a heightmap, add some layers to it, and save the data, or load the data we previously saved: