Created
October 21, 2013 06:37
-
-
Save dermotbalson/7079524 to your computer and use it in GitHub Desktop.
Lunar landing 3a
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Main | |
--The name of the project must match your Codea project name if dependencies are used. | |
--Project: 3D Lunar Landing | |
--Version: Alpha 1.12 | |
--Comments: | |
--Main | |
states={Intro=0,Loading=1,Flying=2,Crashed=3,Landed=4,Walking=5} | |
--this loads the images before doing anything else | |
function setup() | |
displayMode(FULLSCREEN) | |
imageStatus="Ready" --tells draw it's ok to draw the scene (will be turned off if we have to download images) | |
--pass through Codea name of image and internet url | |
--if not in Codea, will be downloaded and saved | |
imgMoon=LoadImage("Dropbox:Moon", | |
"http://i1303.photobucket.com/albums/ag142/ignatz_mouse/Surface_zps8a08cf9b.png") | |
if imageStatus=="Ready" then setup2() end | |
end | |
function setup2() | |
saveProjectInfo("Description","3D Moon landing") | |
saveProjectInfo("Author","ignatz") | |
output.clear() | |
state=states.Loading | |
rad=math.pi/180 | |
d,da=0,0 --navigation | |
Map={} | |
HM={} --height maps | |
B={} --buildings table | |
p=Map4 | |
p:SetupMap() | |
lookY=15 | |
Sky=color(0) | |
FPS=60 | |
--parameter.integer("FPS",0,60,60) | |
textMode(CORNER) | |
textColour=color(156, 202, 150, 255) | |
fill(textColour) | |
font("Inconsolata") | |
fontSize(18) | |
CreateControlPanel() | |
spriteMode(CORNER) | |
state=states.Intro | |
end | |
function CreateControlPanel() | |
pushStyle() | |
powerBar={width=300,height=50,x=WIDTH-400,y=25} | |
leftThrust={width=75,height=75,x=powerBar.x-105,y=powerBar.y-25} | |
rightThrust={width=75,height=75,x=powerBar.x+powerBar.width+40,y=powerBar.y-25} | |
--draw power bar | |
local p=image(powerBar.width,powerBar.height) | |
setContext(p) | |
strokeWidth(50) | |
stroke(textColour) | |
line(-powerBar.width,-powerBar.height*1.5,2*powerBar.width,powerBar.height*1.5) | |
setContext() | |
--create side thruster images | |
lineCapMode(SQUARE) | |
--left | |
local pL=image(leftThrust.width,leftThrust.height) | |
setContext(pL) | |
strokeWidth(leftThrust.height) | |
line(2*leftThrust.width,0,leftThrust.width,leftThrust.height) | |
setContext() | |
--right | |
local pR=image(rightThrust.width,rightThrust.height) | |
setContext(pR) | |
strokeWidth(rightThrust.height) | |
line(-rightThrust.width,0,0,rightThrust.height) | |
setContext() | |
--draw main panel | |
imgPanel=image(WIDTH,100) | |
setContext(imgPanel) | |
background(44, 58, 46, 255) | |
strokeWidth(2) | |
stroke(122, 143, 122, 255) | |
line(0,imgPanel.height-1,imgPanel.width,imgPanel.height-1) | |
spriteMode(CORNER) | |
sprite(p,powerBar.x,powerBar.y) | |
sprite(pL,leftThrust.x,leftThrust.y) | |
sprite(pR,rightThrust.x,rightThrust.y) | |
setContext() | |
popStyle() | |
thrustPos=powerBar.x+thrustLevel*powerBar.width | |
end | |
function draw() | |
if imageStatus~="Ready" then return end | |
background(0) | |
if state==states.Intro then DrawIntro() | |
elseif state==states.Loading then DrawLoading() | |
elseif state==states.Flying then DrawFlying() | |
elseif state==states.Crashed then DrawCrash() | |
elseif state==states.Landed then DrawLanding() | |
elseif state==states.Walking then DrawWalking() | |
end | |
if LL.ResetAlerts then LL.ResetAlerts() end | |
end | |
function DrawIntro() | |
pushStyle() | |
fontSize(60) | |
fill(255,255,255,150) | |
textMode(CENTER) | |
local h,s=HEIGHT*2/3,25 | |
text("Lunar Landing 3D",WIDTH/2,h) | |
fontSize(18) | |
h=h-s*3 text("Your mission is off course and you must land soon",WIDTH/2,h) | |
h=h-s text("Yellow markers (if any - it's random) show where it's flat enough to land",WIDTH/2,h) | |
h=h-s*2 text("Use the main engine (slider bar below) and side thrusters to land",WIDTH/2,h) | |
h=h-s text("near a yellow marker, dropping at less than 50 pixels/sec",WIDTH/2,h) | |
h=h-s text("(and keep an eye on your fuel, too)",WIDTH/2,h) | |
fill(255,255,0,200) | |
h=h-s*1.5 text("Touch the screen to start",WIDTH/2,h) | |
popStyle() | |
DrawFlying() | |
end | |
function DrawFlying() | |
if state~=states.Intro then | |
fallSpeed=fallSpeed +(5-thrustLevel*maxThrustMain)*DeltaTime | |
Fuel=Fuel-thrustLevel*maxFuelUsed | |
if Fuel<0 then state=states.Crashed return end | |
Elevation=Elevation-fallSpeed*DeltaTime | |
else Elevation=1000 | |
end | |
posY=Elevation | |
local f=forwardSpeed*DeltaTime | |
local mx,mz=math.sin(angle),math.cos(angle) | |
posX,posZ=posX+f*mx,posZ+f*mz | |
if posZ>mapDepth then state=states.Crashed return end | |
clearance=Elevation-Scene:HeightAtPos(posX,posZ) | |
if clearance<landerSize/3 then | |
if fallSpeed>maxLandSpeed then state=states.Crashed return end | |
for i=1,#sites do | |
local x,y=sites[i].x,sites[i].y | |
if vec3(x,Scene:HeightAtPos(x,y),y):dist(vec3(posX,posY,posZ))<landerSize*2 then | |
state=states.Landed | |
break | |
end | |
end | |
if state==states.Flying then state=states.Crashed end | |
return | |
end | |
DrawControlPanel() | |
perspective(30, WIDTH/HEIGHT) | |
--look in the same direction as we are facing | |
lookX,lookY,lookZ=posX+1000*mx,200,-posZ-1000*mz | |
camera(posX,posY,-posZ,lookX,lookY,lookZ, 0,1,0) | |
fill(255) | |
for i,mmmm in pairs(Mesh) do | |
for j,mm in pairs(Mesh[i]) do | |
LL.setEyePosition(vec3(posX,posY,-posZ)) | |
LL.draw(mm.m) | |
end | |
end | |
fill(255,255,0,50) | |
for i=#sites,1,-1 do | |
local s=sites[i] | |
pushMatrix() | |
translate(s.x,Scene:HeightAtPos(s.x,s.y)+5,-s.y) | |
rect(0,0,5,5) | |
popMatrix() | |
end | |
FPS=FPS*0.9+.1/DeltaTime | |
end | |
function DrawWalking() | |
pushStyle() | |
fill(255) | |
fontSize(18) | |
text("Touch left/right/top/bottom of screen to turn/move, middle of screen to stop",75,HEIGHT-50) | |
popStyle() | |
perspective(60, WIDTH/HEIGHT) | |
--navigation | |
angle=angle+da*DeltaTime --navigation | |
local dd=d*DeltaTime --navigation | |
local h0=Scene:HeightAtPos(posX,posZ) | |
posX,posZ=posX+dd*math.sin(angle*rad),posZ+dd*math.cos(angle*rad) | |
local h1=Scene:HeightAtPos(posX,posZ) | |
posY=math.max(Elevation,h1+10) | |
if dd>0 then lookY=(h1-h0)*100/dd+150 end | |
--end of navigation | |
--look in the same direction as we are facing | |
lookX,lookZ=posX+1000*math.sin(angle*rad),-posZ-1000*math.cos(angle*rad) | |
camera(posX,posY,-posZ,lookX,lookY,lookZ, 0,1,0) | |
fill(255) | |
for i,mmmm in pairs(Mesh) do | |
for j,mm in pairs(Mesh[i]) do | |
LL.setEyePosition(vec3(posX,posY,-posZ)) | |
LL.draw(mm.m) | |
end | |
end | |
pushMatrix() | |
fill(255,255,0,100) | |
strokeWidth(0) | |
translate(Land.x,Land.y,-Land.z) | |
rect(0,0,5,15) | |
popMatrix() | |
popStyle() | |
FPS=FPS*.9+(1/DeltaTime)*.1 | |
if LL.ResetAlerts then LL.ResetAlerts() end | |
end | |
function DrawCrash() | |
pushStyle() | |
fontSize(60) | |
fill(255,0,0) | |
textMode(CENTER) | |
local h,s=HEIGHT*2/3,25 | |
text("CRASH !",WIDTH/2,h) | |
fontSize(18) | |
fill(255) | |
h=h-s*3 text("Watch the down speed (and time until landing), don't let it get extreme",WIDTH/2,h) | |
h=h-s*2 text("Keep your height 200-800, as you can easily fall out of the sky",WIDTH/2,h) | |
h=h-s text("Markers are further than they may appear",WIDTH/2,h) | |
fill(255,255,0,200) | |
h=h-s*2 text("Touch the screen to try again",WIDTH/2,h) | |
popStyle() | |
end | |
function DrawLanding() | |
pushStyle() | |
fontSize(60) | |
fill(0,100,255,255) | |
textMode(CENTER) | |
local h,s=HEIGHT*2/3,25 | |
text("LANDED !",WIDTH/2,h) | |
fontSize(18) | |
fill(255) | |
h=h-s*3 text("Now you can explore the moon on foot",WIDTH/2,h) | |
h=h-s text("Touch the left and right of the screen to turn",WIDTH/2,h) | |
h=h-s*2 text("Touch the top and bottom of the screen to go forward and back",WIDTH/2,h) | |
h=h-s text("Touch the middle of the screen to stop",WIDTH/2,h) | |
h=h-s*2 text("The yellow marker behind you is your spaceship",WIDTH/2,h) | |
h=h-s text("use your imagination, they're kinda hard to draw in 3D",WIDTH/2,h) | |
fill(255,255,0,200) | |
h=h-s*2 text("Touch the screen to start walking",WIDTH/2,h) | |
popStyle() | |
end | |
function DrawLoading() | |
pushStyle() | |
fontSize(60) | |
fill(255,255,0) | |
textMode(CENTER) | |
text("Loading...",WIDTH/2,HEIGHT/2) | |
popStyle() | |
end | |
function DrawControlPanel() | |
sprite(imgPanel,0,0) | |
stroke(255) | |
strokeWidth(1) | |
pushStyle() | |
strokeWidth(2) | |
stroke(255) | |
fill(255) | |
line(thrustPos,powerBar.y,thrustPos,powerBar.y+powerBar.height) | |
local x,y=WIDTH/2,HEIGHT/2+50 | |
line(x-20,y,x+20,y) line(x,y-20,x,y+20) | |
strokeWidth(0) | |
local x,y=WIDTH/2,HEIGHT/2+50 | |
line(x-20,y,x+20,y) line(x,y-20,x,y+20) | |
text("Height: "..math.floor(clearance or 0),330,75) | |
text("Down: "..math.floor(fallSpeed),330,50) | |
local s=math.min(1000,math.floor(clearance/math.max(fallSpeed,0.001))) --limit to 1000 | |
text("Touchdown in: "..s.. " secs",330,25) | |
text("Fuel: "..math.floor(Fuel),330,5) | |
text("FPS: "..math.floor(FPS+.5),50,75) | |
WarningLight(clearance,250,300,85) | |
WarningLight(s,25,300,35) | |
WarningLight(Fuel,400,300,15) | |
popStyle() | |
end | |
function WarningLight(state,max,x,y) | |
local s=state/max*255 | |
s=math.max(0,math.min(255,s)) | |
fill(255-s,s,0,150) | |
ellipse(x,y,20) | |
end | |
--basic navigation, see Notes tab | |
function touched(touch) | |
if state==states.Walking and touch.state~=ENDED then | |
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 | |
d=d-3 | |
da=0 | |
center=false | |
elseif touch.y>HEIGHT*3/4 then | |
d=d+3 | |
da=0 | |
center=false | |
end | |
if center then d=0 da=0 end | |
elseif touch.state~=BEGAN then | |
if state==states.Intro then | |
state=states.Flying | |
elseif state==states.Crashed then | |
setup2() | |
elseif state==states.Flying then | |
if DidTouchPanel(powerBar,touch) then | |
thrustLevel=(touch.x-powerBar.x)/powerBar.width | |
thrustPos=powerBar.x+thrustLevel*powerBar.width | |
return | |
end | |
if DidTouchPanel(leftThrust,touch) then | |
angle=angle-math.rad(sideThrusterEffect) | |
Fuel=Fuel-sideThrusterFuel | |
return | |
end | |
if DidTouchPanel(rightThrust,touch) then | |
angle=angle+math.rad(sideThrusterEffect) | |
Fuel=Fuel-sideThrusterFuel | |
return | |
end | |
elseif state==states.Landed then | |
SetupWalking() | |
end | |
end | |
end | |
function SetupWalking() | |
state=states.Walking | |
angle,d,dd,da=0,0,0,0 | |
Land=vec3(posX,Scene:HeightAtPos(posX,posZ-5),posZ-5) | |
end | |
function DidTouchPanel(p,t) | |
if t.x>=p.x and t.x<=p.x+p.width and t.y>=p.y and t.y<=p.y+p.height then return true else return false 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('Downloading: ',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('Downloading',imageTable[1].name) | |
else | |
setup() | |
end | |
end | |
--# Map4 | |
--Map4 Everything on this tab is a user setting. Just follow the instructions | |
Map4={} | |
function Map4:SetupMap() | |
imageStatus="Ready" | |
--initialise scene, get back mesh | |
Mesh=Scene:init() | |
landerSize=50 | |
tolerance=10 | |
--initial settings | |
Elevation=1000 --pixels | |
forwardSpeed=50 --pixels/sec | |
fallSpeed=10 --pixels/sec | |
maxLandSpeed=50 | |
angle=0 | |
--engine settings | |
Fuel=10000 | |
maxThrustMain,maxFuelUsed=10,10 | |
thrustLevel=0.5 | |
sideThrusterEffect,sideThrusterFuel=1,3 | |
frontThrusterEffect,frontThrusterFuel=1,3 | |
imgMoon0=image(10,10) | |
setContext(imgMoon0) | |
background(200) | |
setContext() | |
--imgMoon=readImage("Dropbox:Surface") | |
mapWidth,mapDepth=3000,6000 --map width/height in pixels | |
--x is width, y is depth, z is vertical height | |
posX,posZ=mapWidth/2,0 --starting position | |
angle=0 --initial angle, 0 faces forward | |
local s=100 | |
--now the terrain | |
ht=hmap.create(mapWidth/s,mapDepth/s,0,500,.8) | |
Scene:AddTerrain({x=0,y=.4,z=0,width=mapWidth,depth=mapDepth,heightTable=ht, | |
tileSize=s,minY=.4,maxY=75,noiseLevel=.12,img=imgMoon,scaling=.15,heightMaps=HM,noiseStep=.2}) | |
for i,mmmm in pairs(Mesh) do | |
for j,mm in pairs(Mesh[i]) do | |
LL.addMesh({mesh=mm.m,reflect=1,shine=0,averageNormals=true}) | |
end | |
end | |
self:FindLandingSites(landerSize,tolerance) | |
LL.setAmbient({strength=0.15,color=color(150)}) | |
LL.setDirectional({strength=.6,direction=vec3(-500,200,100),color=color(220)}) | |
end | |
function Map4:FindLandingSites(s,tol) | |
sites={} | |
local w=s*2 | |
for i=s*3, mapWidth-s*3,10 do | |
for j=s*3, mapDepth-s*3, 10 do | |
local min,max=9999,-9999 | |
for k=-w,w,w do | |
for m=-w,w,w do | |
local h=Scene:HeightAtPos(i+k,j+m) | |
if h<min then min=h end | |
if h>max then max=h end | |
end | |
end | |
if max-min<=tol then | |
table.insert(sites,vec2(i,j)) | |
end | |
end | |
end | |
--print(#sites) | |
end | |
function Map4:CreateFlag() | |
local m=mesh() | |
--m:addRect | |
end | |
--# Scene | |
Scene = class() | |
function Scene:init() | |
Scene.MeshTable={} | |
Scene.MeshTable[1],Scene.MeshTable[2]={},{} | |
Scene.ObjectMaps={row={},col={}} | |
return Scene.MeshTable | |
end | |
--adds a wall/cuts a hole in a wall of our internal collision avoidance map | |
--walls must be parallel to x or z axis, ie either w or d must be nil | |
--x,z are start position of line, w,d are width/depth | |
--o is true if line cannot be crossed, otherwise false | |
function Scene:AddWallToMap(...) | |
local x,z,w,d,o=Scene:Params({"x","z","width","depth","obstacle"},arg) | |
if o==nil then return end | |
xx,zz,ww,dd=math.floor(x+.5),math.floor(z+.5),math.floor(w+.5),math.floor(d+.5) | |
local i1,i2=0,0 | |
if o==false then i1,i2=-1,1 end | |
if dd>0 then | |
for i=xx+i1,xx+i2 do | |
if Scene.ObjectMaps.col[i]==nil then Scene.ObjectMaps.col[i]={} end | |
table.insert(Scene.ObjectMaps.col[i],{blocked=o,z1=zz,z2=zz+dd}) | |
end | |
else | |
for i=zz+i1,zz+i2 do | |
if Scene.ObjectMaps.row[i]==nil then Scene.ObjectMaps.row[i]={} end | |
table.insert(Scene.ObjectMaps.row[i],{blocked=o,x1=xx,x2=xx+ww}) | |
end | |
end | |
end | |
--adds an image to our internal collision avoidance map | |
--images must be parallel to x axis, x is assumed to be centre of image | |
function Scene:AddImageToMap(...) | |
local x,z,w,r=Scene:Params({"x","z","width","rotate"},arg) | |
for ww=-w/2,w/2,w do | |
local zz=math.floor(z+ww) | |
if Scene.ObjectMaps.row[zz]==nil then Scene.ObjectMaps.row[zz]={} end | |
table.insert(Scene.ObjectMaps.row[zz],{blocked=true,x1=x-w/2,x2=x+w/2}) | |
local xx=math.floor(x+ww) | |
if Scene.ObjectMaps.col[xx]==nil then Scene.ObjectMaps.col[xx]={} end | |
table.insert(Scene.ObjectMaps.col[xx],{blocked=true,z1=z-w/2,z2=z+w/2}) | |
end | |
end | |
--test whether we can make the desired move without colliding with something | |
--information about map objects is stored in ObjectMaps | |
--all objects are parallel to x or z axis and stored in a row or col table accordingly | |
--so | |
--(a) if we aren't moving, there is nothing to do, return | |
--(b) calculate the target x,z square = xx,zz, allowing for the move | |
--(this will never be more than 1 pixel away) | |
--(c) if there is data in the xx ObjectMaps column, check if our zz value is between the ends of any | |
-- objects stored in there | |
--(d) if it is between the ends of an object, then if it is a gap, ie blocked=false, we don't need | |
-- to go any further, and can return the desired move | |
--(e) otherwise we keep looking, and set the blocked marker if we find a blocking object | |
--(f) at the end, if we have been blocked, we return the original position and nil speed | |
--(the reason we keep looking after being blocked is that we might find a gap later) | |
-- x,z = current position | |
-- dx,dz = movement in x and y axes | |
-- current speed (will be left as is or made 0) | |
function Scene:Move(...) | |
local x,z,dx,dz,speed=Scene:Params({"x","z","deltaX","deltaZ","speed"},arg) | |
local backpedal=2*60 --will bounce back by this many pixels, | |
--(prevents being right up against obstacle and seeing nothing) | |
if speed==0 then return x+dx,z+dz,speed end | |
local x0,z0=math.floor(x+.5),math.floor(z+.5) | |
local xx,zz=math.floor(x+dx+.5),math.floor(z+dz+.5) | |
if xx==x0 and zz==z0 then return x+dx,z+dz,speed end | |
local blocked=false | |
if Scene.ObjectMaps.col[xx]~=nil then | |
for i,m in pairs(Scene.ObjectMaps.col[xx]) do | |
if zz>=m.z1 and zz<=m.z2 then | |
if m.blocked==true then blocked=true | |
else return x+dx,z+dz,speed end --gap | |
end | |
end | |
if blocked==true then return x-dx*backpedal/speed,z-dz*backpedal/speed,0 end | |
end | |
if Scene.ObjectMaps.row[zz]~=nil then | |
for i,m in pairs(Scene.ObjectMaps.row[zz]) do | |
if xx>=m.x1 and xx<=m.x2 then | |
if m.blocked==true then blocked=true | |
else return x+dx,z+dz,speed end --gap | |
end | |
end | |
if blocked==true then return x-dx*backpedal/speed,z-dz*backpedal/speed,0 end | |
end | |
return x+dx,z+dz,speed | |
end | |
function Scene:Level(...) | |
local x,y,z,w,d,img,sc=Scene:Params({"x","y","z","width","depth","img","scaling"},arg) | |
sc=sc or 1 | |
local x1,y1,z1,x2,y2,z2=x,y,z,x+w,y,z+d | |
local res1,res2=(x2-x1)/img.width/sc,(z2-z1)/img.height/sc | |
v,t={},{} | |
v = {vec3(x1,y1,-z1), vec3(x2,y1,-z1), vec3(x1,y1,-z2), | |
vec3(x1,y1,-z2), vec3(x2,y1,-z1), vec3(x2,y1,-z2)} | |
t = {vec2(0,0), vec2(res1,0), vec2(0,res2), | |
vec2(0,res2), vec2(res1,0), vec2(res1,res2)} | |
Scene:AddToMeshShader({tableIndex=1,Vertices=v,Texture=t,Image=img,x=x,z=z,w=w}) | |
end | |
-- x,z = bottom left corner | |
-- y = height around the edges | |
-- w,d = width and depth of area to be terrained (pixels) - MUST BE MULTIPLE OF p | |
-- 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 | |
-- 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 Scene:AddTerrain(...) | |
local x,y,z,w,d,p,f,h,r,n,tbl2,img,sc,hm,ns=Scene:Params( | |
{"x","y","z","width","depth","tileSize","minY","maxY","random","noiseLevel", | |
"heightTable","img","scaling","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 tbl2==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 | |
Scene:AddToMeshShader({tableIndex=1,Vertices=v,Texture=t,Image=img}) | |
end | |
--calculates height at x,z | |
function Scene: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 | |
--formula taken from: http://en.wikipedia.org/wiki/Bilinear_interpolation | |
local Q11,Q21,Q12,Q22=v.t[mx][mz],v.t[mx+1][mz],v.t[mx][mz+1],v.t[mx+1][mz+1] | |
local x1,x2,y1,y2,x,y=mx,mx+1,mz,mz+1,1+(x-v.x1)/v.p,1+(z-v.z1)/v.p | |
h=(Q11*(x2-x)*(y2-y)+Q21*(x-x1)*(y2-y)+Q12*(x2-x)*(y-y1)+Q22*(x-x1)*(y-y1))/(x2-x1)/(y2-y1) | |
break | |
end | |
end | |
return h | |
end | |
--draw a basic box with a given texture | |
--a box consists of 24 triangles, made up of the 8 corner vertices | |
--x,z is bottom left corner (looking at map from starting position, and 0,0 is bottom left of map) | |
--y is height above ground | |
--w = width along x axis (pixels) | |
--d = depth along z axis (pixels) | |
--h = height of wall along y axis (pixels) | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
--r = the roof pitch in degrees, defaults to 15 (normal house is 10-15) | |
--b = ID of existing building, creates new one if none supplied | |
function Scene:BuildingWalls(...) | |
local x,y,z,w,d,h,img,sc,r,b=Scene:Params({"x","y","z","width","depth","height", | |
"img","scaling","pitch","ID"},arg) | |
local o | |
if b==nil then | |
table.insert(B,{x=(x+w)/2,z=(z+d)/2,x1=x,y1=y,z1=z,x2=x+w,y2=y+h,z2=z+d,w=w,d=d,h=h,pitch=r,img=img,sc=sc}) | |
b=#B | |
o=true --walls are only an obstacle if they they are the initial ones | |
end | |
local colr | |
--now create rectangular box | |
local wm={} | |
--front wall | |
wm[1]=Scene:AddWall({x1=x,y1=y,z1=z,x2=x+w,y2=y+h,z2=z,img=img,scaling=sc,obstacle=o}) | |
--left side | |
wm[2]=Scene:AddWall({x1=x,y1=y,z1=z,x2=x,y2=y+h,z2=z+d,img=img,scaling=sc,obstacle=o}) | |
--right side | |
wm[3]=Scene:AddWall({x1=x+w,y1=y,z1=z,x2=x+w,y2=y+h,z2=z+d,img=img,scaling=sc,obstacle=o}) | |
--back side | |
wm[4]=Scene:AddWall({x1=x,y1=y,z1=z+d,x2=x+w,y2=y+h,z2=z+d,img=img,scaling=sc,obstacle=o}) | |
--add ends | |
if r then Scene:AddRoofEnds({ID=b,img=img,scaling=sc}) end | |
B[b].wm=wm | |
--add to mesh | |
--Scene:AddToMeshShader(1,v,t,img,colr) | |
--store data unless s set | |
return b | |
end | |
--Draw interior walls and ceiling | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
--c = whether to draw a ceiling as well (omit if not required, else true) | |
function Scene:BuildingInteriorWalls(...) | |
local b,img,sc,c=Scene:Params({"ID","img","scaling","ceiling"},arg) | |
local margin=0.25 | |
local x,y,z=B[b].x1+margin,B[b].y1,B[b].z1+margin | |
local w,d,h=B[b].w-margin*2,B[b].d-margin*2,B[b].h | |
Scene:BuildingWalls({x=x,y=y,z=z,width=w,depth=d,height=h,img=img,scaling=sc,ID=b}) | |
--last param is true so we don't store bldg coords | |
if c then Scene:Level({x=x,y=y+h,z=z,width=w,depth=d,img=img,scaling=sc}) end --ceiling if requested | |
end | |
function Scene:AddWall(...) | |
local x1,y1,z1,x2,y2,z2,img,sc,o=Scene:Params({"x1","y1","z1","x2","y2","z2", | |
"img","scaling","obstacle","hole"},arg) | |
sc=sc or 1 | |
print(x1,y1,z1,x2,y2,z2) | |
local imgW,imgH=img.width*sc,img.height*sc | |
local nX,nY,nZ=math.abs(x2-x1)/imgW,math.abs(y2-y1)/imgH, | |
math.abs(z2-z1)/imgW --number of tiles required in x and y axes | |
v,t={},{} | |
v[#v+1]=vec3(x1,y1,-z1) t[#t+1]=vec2(0,0) | |
v[#v+1]=vec3(x2,y1,-z2) t[#t+1]=vec2(nX+nZ,0) --nX and nZ added because only one can be nonzero | |
v[#v+1]=vec3(x1,y2,-z1) t[#t+1]=vec2(0,nY) | |
v[#v+1]=vec3(x1,y2,-z1) t[#t+1]=vec2(0,nY) | |
v[#v+1]=vec3(x2,y2,-z2) t[#t+1]=vec2(nX+nZ,nY) | |
v[#v+1]=vec3(x2,y1,-z2) t[#t+1]=vec2(nX+nZ,0) | |
Scene:AddWallToMap({x=x1,z=z1,width=x2-x1,depth=z2-z1,obstacle=o}) | |
local m= Scene:AddToMeshShader({tableIndex=2,Vertices=v,Texture=t,Image=img, | |
x=(x1+x2)/2,z=(z1+z2)/2}) | |
m.nx,m.ny,m.nz=nX,nY,nZ | |
m.w,m.h=x2-x1+z2-z1,y2-y1 | |
return m | |
end | |
--adds tint to outside of building | |
function Scene:AddTint(...) | |
local b,c=Scene:Params({"ID","colour"},arg) | |
local e=image(10,10) | |
local margin=0.2 | |
pushStyle() | |
setContext(e) | |
strokeWidth(0) | |
fill(c) | |
rect(-1,-1,e.width+2,e.height+2) | |
setContext() | |
popStyle() | |
Scene:BuildingWalls({x=B[b].x1-margin,y=B[b].y1,z=B[b].z1-margin,width=B[b].w+margin*2, | |
depth=B[b].d+margin*2,height=B[b].h,img=e,scaling=1,pitch=15,ID=b}) | |
end | |
--draws an interior floor surface | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
function Scene:Floor(...) | |
local b,img,sc=Scene:Params({"ID","img","scaling"},arg) | |
local margin=0.05 | |
local x,y,z,w,d=B[b].x1+margin,B[b].y1+margin,B[b].z1+margin,B[b].w-margin*2,B[b].d-margin*2 | |
Scene:Level(x,y,z,w,d,img,sc) | |
end | |
--adds roof | |
--o = overhang, number of pixels by which roof is wider than walls | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
function Scene:Roof(...) | |
local b,o,img,s=Scene:Params({"ID","overhang","img","scaling"},arg) | |
local p=B[b].pitch*math.pi/180 --15 degree roof pitch | |
o=o or 3 | |
s=s or 1 | |
local iw,id,ih | |
local iw=math.max(B[b].w,B[b].d)+o*2 --greater of width and depth of building | |
local id=(iw/2)/math.cos(p) --depth of pitched roof (at an angle, so > d) | |
local ih=(iw/2)*math.tan(p) --height of roof at middle of house | |
local nw,nd=iw/img.width/s,id/img.height/s | |
local sw,sd,x1,x2,z1,z2 | |
local y1,y2=B[b].h,B[b].h+ih | |
local v,t={},{} | |
if B[b].w>B[b].d then | |
x1,x2=B[b].x1-o,B[b].x2+o | |
z1,z2=-(B[b].z1-o),-(B[b].z1+B[b].d/2) | |
v[1]=vec3(x1,y1,z1) t[1]=vec2(0,0) | |
v[2]=vec3(x1,y2,z2) t[2]=vec2(0,nd) | |
v[3]=vec3(x2,y1,z1) t[3]=vec2(nw,0) | |
v[4]=vec3(x2,y1,z1) t[4]=vec2(nw,0) | |
v[5]=vec3(x2,y2,z2) t[5]=vec2(nw,nd) | |
v[6]=vec3(x1,y2,z2) t[6]=vec2(0,nd) | |
z1,z2=-(B[b].z2+o),-(B[b].z2-B[b].d/2) | |
v[7]= vec3(x1,y1,z1) t[7]=vec2(0,0) | |
v[8]= vec3(x1,y2,z2) t[8]=vec2(0,nd) | |
v[9]= vec3(x2,y1,z1) t[9]=vec2(nw,0) | |
v[10]=vec3(x2,y1,z1) t[10]=vec2(nw,0) | |
v[11]=vec3(x2,y2,z2) t[11]=vec2(nw,nd) | |
v[12]=vec3(x1,y2,z2) t[12]=vec2(0,nd) | |
else | |
x1,x2=B[b].x1-o,B[b].x1+B[b].w/2 | |
z1,z2=-(B[b].z1-o),-(B[b].z2+o) | |
v[1]=vec3(x1,y1,z1) t[1]=vec2(0,0) | |
v[2]=vec3(x2,y2,z1) t[2]=vec2(0,nd) | |
v[3]=vec3(x1,y1,z2) t[3]=vec2(nw,0) | |
v[4]=vec3(x1,y1,z2) t[4]=vec2(nw,0) | |
v[5]=vec3(x2,y2,z2) t[5]=vec2(nw,nd) | |
v[6]=vec3(x2,y2,z1) t[6]=vec2(0,nd) | |
x1,x2=B[b].x2+o,B[b].x2-B[b].w/2 | |
z1,z2=-(B[b].z1-o),-(B[b].z2+o) | |
v[7]=vec3(x1,y1,z1) t[7]=vec2(0,0) | |
v[8]=vec3(x2,y2,z1) t[8]=vec2(0,nd) | |
v[9]=vec3(x1,y1,z2) t[9]=vec2(nw,0) | |
v[10]=vec3(x1,y1,z2) t[10]=vec2(nw,0) | |
v[11]=vec3(x2,y2,z2) t[11]=vec2(nw,nd) | |
v[12]=vec3(x2,y2,z1) t[12]=vec2(0,nd) | |
end | |
--add to mes | |
Scene:AddToMeshShader({tableIndex=2,Vertices=v,Texture=t,Image=img, | |
x=(x1+x2)/2,z=(z1+z2)/2}) | |
end | |
function Scene:AddRoofEnds(...) | |
local b,img,sc=Scene:Params({"ID","img","scaling"},arg) | |
local v,t={},{} --vertices and texture mappings | |
local p=B[b].pitch*math.pi/180 --15 degree roof pitch | |
local imax,imin,iw,ih | |
imax=math.max(B[b].w,B[b].d) | |
imin=math.min(B[b].w,B[b].d) | |
ih=(imax/2)*math.tan(p) | |
local nw,nh=imin/sc/img.width,ih/sc/img.height | |
if B[b].w>B[b].d then | |
v[1]=vec3(B[b].x1,B[b].h,-B[b].z1) t[1]=vec2(0,0) | |
v[2]=vec3(B[b].x1,B[b].h+ih,-(B[b].z1+B[b].d/2)) t[2]=vec2(nw*.5,nh) | |
v[3]=vec3(B[b].x1,B[b].h,-B[b].z2) t[3]=vec2(nw,0) | |
v[4]=vec3(B[b].x2,B[b].h,-B[b].z1) t[4]=vec2(0,0) | |
v[5]=vec3(B[b].x2,B[b].h+ih,-(B[b].z1+B[b].d/2)) t[5]=vec2(nw*.5,nh) | |
v[6]=vec3(B[b].x2,B[b].h,-B[b].z2) t[6]=vec2(nw,0) | |
else | |
v[1]=vec3(B[b].x1,B[b].h,-B[b].z1) t[1]=vec2(0,0) | |
v[2]=vec3(B[b].x1+B[b].w/2,B[b].h+ih,-(B[b].z1)) t[2]=vec2(nw*.5,nh) | |
v[3]=vec3(B[b].x2,B[b].h,-B[b].z1) t[3]=vec2(nw,0) | |
v[4]=vec3(B[b].x1,B[b].h,-B[b].z2) t[4]=vec2(0,0) | |
v[5]=vec3(B[b].x1+B[b].w/2,B[b].h+ih,-(B[b].z2)) t[5]=vec2(nw*.5,nh) | |
v[6]=vec3(B[b].x2,B[b].h,-B[b].z2) t[6]=vec2(nw,0) | |
end | |
--add to mesh | |
Scene:AddToMeshShader({tableIndex=2,Vertices=v,Texture=t,Image=img, | |
x=B[b].x,z=B[b].z}) | |
end | |
--ID is ID of building | |
--wallID =1 front wall, 2=left, 3=right, 4=back, viewed from front perspective | |
--x,y = position of bottom left corner of feature on the wall, as seen looking at it from the outside | |
-- (so if you want a window 20 pixels from the left hand edge, 4 up from the ground, then x=20, y=4 | |
--w = width | |
--img is image | |
--d is whether image shows on the inside as well-if true means image is mirrored on the inside | |
--o is false if this creates a gap you can walk through, like a door | |
--l is lighting factor | |
--t is whether to make a hole (transparent) | |
function Scene:AddFeature(...) | |
local b,n,x,y,w,img,d,o,l,hole=Scene:Params({"ID","wallID","x","y","w", | |
"img","inside","obstacle","lighting","hole"},arg) | |
sc=w/img.width | |
h=sc*img.height | |
local v,t={},{} | |
local margin | |
if hole then margin=0.1 else margin=0.8 end | |
local mm | |
if n==1 then --front | |
local zz=B[b].z1-margin | |
mm=Scene:AddWall({x1=B[b].x1+x,y1=y,z1=zz,x2=B[b].x1+x+w,y2=y+h,z2=zz,img=img,scaling=sc,obstacle=o}) | |
if d then | |
Scene:AddWall({x1=B[b].x1+x,y1=y,z1=B[b].z1+margin,x2=B[b].x1+x+w,y2=y+h,z2=B[b].z1+margin, | |
img=img,scaling=sc}) | |
end | |
elseif n==2 then --left | |
local xx=B[b].x1-margin | |
mm=Scene:AddWall({x1=xx,y1=y,z1=B[b].z2-x,x2=xx,y2=y+h,z2=B[b].z2-x-w,img=img,scaling=sc,obstacle=o}) | |
if d then | |
Scene:AddWall({x1=B[b].x1+margin,y1=y,z1=B[b].z2-x-w,x2=B[b].x1+margin,y2=y+h,z2=B[b].z2-x, | |
img=img,scaling=sc}) | |
end | |
elseif n==3 then --right | |
local xx=B[b].x2+margin | |
mm=Scene:AddWall({x1=xx,y1=y,z1=B[b].z1+x,x2=xx,y2=y+h,z2=B[b].z1+x+w,img=img,scaling=sc,obstacle=o}) | |
if d then | |
Scene:AddWall({x1=B[b].x2-margin,y1=y,z1=B[b].z1+x,x2=B[b].x2-margin,y2=y+h,z2=B[b].z1+x+w, | |
img=img,scaling=sc}) | |
end | |
elseif n==4 then --back | |
local zz=B[b].z2+margin | |
mm=Scene:AddWall({x1=B[b].x2-x,y1=y,z1=zz,x2=B[b].x2-x-w,y2=y+h,z2=zz,img=img,scaling=sc,obstacle=o}) | |
if d then | |
Scene:AddWall({x1=B[b].x2-x,y1=y,z1=B[b].z2-margin,x2=B[b].x2-x-w,y2=y+h,z2=B[b].z2-margin, | |
img=img,scaling=sc}) | |
end | |
end | |
--add to mesh | |
mm.light=l | |
if hole then Scene:MakeHole({ID=b,wallNo=n,x=x,y=y,width=w,height=h}) end | |
end | |
function Scene:MakeHole(...) | |
local b,n,x,y,w,h=Scene:Params({"ID","wallNo","x","y","width","height"},arg) | |
local mm=B[b].wm[n] | |
mm.m.shader = shader(autoTilerHoleShader.vertexShader, autoTilerHoleShader.fragmentShader) | |
if B[b].nHoles==nil then | |
B[b].nHoles=0 | |
mm.m.shader.hole1=vec4(0,0,0,0) | |
mm.m.shader.hole2=vec4(0,0,0,0) | |
mm.m.shader.hole3=vec4(0,0,0,0) | |
end | |
local h,shole | |
if B[b].nHoles==3 then return else | |
B[b].nHoles=B[b].nHoles+1 h=B[b].nHoles | |
end | |
local v | |
local fx,fy,fz=mm.nx/mm.w,mm.ny/mm.h,mm.nz/mm.w | |
if n==1 then | |
v=vec4(x*fx,y*fy,(x+w)*fx,(y+h)*fy) | |
elseif n==2 then | |
v=1 | |
elseif n==3 then | |
v=1 | |
elseif n==4 then | |
v=vec4((B[b].w-x-w)*fx,y*fy,(B[b].w-x)*fx,(y+h)*fy) | |
end | |
if h==1 then mm.m.shader.hole1=v elseif h==2 then mm.m.shader.hole2=v else mm.m.shader.hole3=v end | |
end | |
--adds stand alone image | |
--x,z is bottom left corner (looking at map from starting position, and 0,0 is bottom left of map) | |
--w = width of object (pixels) | |
--i = image or string name of image (do not include library name) | |
--r = true if image should always rotate to face the user | |
--ow = area you cannot walk through, in pixels, centred on xx | |
--l = lighting | |
function Scene:AddImage(...) | |
local xx,zz,w,img,r,ow,l=Scene:Params({"x","z","w","img","rotate","obstacleWidth","lighting"},arg) | |
local h=w*img.height/img.width | |
local v,t={},{} | |
local x,z | |
if r then x,z=-w/2,0 else x,z=xx-w/2,zz end | |
--identify the location and calculate height | |
local y=Scene:HeightAtPos(xx,zz) | |
if y>0 then y=y-1 end | |
v[1]=vec3(x,y,-z) t[1]=vec2(0,0) | |
v[2]=vec3(x,y+h,-z) t[2]=vec2(0,1) | |
v[3]=vec3(x+w,y,-z) t[3]=vec2(1,0) | |
v[4]=vec3(x+w,y,-z) t[4]=vec2(1,0) | |
v[5]=vec3(x+w,y+h,-z) t[5]=vec2(1,1) | |
v[6]=vec3(x,y+h,-z) t[6]=vec2(0,1) | |
--prevent user walking through | |
if ow~=nil then | |
Scene:AddImageToMap({x=xx,z=zz,width=w,rotate=r}) | |
end | |
if r then | |
Scene:AddToMeshShader({tableIndex=2,Vertices=v,Texture=t,Image=img, | |
x=xx,z=zz,w=w,rotate=true,lighting=l}) | |
else | |
Scene:AddToMeshShader({tableIndex=2,Vertices=v,Texture=t,Image=img, | |
w=w,lighting=l}) | |
end | |
collectgarbage() | |
end | |
function Scene:AddToMeshShader(...) | |
local n,v,t,img,c,x,y,z,w,l,sh,r=Scene:Params({"tableIndex","Vertices","Texture","Image","Colour", | |
"x","y","z","w","lighting","holeShader","rotate"},arg) | |
local mm={} | |
mm.m=mesh() | |
local colr=c or color(255) | |
mm.m.vertices=v | |
mm.m.texCoords=t | |
mm.m:setColors(colr) | |
mm.m.texture=img | |
--mm.x,mm.y,mm.z,mm.w,mm.r=x,y,z,w,r | |
--mm.light=l | |
--if sh ==nil then sh=autoTilerShader end | |
--if Shadow then | |
-- mm.m.shader=shader(ADSLighting.vertexShader, ADSLighting.fragmentShader) | |
-- local normals={} | |
-- for i=1,#v,3 do --determine the surfacenormal for the triangle | |
-- local n = (v[i+1] - v[i]):cross(v[i+2] - v[i]) | |
-- normals[i] = n | |
-- normals[i+1] = n | |
-- normals[i+2] = n | |
-- end | |
-- mm.m.normals=normals | |
-- mm.m.shader.vLightPosition = vec4(0,400,200,1) | |
-- mm.m.shader.useTexture = true | |
-- mm.m.shader.useBumpMap = false | |
-- --else | |
-- --mm.m.shader = shader(sh.vertexShader, sh.fragmentShader) | |
-- --end | |
table.insert(Scene.MeshTable[n],mm) | |
collectgarbage() | |
--return mm | |
end | |
-- | |
function Scene: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 | |
local www | |
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,1,table.maxn(t)) | |
else --was an unnamed list, return it as is | |
return unpack(tbl) | |
end | |
end | |
--# HeightMap | |
-- Heightmap module | |
-- Copyright (C) 2011 Marc Lepage | |
hmap={} | |
-- Find power of two sufficient for size | |
function hmap.pot(size) | |
local pot = 2 | |
while true do | |
if size <= pot then return pot end | |
pot = 2*pot | |
end | |
end | |
-- Create a table with 0 to n zero values | |
function hmap.tcreate(n) | |
local t = {} | |
for i = 0, n do t[i] = 0 end | |
return t | |
end | |
-- Square step | |
-- Sets map[x][y] from square of radius d using height function f | |
function hmap.square(map, x, y, d, f) | |
local sum, num = 0, 0 | |
if 0 <= x-d then | |
if 0 <= y-d then sum, num = sum + map[x-d][y-d], num + 1 end | |
if y+d <= map.h then sum, num = sum + map[x-d][y+d], num + 1 end | |
end | |
if x+d <= map.w then | |
if 0 <= y-d then sum, num = sum + map[x+d][y-d], num + 1 end | |
if y+d <= map.h then sum, num = sum + map[x+d][y+d], num + 1 end | |
end | |
map[x][y] = f(map, x, y, d, sum/num) | |
end | |
-- Diamond step | |
-- Sets map[x][y] from diamond of radius d using height function f | |
function hmap.diamond(map, x, y, d, f) | |
local sum, num = 0, 0 | |
if 0 <= x-d then sum, num = sum + map[x-d][y], num + 1 end | |
if x+d <= map.w then sum, num = sum + map[x+d][y], num + 1 end | |
if 0 <= y-d then sum, num = sum + map[x][y-d], num + 1 end | |
if y+d <= map.h then sum, num = sum + map[x][y+d], num + 1 end | |
map[x][y] = f(map, x, y, d, sum/num) | |
end | |
-- Diamond square algorithm generates cloud/plasma fractal heightmap | |
-- http://en.wikipedia.org/wiki/Diamond-square_algorithm | |
-- Size must be power of two | |
-- Height function f must look like f(map, x, y, d, h) and return h' | |
function hmap.diamondsquare(size, f) | |
-- create map | |
local map = { w = size, h = size } | |
for c = 0, size do map[c] = hmap.tcreate(size) end | |
-- seed four corners | |
local d = size | |
dd=2^-hmap.H | |
D=dd | |
local dx=(math.random()-0.5)*2 | |
map[0][0] = dx --f(map, 0, 0, d, 0) | |
map[0][d] = dx --f(map, 0, d, d, 0) | |
map[d][0] = dx --f(map, d, 0, d, 0) | |
map[d][d] = dx --f(map, d, d, d, 0) | |
d = d/2 D=D*dd | |
-- perform square and diamond steps | |
while 1 <= d do | |
for x = d, map.w-1, 2*d do | |
for y = d, map.h-1, 2*d do | |
hmap.square(map, x, y, d, f) | |
end | |
end | |
for x = d, map.w-1, 2*d do | |
for y = 0, map.h, 2*d do | |
hmap.diamond(map, x, y, d, f) | |
end | |
end | |
for x = 0, map.w, 2*d do | |
for y = d, map.h-1, 2*d do | |
hmap.diamond(map, x, y, d, f) | |
end | |
end | |
d = d/2 D=D*dd | |
end | |
return map | |
end | |
-- Default height function | |
-- d is depth (from size to 1 by powers of two) | |
-- h is mean height at map[x][y] (from square/diamond of radius d) | |
-- returns h' which is used to set map[x][y] | |
function hmap.defaultf(map, x, y, d, h) | |
return h + (math.random()-0.5)*D | |
end | |
-- Create a heightmap using the specified height function (or default) | |
-- map[x][y] where x from 0 to map.w and y from 0 to map.h | |
function hmap.create(width, height, s0,s1,h) | |
f = hmap.defaultf | |
hmap.H=h | |
-- make heightmap | |
local map = hmap.diamondsquare(hmap.pot(math.max(width, height)), f) | |
-- clip heightmap to desired size | |
for x = 0, map.w do for y = height+1, map.h do map[x][y] = nil end end | |
for x = width+1, map.w do map[x] = nil end | |
smap=hmap.rescale(map,s0,s1) | |
smap.w, smap.h = width, height | |
return smap | |
end | |
function hmap.rescale(m,s0,s1) | |
local min,max=9999,-9999 | |
for i=0,#m do | |
for j=0,#m[1] do | |
if m[i][j]<min then min=m[i][j] end | |
if m[i][j]>max then max=m[i][j] end | |
end | |
end | |
mm={} | |
for i=0,#m do | |
mm[i+1]={} | |
for j=0,#m[1] do | |
mm[i+1][j+1]=s0 + s1*(m[i][j]-min)/(max-min) | |
end | |
end | |
return mm | |
end | |
--# Lighting | |
--Ambient/diffuse/Specular lighting library | |
--ver 1.04 point lights adjustable | |
LL={} | |
-- === LIGHT SETTINGS ==== | |
--setAmbient, setDirectional and setPoint1 all accept a table of named parameters | |
--this means you need only set the ones you want, and can change any of them easily at any time | |
function LL.ClearLights() | |
LL.ambient=nil | |
LL.directional=nil | |
LL.point1=nil | |
LL.point2=nil | |
end | |
--general background light | |
--parameters = strength (0-1), color | |
function LL.setAmbient(p) | |
if LL.ambient==nil then LL.ambient={} end | |
LL.fill(LL.ambient,p) | |
LL.changeAmbient=true | |
end | |
--directional light | |
--parameters = strength (0-1), direction (vec3), color | |
function LL.setDirectional(p) | |
if LL.directional==nil then LL.directional={} end | |
LL.fill(LL.directional,p) | |
LL.changeDirectional=true | |
end | |
--point light | |
--parameters = strength (0-1), source (vec3), range (pixels), color | |
function LL.setPoint1(p) | |
if LL.point1==nil then LL.point1={} end | |
LL.fill(LL.point1,p) | |
LL.changePoint1=true | |
end | |
function LL.setPoint2(p) | |
if LL.point2==nil then LL.point2={} end | |
LL.fill(LL.point2,p) | |
LL.changePoint2=true | |
end | |
--specular reflection | |
function LL.setEyePosition(v) | |
LL.eyePosition=v | |
LL.changeEyePosition=true | |
end | |
--for each mesh | |
--parameters = mesh, reflect, shine, averageNormals | |
function LL.addMesh(...) | |
local m,reflect,shine,norm,ave=LL.Params({"mesh","reflect","shine","normals","averageNormals"},arg) | |
m.shader=shader(LL.Lighting.vertexShader,LL.Lighting.fragmentShader) | |
m.normals=norm or LL.CalculateNormals(m.vertices,ave) | |
m.shader.hasTexture=(m.texture~=nil) | |
m.shader.hasSpecular=(shine>0) | |
m.shader.reflectivity=reflect | |
m.shader.specularPower=32 --hard coded for now | |
m.shader.specularStrength=shine | |
m.shader.specularColor=color(255,255,255,255) | |
end | |
-- === RUNTIME FUNCTIONS ==== | |
function TransformLights(objectPos,lightPos,rot,light) | |
pushMatrix() | |
translate(objectPos.x,objectPos.y,objectPos.z) | |
rotate(rot.x,1,0,0) | |
rotate(rot.y,0,1,0) | |
rotate(rot.z,0,0,1) | |
if light==1 then | |
m.shader.point1Source=ObjectToWorld(lightPos) | |
if LL.point1["direction"] then | |
m.shader.point1Direction=(ObjectToWorld(LL.point1["direction"])-ObjectToWorld(vec3(0,0,0))):normalize() | |
end | |
else | |
m.shader.point2Source=ObjectToWorld(lightPos) | |
if LL.point2["direction"] then | |
m.shader.point2Direction=(ObjectToWorld(LL.point2["direction"])-ObjectToWorld(vec3(0,0,0))):normalize() | |
end | |
end | |
popMatrix() | |
end | |
--this runs at each draw and checks for any user changes or settings | |
function LL.UpdateLights(m) | |
--ambient | |
if LL.changeAmbient then | |
m.shader.ambientStrength=LL.ambient["strength"] or 0 | |
m.shader.ambientColor=LL.ambient["color"] or color(255) | |
end | |
--directional | |
if LL.changeDirectional then | |
m.shader.directionalStrength=LL.directional["strength"] or 0 | |
m.shader.directionalDirection=Vec3To4(LL.directional["direction"],0) | |
m.shader.directionalColor=LL.directional["color"] or color(255) | |
m.shader.hasDirectional=true | |
end | |
if m.shader.directionalStrength==nil then | |
m.shader.hasDirectional=false | |
end | |
--point light | |
if LL.changePoint1 then | |
m.shader.point1Strength=LL.point1["strength"] or 0 | |
m.shader.point1Source=Vec3To4(LL.point1["source"],1) | |
m.shader.point1Range= LL.point1["range"] or 0 | |
m.shader.point1Color=LL.point1["color"] or color(255) | |
m.shader.hasPoint1=(m.shader.point1Strength>0) | |
--spotlight has direction as well | |
if LL.point1["direction"] then | |
m.shader.point1IsSpot=true | |
m.shader.point1Direction=Vec3To4(LL.point1["direction"]:normalize(),0) | |
m.shader.point1Cos=math.cos(math.rad(LL.point1["radius"] or 15)) | |
else m.shader.point1IsSpot=false end | |
end | |
if LL.changePoint2 then | |
m.shader.point2Strength=LL.point2["strength"] or 0 | |
m.shader.point2Source=Vec3To4(LL.point2["source"],1) | |
m.shader.point2Range= LL.point2["range"] or 0 | |
m.shader.point2Color=LL.point2["color"] or color(255) | |
m.shader.hasPoint2=(m.shader.point2Strength>0) | |
--spotlight has direction as well | |
if LL.point2["direction"] then | |
m.shader.point2IsSpot=true | |
m.shader.point2Direction=Vec3To4(LL.point2["direction"]:normalize(),0) --XXXX | |
m.shader.point2Cos=math.cos(math.rad(LL.point2["radius"] or 15)) | |
else m.shader.point2IsSpot=false end | |
end | |
--eye position | |
if LL.changeEyePosition then m.shader.eyePosition=Vec3To4(LL.eyePosition,1) end | |
m.shader.hasEmissive=false | |
m.shader.hasTexture=(m.texture~=nil) --TEMPORARY, needed while user can change textures | |
end | |
function LL.draw(m,p) | |
LL.UpdateLights(m) | |
--next line is needed in shader to convert normals to world space | |
--m.shader.mInvModel = modelMatrix():inverse():transpose() | |
m.shader.mModel = modelMatrix() | |
m:draw() | |
end | |
function LL.ResetAlerts() --reset event notifications | |
LL.changeAmbient,LL.changeDirectional,LL.changePoint1,LL.changePoint2,LL.changeEyePosition= | |
false,false,false,false,false end | |
-- ==== UTILITY FUNCTIONS ==== | |
--reads parameters for colour settings into their tables | |
function LL.fill(t,p) | |
if t==nil then t={} end | |
for a,b in pairs(p) do | |
t[a]=b | |
end | |
end | |
function Vec3To4(v,n) | |
if n==1 then | |
return vec4(v.x,v.y,v.z,n) | |
else | |
local nv=v:normalize() | |
return vec4(nv.x,nv.y,nv.z,n) | |
end | |
end | |
function ObjectToWorld(v) --returns vec4 position | |
local mv=modelMatrix():translate(v.x,v.y,v.z) | |
return vec4(mv[13],mv[14],mv[15],1) | |
end | |
function VectorToEyeSpace(v,w) --returns vec4 direction | |
local mv=viewMatrix():inverse():translate(v.x,v.y,v.z) | |
return vec4(mv[13],mv[14],mv[15],w or 0) | |
end | |
function LL.CalculateNormals(vertices,average) | |
if average then | |
--average normals at each vertex | |
--first get a list of unique vertices, concatenate the x,y,z values as a key | |
local norm,unique= {},{} | |
for i=1, #vertices do | |
unique[vertices[i].x ..vertices[i].y..vertices[i].z]=vec3(0,0,0) | |
end | |
--calculate normals, add them up for each vertex and keep count | |
for i=1, #vertices,3 do --calculate normal for each set of 3 vertices | |
local n = (vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i]) | |
for j=0,2 do | |
local v=vertices[i+j].x ..vertices[i+j].y..vertices[i+j].z | |
unique[v]=unique[v]+n | |
end | |
end | |
--calculate average for each unique vertex | |
for i=1,#unique do | |
unique[i] = unique[i]:normalize() | |
end | |
--now apply averages to list of vertices | |
for i=1, #vertices do --calculate average | |
norm[i] = unique[vertices[i].x ..vertices[i].y..vertices[i].z] | |
end | |
return norm | |
else | |
--this assumes flat surfaces, and hard edges between triangles | |
local norm = {} | |
for i=1, #vertices,3 do --calculate normal for each set of 3 vertices | |
local n = ((vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i])):normalize() | |
norm[i] = n --then apply it to all 3 | |
norm[i+1] = n | |
norm[i+2] = n | |
end | |
return norm | |
end | |
end | |
--read table of parameters into a list | |
function LL.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 | |
local www | |
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,1,table.maxn(t)) | |
else --was an unnamed list, return it as is | |
return unpack(tbl) | |
end | |
end | |
-- ==== SHADER ========== | |
--cred to spacemonkey for his great code! | |
LL.Lighting = { | |
vertexShader = [[ | |
uniform mat4 modelViewProjection; | |
uniform mat4 mInvModel; | |
uniform mat4 mModel; | |
// --directional light | |
uniform bool hasDirectional; | |
uniform float directionalStrength; | |
uniform vec4 directionalDirection; | |
uniform vec4 directionalColor; | |
// --point light | |
uniform bool hasPoint1; | |
uniform vec4 point1Source; | |
uniform vec4 point1Color; | |
uniform float point1Strength; | |
uniform bool hasPoint2; | |
uniform vec4 point2Source; | |
uniform vec4 point2Color; | |
uniform float point2Strength; | |
uniform bool hasSpecular; | |
attribute vec4 position; | |
attribute vec4 color; | |
attribute vec2 texCoord; | |
attribute vec3 normal; | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
varying highp vec4 vNormal; | |
varying highp vec4 vPosition; | |
varying highp vec4 vDirectionalDiffuse; | |
varying highp vec4 vPoint1Diffuse; | |
varying highp vec4 vPoint2Diffuse; | |
void main() | |
{ | |
vColor = color; | |
vTexCoord = texCoord; | |
//--vNormal = mInvModel * vec4( normal, 0.0 ); //--convert to world space | |
vNormal = mModel * vec4( normal, 0.0 ); | |
vec4 norm = normalize(vNormal); | |
vPosition = mModel * position; //-- convert to world space | |
gl_Position = modelViewProjection * position; | |
if ( hasDirectional ) { | |
vDirectionalDiffuse = directionalColor * directionalStrength * | |
max( 0.0, dot( norm, directionalDirection )); | |
} | |
if ( hasPoint1 ) { | |
vec4 d = normalize( point1Source - vPosition ); | |
vPoint1Diffuse = point1Color * point1Strength * max( 0.0, dot( norm, d )); | |
} | |
if ( hasPoint2 ) { | |
vec4 d2 = normalize( point2Source - vPosition ); | |
vPoint2Diffuse = point2Color * point2Strength * max( 0.0, dot( norm, d2 )); | |
} | |
} | |
]], | |
fragmentShader = [[ | |
precision highp float; | |
uniform lowp sampler2D texture; | |
// --ambient light | |
uniform float ambientStrength; | |
uniform vec4 ambientColor; | |
// --directional light | |
uniform float directionalStrength; | |
uniform vec4 directionalDirection; | |
uniform vec4 directionalColor; | |
// -- point 1 light | |
uniform float point1Strength; | |
uniform vec4 point1Source; | |
uniform float point1Range; | |
uniform vec4 point1Color; | |
uniform bool point1IsSpot; | |
uniform vec4 point1Direction; | |
uniform float point1Cos; | |
// -- point 2 light | |
uniform float point2Strength; | |
uniform vec4 point2Source; | |
uniform float point2Range; | |
uniform vec4 point2Color; | |
uniform bool point2IsSpot; | |
uniform vec4 point2Direction; | |
uniform float point2Cos; | |
uniform vec4 eyePosition; | |
// --material properties | |
uniform float reflectivity; | |
uniform float specularPower; | |
uniform float specularStrength; | |
uniform vec4 specularColor; | |
// --switches to reduce unnecessary calculation | |
uniform bool hasTexture; | |
uniform bool hasDirectional; | |
uniform bool hasPoint1; | |
uniform bool hasPoint2; | |
uniform bool hasSpecular; | |
uniform bool hasEmissive; | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
varying highp vec4 vNormal; | |
varying highp vec4 vPosition; | |
varying highp vec4 vDirectionalDiffuse; | |
varying highp vec4 vPoint1Diffuse; | |
varying highp vec4 vPoint2Diffuse; | |
vec4 normalizedNormal = normalize(vNormal); | |
// -- specular light, uses Blinn-Phong half vector approach, faster than Phong | |
vec4 GetSpecularColor(vec4 lightPosition, vec4 lightColor, bool isDirectional) | |
{ | |
vec4 lightDirection; | |
if ( isDirectional ) lightDirection = lightPosition; | |
else lightDirection = vec4( normalize( lightPosition - vPosition )); | |
vec4 cameraDirection = normalize( eyePosition - vPosition ); | |
vec4 halfAngle = normalize( cameraDirection + lightDirection ); | |
vec4 specularColor = min(lightColor + 0.5, 1.0); | |
float spec = pow( max( 0.0, dot( normalizedNormal, halfAngle)), specularPower ); | |
return specularColor * spec * specularStrength; | |
} | |
// --attentuation over the range provided, stays at full for first 5% of range, then degrades linearly | |
float GetAttenuation(vec4 source,float range) | |
{ | |
float d = length( source.xyz - vPosition.xyz ) / range; | |
if (d < 0.02) return 1.0; | |
else return max( 0.0, (1.0 - d) / 0.98 ); | |
} | |
void main() | |
{ | |
lowp vec4 pixel=vColor; | |
if (hasTexture) pixel = texture2D( texture, vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0))) ; | |
// -- ambient | |
vec4 ambient = pixel * ambientStrength * ambientColor; | |
// --default | |
vec4 specular = vec4(0.,0.,0.,0.); | |
// -- directional light | |
vec4 directional=vec4(0.,0.,0.,0.); | |
if (hasDirectional) { | |
directional = pixel * reflectivity * vDirectionalDiffuse; | |
if (hasSpecular) { | |
specular=specular + directionalStrength * GetSpecularColor(directionalDirection, specularColor, true); | |
} | |
} | |
// -- point light 1 | |
vec4 point1Light=vec4(0.,0.,0.,0.); | |
if (hasPoint1) { | |
float fracSpot=1.0; | |
if (point1IsSpot) { | |
//--get vector from light to current pixel | |
vec4 lightToPixel = normalize( vPosition - point1Source ); | |
//--calculate cos of angle | |
float cos = dot( point1Direction, lightToPixel ); | |
//---compare with max cos of spotlight, interpolate if within its beam | |
if ( cos >= point1Cos && cos <= 1.0 ) { | |
fracSpot = max( 0.0, 1.0 - ( 1.0 - cos ) / ( 1.0 - point1Cos )); | |
} | |
else fracSpot = 0.0; | |
} | |
float attenuation1 = GetAttenuation( point1Source, point1Range ); | |
point1Light = pixel * reflectivity * fracSpot * attenuation1 * vPoint1Diffuse; | |
if (hasSpecular) { | |
specular=specular + fracSpot * attenuation1 * point1Strength * | |
GetSpecularColor(point1Source, specularColor, false); | |
} | |
} | |
// -- point light 2 | |
vec4 point2Light=vec4(0.,0.,0.,0.); | |
if (hasPoint2) { | |
float fracSpot2=1.0; | |
if (point2IsSpot) { | |
//--get vector from light to current pixel | |
vec4 lightToPixel2 = normalize( vPosition - point2Source ); | |
//--calculate cos of angle | |
float cos2 = dot( point2Direction, lightToPixel2 ); | |
//---compare with max cos of spotlight, interpolate if within its beam | |
if ( cos2 >= point2Cos && cos2 <= 1.0 ) { | |
fracSpot2 = max( 0.0, 1.0 - ( 1.0 - cos2 ) / ( 1.0 - point2Cos )); | |
} | |
else fracSpot2 = 0.0; | |
} | |
float attenuation2 = GetAttenuation( point2Source, point2Range ); | |
point2Light = pixel * reflectivity * fracSpot2 * attenuation2 * vPoint2Diffuse; | |
if (hasSpecular) { | |
specular=specular + fracSpot2 * attenuation2 * point2Strength * | |
GetSpecularColor(point2Source, specularColor, false); | |
} | |
} | |
vec4 totalColor = clamp(ambient + directional + point1Light + point2Light + specular,0.,1.); | |
totalColor.a=1.; | |
gl_FragColor=totalColor; | |
} | |
]] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment