Heightmap Editor Scripting For Noobs, Part 1

Published January 14, 2007
Advertisement
A Quick and Dirty Crater
Grab the latest build of the heightmap editor here.

This is a real quick example of how to use the scripting interface to construct a simple elevation map of a crater. The crater construction function is whipped together in about 10 min, with no research on the web into exact crater mathematics, so the shape of it could stand some improvement, but for the purposes of the example it should be sufficient.

To start with, create a text file called crater.lua. First up will be our crater function:

function build_crater(buf, x, y, radius, rimheight, craterdepth, falloff)  local w=buf:getWidth()  local h=buf:getHeight()    for i=0,w-1,1 do    for j=0,h-1,1 do      dx = x-i      dy = y-j      dist = sqrt(dx*dx + dy*dy)      scaleabs = abs(radius - dist)      if(dist        depthscale=(radius-dist)/radius        terradd = -depthscale*craterdepth      else terradd = 0 end            if(scaleabs        fs = scaleabs/falloff        terradd = terradd + (1.0-fs) * rimheight      end            buf:set(i,j,terradd)    end  endend


What this does is create a crater of a certain radius in a buffer. The crater can have specifiable constants for rim height and crater depth. The falloff parameter defines the 'thickness' of the crater rim; greater falloff creates a thicker rim with shallower sideslopes.

The way this function works is we iterate the buffer, and for each sample we calculate the distance from the sample to the crater's center. If this distance is less than the radius of the crater, then we compute a scaling factor with (radius-dist)/radius and multiply this by the specified crater depth, to get a sort of shallow bowl shape for the crater. Then we take the absolute value of the difference between the radius and the sample distance. If this value lies within the falloff range, then we need to calculate another scaling factor with scaleabs/falloff to scale the rim height, and add it to the base height. A sample crater generated thusly looks like:



(As you can see, the shape could use some tweaking. If I ever do an actual crater map level, I'll look into the math behind a crater's shape in depth.)

It's a little smooth, though, so we need to modify it with some noise. Our next function is going to setup a couple noise modules, and will use our crater map to blend between them. We'll set up a low, bumpy terrain for the low areas, and a high, mountainous terrain for the crater rim. We will also add a slight amount of turbulence to the crater map, so that the rim isn't such a perfect circle. Here's the build_crater_map() function:

function build_crater_map()  local b=builder:getFloatBuffer()  local mw=b:getWidth()  local mh=b:getHeight()    -- Crater constants; tweak to suit  local craterx = mw/2  local cratery = mh/2  local craterrad = mw/3  local craterdepth = 1.0  local craterrim = 3.0  local craterfall = 20    -- Build the crater, and set to a Buffer module  build_crater(b, craterx, cratery, craterrad, craterrim, craterdepth, craterfall)  b:scaleToRange(-1,1)    local crater = BufferModule()  crater:SetMapping(-2,-2,2,2)  crater:SetBuffer(b)    local cratermap = TurbulenceModule()  local seed  seed=g_randInt()  cratermap:SetSeed(seed)  cratermap:SetPower(0.05)  cratermap:SetFrequency(2.5)  cratermap:SetSourceModule(0,crater)      -- Now, setup terrain types      -- Lowland type for inside crater and outside rim  local lowterrainbase = BillowModule()  lowterrainbase:SetFrequency(3)  seed=g_randInt()  lowterrainbase:SetSeed(seed)    local lowterrainsb = ScaleBiasModule()  lowterrainsb:SetSourceModule(0,lowterrainbase)  lowterrainsb:SetScale(0.25)  lowterrainsb:SetBias(-0.95)    local lowterrain = CacheModule()  lowterrain:SetSourceModule(0,lowterrainsb)      -- Mountainous type for crater rim  local highterrainbase = BillowModule()  highterrainbase:SetFrequency(1.5)  seed=g_randInt()  highterrainbase:SetSeed(seed)    local highterrainsb = ScaleBiasModule()  highterrainsb:SetSourceModule(0,highterrainbase)  highterrainsb:SetScale(1.0)  highterrainsb:SetBias(2.0)    local highterrain = CacheModule()  highterrain:SetSourceModule(0, highterrainsb)    -- Now, setup a blend module    local finalterrain = BlendModule()  finalterrain:SetSourceModule(0, lowterrain)  finalterrain:SetSourceModule(1, highterrain)    finalterrain:SetControlModule(cratermap)    -- Test it out  local cb=builder:getFloatBuffer()  fillBufferFromModule(cb, finalterrain, -2,-2,2,2)  blurFloatArray(cb, blur55)  cb:scaleToRange(0,800)  builder:setHeightMap(cb)    cb:scaleToRange(0,1)  dumpFloatArrayTGA(cb,"cratermap.tga")    builder:releaseFloatBuffer(b)  builder:releaseFloatBuffer(cb)end


We first generate a crater map, then assign it to a Buffer module for chaining with other libnoise modules. We set the mapping to (-2,-2,2,2) because that is the final range we will map the noise source from into the height buffer. The Buffer module will tile a buffer source across the input range on (x,y), ignoring z. So if we were to set the mapping here to (-1,-1,1,1) and still map the final noise from (-2,-2,2,2) we would get 4 evenly tiled craters across the map.

After we have our buffer source, we setup an intermediary Turbulence module to perturb it just a little bit. You can tweak the frequency and power settings to get different turbulence variations. Too high of a power, however, distorts the crater rim beyond recognition.

Next, we setup a couple noise generators. They are both Billow modules but with different settings. One is a small-scale, surface noise map, and the other is a larger scale, lower frequency generator for the crater rim.

After we have our noise sources, we setup a final Blend module, and specify lowterrain and highterrain for the sources, and our blend map's Turbulence module as the control. This causes the crater map to act as an interpolant between low and high terrains.

A sample crater map thusly generated may look like:



Put these functions into the file crater.lua, then execute the heightmap editor. Press 'ESC' to open the console, then execute the command dofile("crater.lua") When that is finished, type build_crater_map() and press enter. The editor should pause for a few seconds while the map is generated, then you should see something like this:



You can then use the provided editing tools to tweak the map as you see fit.

Obviously, the crater shape could use some improvement, and you may want to tweak the high and low terrain sources for better results, but this is a place to start anyway.

I could even see doing the final combination differently, by using the crater map merely as a height multiplier. You could add radial splash ridges to the crater map, and these would raise terrain in a sort of spoked pattern to give the appearance of dirt having splashed out at impact. You could also raise the center of the crater to give that little hill that a lot of impact craters seem to have.

EDIT: After posting, I fiddled around some more and came up with this function for building a more hemispherical crater, like some of the smaller/newer lunar impact craters appear. Again, its probably not right, but it looks okay.

function build_crater_2(buf, x, y, radius, rimheight, craterdepth, falloff)  local w=buf:getWidth()  local h=buf:getHeight()    for i=0,w-1,1 do    for j=0,h-1,1 do      dx = x-i      dy = y-j      dist = sqrt(dx*dx + dy*dy)      if(dist        height = 1-((dx*dx + dy*dy) / (radius*radius))        height = rimheight - (height*craterdepth)      else        if((dist-radius) < falloff) then          fallscale = (dist-radius)/falloff          height = (1-fallscale) * rimheight        else          height = 0        end      end            buf:set(i,j,height)    end  end  buf:scaleToRange(0,1)  dumpFloatArrayTGA(buf,"crater2.tga")end


I generated the following with rimheight=3, craterdepth=4, falloff=20.



The final result (with lowterrain scaled to 0.1 to make it less pronounced):



SECOND EDIT:
After more tweaking, I think I prefer an additive terrain structure, rather than the blend I was doing.

function build_crater_map2()  local b=builder:getFloatBuffer()  local mw=b:getWidth()  local mh=b:getHeight()    -- Crater constants; tweak to suit  local craterx = mw/2  local cratery = mh/2  local craterrad = mw/4  local craterdepth = 10.0  local craterrim = 3.0  local craterfall = 60    build_crater_2(b,craterx,cratery,craterrad,craterrim, craterdepth, craterfall)  b:scaleToRange(-1,1)    local crater = BufferModule()  crater:SetMapping(-2,-2,2,2)  crater:SetBuffer(b)    local cratermap = TurbulenceModule()  local seed  seed=g_randInt()  cratermap:SetSeed(seed)  cratermap:SetPower(0.05)  cratermap:SetFrequency(2.5)  cratermap:SetSourceModule(0,crater)    --    -- Lowland type for inside crater and outside rim  local lowterrainbase = BillowModule()  lowterrainbase:SetFrequency(3)  seed=g_randInt()  lowterrainbase:SetSeed(seed)    local lowterrainsb = ScaleBiasModule()  lowterrainsb:SetSourceModule(0,lowterrainbase)  lowterrainsb:SetScale(0.1)  lowterrainsb:SetBias(1.0)    local lowterrain = CacheModule()  lowterrain:SetSourceModule(0,lowterrainsb)    -- Additive module  local mult = AddModule()  mult:SetSourceModule(0,lowterrain)  mult:SetSourceModule(1,cratermap)    -- Test it out  local cb=builder:getFloatBuffer()  fillBufferFromModule(cb, mult, -2,-2,2,2)  blurFloatArray(cb, blur55)  cb:scaleToRange(0,800)  builder:setHeightMap(cb)    cb:scaleToRange(0,1)  dumpFloatArrayTGA(cb,"cratermap.tga")    builder:releaseFloatBuffer(b)  builder:releaseFloatBuffer(cb)end


This simply takes the crater map and adds the low-level, high-frequency surface noise map to it. Our final crater map, then, looks like:

0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement