Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Last active December 17, 2015 15:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dermotbalson/5632997 to your computer and use it in GitHub Desktop.
Save dermotbalson/5632997 to your computer and use it in GitHub Desktop.
terrain!
--# Main
function setup()
LoadImages()
end
function setup2()
parameter.integer("Viewpoint",-2000,2000,20)
speed,angle=0,0
ds,da=0,0
posX,posY,posZ=-20,0,10 --starting position
rad=math.pi/180
meshTable={}
meshTable[1],meshTable[2]={},{}
AddLevel(-500,0,0,1000,1000,gravel,.1)
HM={} --holds height maps, one for each terrain area we build -- NEW
--add a terrain area --NEW
AddTerrain({x=-400,y=0.5,z=100,width=800,depth=800,tileSize=50,minY=10,maxY=100,random=false,
noiseLevel=.2,noiseStep=.5,img=grass,scaleFactor=.1,heightMaps=HM})
--add 200 trees
for i=1,200 do
local x=math.random(-375,375)
local z=math.random(150,750)
local w=math.random(20,60)
AddItem(x,0,z,w,tree) --rotating to face us
end
count=0
FPS=60
end
function draw()
if imageStatus~="Ready" then return end
count=count+1
background(220)
perspective(45,WIDTH/HEIGHT)
if Viewpoint<10 then Viewpoint=10 end
angle=angle+da*DeltaTime
posX,posZ=posX+ds*DeltaTime*math.sin(angle*rad),posZ+ds*DeltaTime*math.cos(angle*rad)
local h0=posY --NEW
posY=math.max(Viewpoint,HeightAtPos(posX,posZ)+10) --NEW
local h1=posY --NEW
--try to look up if climbing or down if descending --NEW
if posY==Viewpoint then lookY=20 else lookY=(h1-h0)*100/ds+10 end
--look in the same direction as we are facing
lookX,lookY,lookZ=posX+1000*math.sin(angle*rad),20,posZ+1000*math.cos(angle*rad)
camera(posX,posY,-posZ,lookX,lookY,-lookZ, 0,1,0)
if count%20==1 then
table.sort(meshTable[2],
function(a,b) return
vec2(posX,-posZ):dist(vec2(a.pos.x,-a.pos.z))>vec2(posX,-posZ):dist(vec2(b.pos.x,-b.pos.z)) end)
end
for k=1,2 do
for i,m in pairs(meshTable[k]) do
--rotate free standing objects if required, so they face us
if m.rotate==true then
pushMatrix()
translate(m.pos.x,m.pos.y,-m.pos.z)
local dx,dz=m.pos.x-posX,-m.pos.z+posZ
local ang=math.atan(dx/-dz)/rad
rotate(-ang,0,1,0)
m:draw()
popMatrix()
elseif m.ang==nil then m:draw()
else
pushMatrix()
translate(m.pos.x,m.pos.y,-m.pos.z)
rotate(-m.ang,0,1,0)
m:draw()
popMatrix()
end
end
end
ortho() --this is needed to get text written on the screen
viewMatrix(matrix()) --and this
FPS=FPS*.9+.1/DeltaTime
pushStyle()
fill(0)
fontSize(18)
strokeWidth(3)
textMode(CORNER)
text("FPS: "..string.format("%d",FPS),50,HEIGHT-50)
text("x="..string.format("% d",posX),50,HEIGHT-70)
text("y="..string.format("% d",posY),50,HEIGHT-90)
text("z="..string.format("% d",posZ),50,HEIGHT-110)
popStyle()
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(...)
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","scaleFactor","heightMaps","noiseStep"},arg)
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(1,math.min(i-1,j-1,nw+1-i,nd+1-j)*ns)
--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
local wx,wz=img.width*sc,img.height*sc
v,t={},{}
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) --bottom left
v[#v+1]=vec3(x2,y2,-z2) t[#t+1]=vec2(x2/wx,z2/wz) --bottom right
v[#v+1]=vec3(x3,y3,-z3) t[#t+1]=vec2(x3/wx,z3/wz) --top right
v[#v+1]=vec3(x3,y3,-z3) t[#t+1]=vec2(x3/wx,z3/wz) --top right
v[#v+1]=vec3(x4,y4,-z4) t[#t+1]=vec2(x4/wx,z4/wz) --top left
v[#v+1]=vec3(x1,y1,-z1) t[#t+1]=vec2(x1/wx,z1/wz) --bottom left
end
end
local m=mesh()
m.texture=img
m.vertices=v
m:setColors(color(255))
m.texCoords=t
m.pos=vec3(x,y,z)
m.shader = shader(autoTilerShader.vertexShader, autoTilerShader.fragmentShader)
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
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,mz=1+math.floor((x-v.x1)/v.p),1+math.floor((z-v.z1)/v.p)
--use bilinear interpolation (most common method) for interpolating 4 corner values
local px,pz=1+(x-v.x1)/v.p-mx,1+(z-v.z1)/v.p-mz
h=v.t[mx][mz]*(1-px)*(1-pz)+
v.t[mx+1][mz]*px*(1-pz)+
v.t[mx+1][mz+1]*px*pz+
v.t[mx][mz+1]*(1-px)*pz
break
end
end
return h
end
function AddItem(x,y,z,w,i,r) --centred on x
y=HeightAtPos(x,z)
local h=i.height*w/i.width
local m=mesh()
m.texture=i
local v,t={},{}
v,t=AddImage(-w/2,0,0,w,h,0,v,t)
m.pos=vec3(x,y,z)
if r~=nil then m.ang=r else m.rotate=true end
m.vertices=v
m:setColors(color(255))
m.texCoords=t
table.insert(meshTable[2],m)
end
function AddImage(x,y,z,w,h,d,v,t)
v[#v+1]=vec3(x,y,z) t[#t+1]=vec2(0,0)
v[#v+1]=vec3(x+w,y,z-d) t[#t+1]=vec2(1,0)
v[#v+1]=vec3(x+w,y+h,z-d) t[#t+1]=vec2(1,1)
v[#v+1]=vec3(x+w,y+h,z-d) t[#t+1]=vec2(1,1)
v[#v+1]=vec3(x,y+h,z) t[#t+1]=vec2(0,1)
v[#v+1]=vec3(x,y,z) t[#t+1]=vec2(0,0)
return v,t
end
function AddLevel(x,y,z,w,d,i,s)
local m=mesh()
m.texture=i
local v,t={},{}
local nx,nz=w/i.width/s,d/i.height/s
v[#v+1]=vec3(x,y,-z) t[#t+1]=vec2(0,0)
v[#v+1]=vec3(x+w,y,-z) t[#t+1]=vec2(nx,0)
v[#v+1]=vec3(x+w,y,-z-d) t[#t+1]=vec2(nx,nz)
v[#v+1]=vec3(x+w,y,-z-d) t[#t+1]=vec2(nx,nz)
v[#v+1]=vec3(x,y,-z-d) t[#t+1]=vec2(0,nz)
v[#v+1]=vec3(x,y,-z) t[#t+1]=vec2(0,0)
m.vertices=v
m:setColors(color(255))
m.texCoords=t
m.pos=vec3(x,y,z)
m.shader = shader(autoTilerShader.vertexShader, autoTilerShader.fragmentShader)
table.insert(meshTable[1],m)
end
function touched(touch)
local center=true
if touch.x<WIDTH/4 then
da=da-2
center=false
elseif touch.x>WIDTH*3/4 then
da=da+2
center=false
end
if touch.y<HEIGHT/4 then
ds=ds-3
center=false
elseif touch.y>HEIGHT*3/4 then
ds=ds+3
center=false
end
if center then ds=0 da=0 end
end
function Params(list,tbl) --tbl is params
if type(tbl[1])=="table" then --was named table
local t={} --match the named params with the list provided
for n,v in pairs(tbl[1]) do
for i=1,#list do
if n==list[i] then t[i]=v break end
end
end
return unpack(t)
else --was an unnamed list, return it as is
return unpack(tbl)
end
end
function LoadImages()
imageStatus="Ready" --tells draw it's ok to draw the scene (will be turned off if we have to download images)
output.clear()
--pass through Codea name of image and internet url
--not in Codea, will be downloaded and saved
grass=LoadImage("Dropbox:3D-grass2",
"http://i1303.photobucket.com/albums/ag142/ignatz_mouse/map-grass10_zps7573c68c.png")
gravel=LoadImage("Dropbox:3D-gravel",
"http://i1303.photobucket.com/albums/ag142/ignatz_mouse/map-gravel11_zpsc3a6ac7d.png")
tree=LoadImage("Dropbox:3D-tree2",
"http://i1303.photobucket.com/albums/ag142/ignatz_mouse/map-tree21c_zps655e421c.png")
if imageStatus=="Ready" then setup2() end
end
--downloads images one by one
function LoadImage(fileName,url)
local i=readImage(fileName)
if i~=nil then return i end
--not found, we need to download, add to queue (ie table)
if imageTable==nil then imageTable={} end
imageTable[#imageTable+1]={name=fileName,url=url}
print('Queueing',fileName)
imageStatus='Loading'
--if the first one, go ahead and download
if #imageTable==1 then
http.request(imageTable[1].url,ImageDownloaded)
print('loading',imageTable[1].name)
end
end
--saves downloaded images
function ImageDownloaded(img)
print(imageTable[1].name,'loaded')
saveImage(imageTable[1].name,img) --save
table.remove(imageTable,1)
--load next one if we have any more to do
if #imageTable>0 then
http.request(imageTable[1].url,ImageDownloaded)
print('loading',imageTable[1].name)
else
LoadImages()
end
end
--# Tiler
autoTilerShader = {
vertexShader = [[
//
// A basic vertex shader
//
//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
//Pass the mesh color to the fragment shader
vColor = color;
vTexCoord = texCoord;
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * position;
}
]],
fragmentShader = [[
//
// A basic fragment shader
//
//Default precision qualifier
precision highp float;
//This represents the current texture on the mesh
uniform lowp sampler2D texture;
//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;
void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0))) * vColor;
//Set the output color to the texture color
gl_FragColor = col;
}
]]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment