I use this from @Ignatz modified to use PseudoMesh from @LoopSpace.

I think it from Ignatz project with the trees (but not used here).

```
function terrain_setup()
print("in terrain setup")
rad=math.pi/180
col=color(138, 92, 36, 255)
meshTable={}
meshTable[1]={}
meshTable[2]={}
-- AddLevel(-500,0,0,1000,1000,gravel,.1)
HM={} --holds height maps, one for each terrain area we build
--add a terrain area --NEW
-- x,z = bottom left corner
-- y = height around the edges
-- w,d = width and depth of area to be terrained (pixels)
-- p = pixels per tile
-- f = minimum height (pixels), can be lower than y
-- h = max height (pixels)
-- r = whether to randomise terrain each run (true/false)
-- n = noise level (bigger numbers result in bumpy terrain, smaller gives smoother results)
-- tbl = table of heights, if provided
-- img = image or string name of image (do not include library name)
-- sc = image scaling factor (see above) 0-1
-- hm = height map storing heights of each point in
-- ns = the fraction by which noise level increases for each tile as we go deeper into the terrain
-- eg a value of 0.3 means that the outside ring of tiles has nil noise (as always, so these tiles
-- exactly meet the surface around), the next ring of tiles has noise of 0.3 of the full level,
-- the third ring of tiles has noise of 0.6, and so on, up to a maximum of 1.0. The bigger ns, the
-- the more you will get cliffs at the edges of the terrain
terrainWidthOver2=1000
terrainDepthOver2=1000
AddTerrain( {x=-terrainWidthOver2, y=0.0, z=-terrainWidthOver2,
width=terrainWidthOver2*2,depth=terrainDepthOver2*2,
tileSize=25,minY=0,maxY=50,random=false,
noiseLevel=0.05,noiseStep=0.3,sc=0.11,
-- img=gravel,
heightMaps=HM} )
terrainEnt=scene:entity()
terrainEnt.active=True
terrainEnt.position=offset
-- terrainEnt.position=vec3(0,0,0)
local tmodel=meshTable[1][1]:toModel()
terrainEnt.model=tmodel
--terrainEnt.material = craft.material("Materials:Standard")
terrainEnt.material = craft.material.preset("Surfaces:Desert Cliff")
-- terrainEnt.material = craft.material.preset("Surfaces:Stone Brick")
-- terrainEnt.material.map = gravel
-- terrainEnt.material.map =readImage("Project:sand-seamless"):copy(1,1,512,512)
terrainEnt.material.map =readImage("Project:TexturesCom_MuddySand2_2x2_1K_albedo")
-- print("vertex, index count",terrainEnt.model.vertexCount, indexCount)
end
-- x,z = bottom left corner
-- y = height around the edges
-- w,d = width and depth of area to be terrained (pixels)
-- p = pixels per tile
-- f = minimum height (pixels), can be lower than y
-- h = max height (pixels)
-- r = whether to randomise terrain each run (true/false)
-- n = noise level (bigger numbers result in bumpy terrain, smaller gives smoother results)
-- tbl = table of heights, if provided
-- img = image or string name of image (do not include library name)
-- sc = image scaling factor (see above) 0-1
-- hm = height map storing heights of each point in
-- ns = the fraction by which noise level increases for each tile as we go deeper into the terrain
-- eg a value of 0.3 means that the outside ring of tiles has nil noise (as always, so these tiles
-- exactly meet the surface around), the next ring of tiles has noise of 0.3 of the full level,
-- the third ring of tiles has noise of 0.6, and so on, up to a maximum of 1.0. The bigger ns, the
-- the more you will get cliffs at the edges of the terrain
function AddTerrain(myarg)
local x,y,z,w,d,p,f,h,r,n,tbl,--img,
sc,hm,ns=
Params({"x","y","z","width","depth","tileSize","minY","maxY","random","noiseLevel",
"heightTable",
-- "img",
"sc", "heightMaps","noiseStep"},myarg)
local nw,nd=math.floor(w/p),math.floor(d/p) --number of tiles in each direction
--if no table of heights provided, generate one with noise
if tbl==nil then
n=n or 0.2 --default noise level
tbl1,tbl2={},{}
--noise starts at 0 on the outside, increases by the step as we move inward, max of 1
--noise is nil at edge, increases by 30% per tile inwards, to 100%
local min,max=9999,0
--if user wants a random result each time. add a random number to the noise results
if r==true then rnd=math.random(1,10000) else rnd=0 end
for i=1,nw+1 do
tbl1[i],tbl2[i]={},{}
for j=1,nd+1 do
--noise fraction for this tile, based on how close to the edge it is
--this formula counts how many rows in from the edge this tile is
-- tbl1[i][j]=math.min(i-1,j-1,nw+1-i,nd+1-j)
-- tbl1[i][j]=10-math.min(i-1,j-1,nw+1-i,nd+1-j) --added by me
tbl1[i][j]=20-j*2 --added by me
if tbl1[i][j]<0 then tbl1[i][j]=0.0 end --added by me
tbl1[i][j]=tbl1[i][j]*ns
-- print(i,j, math.min(i-1,j-1,nw+1-i,nd+1-j) )
--the noise itself
tbl2[i][j]=noise(rnd+i*n, rnd+j*n)
--keep track of min and max values of noise
if tbl2[i][j]<min then min=tbl2[i][j] end
if tbl2[i][j]>max then max=tbl2[i][j] end
end
end
--now go back through the whole array and scale it
--we know the user wants values between f and h
--we know our noise varies between min and max
--so now we pro rate all our values so they vary between f and h, based on the noise values
for i=1,nw+1 do
for j=1,nd+1 do
local f1=y
--pro rate
local f2=f+(h-f)*(tbl2[i][j]-min)/(max-min)
--now apply noise fraction, to reduce noise around the edges
tbl2[i][j]=f1*(1-tbl1[i][j])+f2*tbl1[i][j]
end
end
end
--store details for later use in determining height
table.insert(hm,{x1=x,z1=z,x2=x+w,z2=z+d,p=p,t=tbl2})
--create the vectors and mesh
-- print("sc",sc)
-- local wx, wz=img.width*sc, img.height*sc --connecred with bug
local wx,wz= 400,400
v,t,c={},{},{}
-- col=color(208, 160, 42, 255)
for i=1,nw do
for j=1,nd do
local x1,x2=x+(i-1)*p,x+i*p local x3,x4=x2,x1
local y1,y2,y3,y4=tbl2[i][j],tbl2[i+1][j],tbl2[i+1][j+1],tbl2[i][j+1]
local z1,z3=z+(j-1)*p,z+j*p local z2,z4=z1,z3
v[#v+1]=vec3(x1,y1,-z1) t[#t+1]=vec2(x1/wx,z1/wz) c[#c+1]=col--bottom left
v[#v+1]=vec3(x2,y2,-z2) t[#t+1]=vec2(x2/wx,z2/wz) c[#c+1]=col--bottom right
v[#v+1]=vec3(x3,y3,-z3) t[#t+1]=vec2(x3/wx,z3/wz) c[#c+1]=col--top right
v[#v+1]=vec3(x3,y3,-z3) t[#t+1]=vec2(x3/wx,z3/wz) c[#c+1]=col--top right
v[#v+1]=vec3(x4,y4,-z4) t[#t+1]=vec2(x4/wx,z4/wz) c[#c+1]=col--top left
v[#v+1]=vec3(x1,y1,-z1) t[#t+1]=vec2(x1/wx,z1/wz) c[#c+1]=col--bottom left
end
end
local norms=CalculateNormals(v)
local m=PseudoMesh()
-- local m=mesh()
-- m.texture=img
m.vertices=v
m.normals=norms
-- m:setColors(color(255))
m.colors=c
m.texCoords=t
m.pos=vec3(x,y,z)
print("terrain x y z",x,y,z)
-- m.shader = shader(autoTilerShader.vertexShader, autoTilerShader.fragmentShader)
-- m.shader=mega()
table.insert(meshTable[1],m)
end
--calculates height at x,z
function HeightAtPos(x,z)
--identify the location and calculate height
local h=0 --set default as 0
local i,v
for i,v in pairs(HM) do --look in each terrain area
if x>=v.x1 and x<=v.x2 and z>=v.z1 and z<=v.z2 then --if in this area...
--calc which square we are in
local mx=1+math.floor((x-v.x1)/v.p)
local mz=1+math.floor((z-v.z1)/v.p)
--use bilinear interpolation (most common method) for interpolating 4 corner values
-- local px,pz=(x-v.x1)/v.p-mx,(z-v.z1)/v.p-mz
local px,pz=1+(x-v.x1)/v.p-mx,1+(z-v.z1)/v.p-mz
-- output.clear()
-- print(x,z)
-- print (mx+px,mz+pz)
h= v.t[mx][mz]* (1-px)*(1-pz)+
v.t[mx+1][mz]* px*(1-pz)+
v.t[mx][mz+1]* (1-px)*pz+
v.t[mx+1][mz+1]* px*pz
break
end
end
return h
end
```