Created
May 12, 2013 03:28
-
-
Save dermotbalson/5562307 to your computer and use it in GitHub Desktop.
Town225
This file contains hidden or 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
--# Notes | |
--[[ | |
These are notes for 3D Town version 2.25, 12 May 2012 by Ignatz | |
DESCRIPTION | |
This project allows you to create a 3D map with buildings you can walk around, and through | |
You customise it by adding buildings and other settings in the Map tab | |
NAVIGATION | |
The navigation can be changed to suit yourself. It is programmed in the touched function, along with a couple of other lines of code marked with "navigation" so you can find them | |
The navigation provided here works as follows: | |
* touch the top 1/4 or bottom 1/4 of the screen to move forward or backward. | |
* touch again to increase/ decrease speed. | |
* touch the left 1/4 or right 1/4 of the screen to turn left or right. | |
* touch again to turn faster or slower | |
* to stop moving and turning, touch the centre of the screen. | |
HOW TO USE IT - PREPARATION | |
1. You need all the images you are going to use, in your Dropbox or Documents account. They can | |
have any name as long as it is prefixed with "map-", so the program can find them. | |
If you choose Documents, change the imageFolder variable in the Map tab | |
2. You need to figure out how to scale each image (see below) | |
** Scaling your images ** | |
What I mean by scaling is that your images may be different sizes, so they won't be in | |
proportion to each other. When you define your town, you specify a scaling fraction for each image. | |
For example, in the demo town, most of the stone walls come from large images that would produce | |
enormous stones if they were used as is, so I've used a scaling factor of less than 0.1. | |
In general, you don't need large images - quite small images will do fine, when you consider that | |
buildings are only about 100 pixels, and doors only 10 across. | |
So - you don't need to edit the images, just adjust the scaling fraction to make them look right. | |
Don't worry too much about it at first, you can do it by trial and error. | |
HOW TO USE IT - DEFINING BUILDINGS | |
** Terrain (hills) ********************************************** | |
What to use it for: hills | |
Synatax: b:CreateTerrain(nx,ny,z,nw,nd,h,n,i,sc) | |
where | |
-- x,y = bottom left corner | |
-- z = min vertical height (ie can't fall below this) | |
-- w,d = width and depth of area to be terrained (pixels) | |
-- h = max height (pixels) | |
-- p = pixels per tile | |
-- n = noise level (bigger numbers result in bumpy terrian, smaller gives smoother results) | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
** Basic box building (4 walls) ********************************************** | |
What to use it for: house walls, hedges, fences | |
Syntax: b:BuildingWalls(x,y,w,d,h,i,sc,r) | |
where | |
--x,y is bottom left corner (looking at map from starting position, and 0,0 is bottom left of map) | |
--w = width along x axis (pixels) | |
--d = depth along x axis (pixels) | |
--h = height above ground along z 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) | |
--s = whether we want to store the building co-ords for future use - used by Building class, don't use it yourself | |
** Wall colour tint ********************************************** | |
What to use it for: tint the outer walls any colour | |
Syntax: b:AddTint(c) | |
--where c = colour (use a low alpha, eg 50) | |
** Building interior ********************************************** | |
What to use it for - to create a plastered interior, it will draw an interior set of walls | |
using a chosen image | |
Syntax: b:BuildingInteriorWalls(i,sc,c) | |
where | |
--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) | |
** Horizontal level ********************************************** | |
What to use it for: surface of map, any horizontal 2D surface | |
Syntax: b:Level(x,y,w,d,z,i,sc) | |
where | |
--x,y is bottom left corner (looking at map from starting position, and 0,0 is bottom left of map) | |
--w = width along x axis (pixels) | |
--d = depth along x axis (pixels) | |
--z = height above floor | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
Note - for a floor surface, set z=0.1 or similarly small. You can also use level to create ceilings inside buildings by setting z=height of walls. | |
** Roof ********************************************** | |
Syntax: b:Roof(o,i,sc) | |
where | |
--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 | |
** Floor (interior) ********************************************** | |
Syntax: b:Floor(i,sc) | |
where | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
** Extras for houses (doors, windows..) ********************************************** | |
What to use it for - anything attached to the wall of a building | |
Syntax: b:AddFeature(n,x,y,w,h,i,d) | |
where | |
--n is which wall to put it on, 1=front, 2=left, 3=right, 4=back | |
--x,y is bottom left corner (looking at map from starting position, and 0,0 is bottom left of map) | |
--w = width (pixels) | |
--h = height above ground (pixels) | |
--i = image or string name of image (do not include library name) | |
--d = true/false whether this feature should appear on the inside wall as well | |
** Stand alone features ********************************************** | |
What to use it for: images that aren't attached to anything, eg a dog | |
Syntax: b:AddImage(x,y,w,i,r) | |
where | |
--x,y 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 | |
TECHNICAL NOTES | |
The Building class is initialised with 6 parameters, as follows | |
Mesh = table of meshes holding all the objects | |
Layout = table holding details of where the walls are, so you don't walk through them | |
mapWidth,mapHeight | |
squareSize = size of squares for terrain purposes. Terrain is created using the noise function, and the bigger the squares, the more gradual the terrain. Make the map dimensions a multiple of this if possible. Note that the terrain height is reduced around the edge of the chosen area, so you don't get cliffs. | |
--]] | |
--# Main | |
-- Walkthrough main | |
function setup() | |
if Backup then Backup("Town 225a") end --my backup software | |
rad=math.pi/180 | |
d,da=0,0 --navigation | |
Mesh={} Mesh[1],Mesh[2]={},{} | |
Map={} | |
SetupMap() | |
lookY=10 | |
count=0 --for garbage collection, keeps memory down | |
parameter.integer("FPS",0,60,60) | |
parameter.integer("Elevation",0,500,posZ) -- allow user to vary height of viewpoint | |
parameter.text("Memory") | |
parameter.boolean("WalkThroughWalls",false) | |
print("Touch left, right of screen to turn, top/bottom to go forward/back") | |
end | |
function draw() | |
count=count+1 | |
background(175, 206, 223, 50) | |
spriteMode(CORNER) | |
pushMatrix() | |
pushStyle() | |
perspective(30, WIDTH/HEIGHT) | |
--navigation | |
angle=angle+da*DeltaTime | |
local dd=d*DeltaTime | |
local h0=b:HeightFromXY(posX,posY) | |
if WalkThroughWalls==false then | |
posX,posY,d=b:Move(posX,posY,dd*math.sin(angle*rad),dd*math.cos(angle*rad),d) | |
else | |
posX,posY=posX+dd*math.sin(angle*rad),posY+dd*math.cos(angle*rad) | |
end | |
local h1=b:HeightFromXY(posX,posY) | |
posZ=math.max(Elevation,h1+10) | |
if dd==0 or Elevation>10 then lookZ=10 else lookZ=(h1-h0)*100/dd+10 end | |
--end of navigation | |
--look in the same direction as we are facing | |
lookX,lookY=posX+1000*math.sin(angle*rad),posY+1000*math.cos(angle*rad) | |
camera(posX,posY,posZ,lookX,lookY,lookZ, 0,0,1) | |
--rotate(-90,1,0,0) --rotate so map is drawn into the screen instead of up it as normal - per 3D lab example | |
fill(255) | |
if count%20==1 then | |
table.sort(Mesh[2], | |
function(a,b) return vec2(posX,posY):dist(vec2(a.x,a.y))>vec2(posX,posY):dist(vec2(b.x,b.y)) end) | |
end | |
for i,mmmm in pairs(Mesh) do | |
for j,mm in pairs(Mesh[i]) do | |
--rotate free standing objects if required, so they face us | |
if i==2 and mm.x~=nil then | |
pushMatrix() | |
translate(mm.x+mm.w/2,mm.y,0) | |
local a | |
if count%21==1 then | |
local dx,dy=mm.x-posX,mm.y-posY | |
local ang=math.atan(dx/dy)/rad | |
rotate(-ang,0,0,1) | |
mm.m.a=ang | |
else | |
rotate(-mm.m.a,0,0,1) | |
end | |
translate(-mm.w/2,0,0) | |
mm.m:draw() | |
popMatrix() | |
else | |
mm.m:draw() | |
end | |
end | |
end | |
popStyle() | |
popMatrix() | |
FPS=FPS*.9+(1/DeltaTime)*.1 | |
Memory=gcinfo() | |
if count%100==0 then collectgarbage() end | |
end | |
--basic navigation, see Notes tab | |
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 | |
d=d-3 | |
center=false | |
elseif touch.y>HEIGHT*3/4 then | |
d=d+3 | |
center=false | |
end | |
if center then d=0 da=0 end | |
end | |
--# Building | |
Building = class() | |
function Building:init(m,l,w,d,sq) | |
self.MeshTable=m | |
--self.Map=mm | |
self.Layout=l | |
self.mapWidth,self.mapHeight=w,d | |
self.SquareSize=sq | |
self.HeightMap={} | |
self.exists=self:ImageExists() --set up list of available images | |
--initalise height map | |
for i=0,w/sq do | |
self.HeightMap[i]={} | |
for j=0,d/sq do | |
self.HeightMap[i][j]=0 | |
end | |
end | |
--initialise layout map | |
self.Layout={} | |
end | |
--draw a basic box with a given texture | |
--a box consists of 24 triangles, made up of the 8 corner vertices | |
--x,y is bottom left corner (looking at map from starting position, and 0,0 is bottom left of map) | |
--w = width along x axis (pixels) | |
--d = depth along x axis (pixels) | |
--h = height above ground along z 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) | |
--s = whether we want to store the building co-ords for future use - used by Building class, don't use it yourself | |
function Building:BuildingWalls(x,y,z,w,d,h,i,sc,r,s) | |
--store data first | |
if s==nil then --don't store if s is set | |
self.w={} | |
self.w.x1,self.w.y1,self.w.z1=x ,y ,z | |
self.w.x2,self.w.y2,self.w.z2=x+w,y+d,h | |
self.w.w,self.w.d,self.w.h=w,d,h | |
self.w.pitch=r | |
end | |
local img,colr | |
img=self:GetTexture(i) | |
if type(i)=="string" then | |
colr=color(255) | |
else | |
sc=1 | |
local r,g,b,a=img:get(1,1) | |
colr=color(r,g,b,a) | |
end | |
self.w.img,self.w.sc=img,sc | |
--now create rectangular box | |
local v,t={},{} --vertices and texture mappings | |
--front wall | |
self:AddWall(x,y,z,x+w,y,z+h,img,sc,v,t) | |
--left side | |
self:AddWall(x,y,z,x,y+d,z+h,img,sc,v,t) | |
--right side | |
self:AddWall(x+w,y,z,x+w,y+d,z+h,img,sc,v,t) | |
--back side | |
self:AddWall(x,y+d,z,x+w,y+d,z+h,img,sc,v,t) | |
--add ends | |
if r then self:AddRoofEnds(img,sc) end | |
--add to mesh | |
self:AddToMesh(1,v,t,img,colr) | |
--add to layout map, assume walls are no go area | |
for i=x,x+w do | |
if self.Layout[i]==nil then self.Layout[i]={} end | |
self.Layout[i][y],self.Layout[i][y+d]=1,1 | |
end | |
if self.Layout[x]==nil then self.Layout[x]={} end | |
if self.Layout[x+w]==nil then self.Layout[x+w]={} end | |
for i=y,y+d do | |
self.Layout[x][i],self.Layout[x+w][i]=1,1 | |
end | |
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 Building:BuildingInteriorWalls(i,sc,c) | |
local margin=0.05 | |
local x,y,z=self.w.x1+margin,self.w.y1+margin,self.w.z1 | |
local w,d,h=self.w.w-margin*2,self.w.d-margin*2,self.w.h | |
self:BuildingWalls(x,y,z,w,d,h,i,sc,nil,true) --last param is true so we don't store bldg coords | |
if c then self:Level(x,y,w,d,h,i,sc) end --ceiling if requested | |
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 Building:Floor(i,sc) | |
local margin=0.05 | |
local x,y,w,d,h=self.w.x1+margin,self.w.y1+margin,self.w.w-margin*2,self.w.d-margin*2,self.w.h | |
self:Level(x,y,w,d,margin,i,sc) | |
end | |
--this function is bulky mainly because of the scaling of images means they need to be tiled | |
--ie you may need several copies next to each other (and a fractional bit at the end) | |
--we have to calculate where each tile goes, in each triangle | |
--add to this the fact that the wall may be horizontal (ie x1~=X2, y1=y2) or vertical (x1=x2,y1~=y2) | |
function Building:AddWall(x1,y1,z1,x2,y2,z2,img,sc,v,t) | |
sc=sc or 1 | |
local imgW,imgH=img.width*sc,img.height*sc | |
local nX,nY,nZ=(x2-x1)/imgW,(y2-y1)/imgW,(z2-z1)/imgH --number of tiles required in x and y axes | |
for xx=0,nX do | |
fX=math.min(1,nX-xx) --fractional part of tile, x axis | |
xx1,xx2=x1+xx*imgW,x1+(xx+fX)*imgW | |
for yy=0,nY do | |
fY=math.min(1,nY-yy) --fractional part of tile, y axis | |
yy1,yy2=y1+yy*imgW,y1+(yy+fY)*imgW | |
for zz=0,nZ do | |
fZ=math.min(1,nZ-zz) --fractional part of tile, in Z axis | |
zz1,zz2=z1+zz*imgH,z1+(zz+fZ)*imgH | |
--create vectors and texture mappings | |
v[#v+1]=vec3(xx1,yy1,zz1) t[#t+1]=vec2(0,0) | |
v[#v+1]=vec3(xx2,yy2,zz1) t[#t+1]=vec2(fX+fY,0) --fX and fY added because only one can be nonzero | |
v[#v+1]=vec3(xx1,yy1,zz2) t[#t+1]=vec2(0,fZ) | |
v[#v+1]=vec3(xx1,yy1,zz2) t[#t+1]=vec2(0,fZ) | |
v[#v+1]=vec3(xx2,yy2,zz2) t[#t+1]=vec2(fX+fY,fZ) | |
v[#v+1]=vec3(xx2,yy2,zz1) t[#t+1]=vec2(fX+fY,0) | |
end | |
end | |
end | |
end | |
--similar to wall but horizontal, only using x and y axes | |
--x,y is bottom left corner (looking at map from starting position, and 0,0 is bottom left of map) | |
--w = width along x axis (pixels) | |
--d = depth along x axis (pixels) | |
--z = height above floor | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
function Building:Level(x,y,w,d,z,i,sc) | |
sc=sc or 1 | |
local img=self:GetTexture(i) | |
local v,t={},{} | |
local imgW,imgH=img.width*sc,img.height*sc | |
local nX,nY=w/imgW,d/imgW | |
for xx=0,nX do | |
local fX=math.min(1,nX-xx) | |
local xx1,xx2=x+xx*imgW,x+(xx+fX)*imgW | |
for yy=0,nY do | |
local fY=math.min(1,nY-yy) | |
local yy1,yy2=y+yy*imgW,y+(yy+fY)*imgW | |
v[#v+1]=vec3(xx1,yy1,z) t[#t+1]=vec2(0,0) | |
v[#v+1]=vec3(xx2,yy1,z) t[#t+1]=vec2(fX,0) | |
v[#v+1]=vec3(xx2,yy2,z) t[#t+1]=vec2(fX,fY) | |
v[#v+1]=vec3(xx2,yy2,z) t[#t+1]=vec2(fX,fY) | |
v[#v+1]=vec3(xx1,yy2,z) t[#t+1]=vec2(0,fY) | |
v[#v+1]=vec3(xx1,yy1,z) t[#t+1]=vec2(0,0) | |
end | |
end | |
--add to mesh | |
self:AddToMesh(1,v,t,img) | |
end | |
--puts image on wall n at position x,y with dimensions w,h, d is there for the future if we draw insides | |
--(if true means image is mirrored on the inside) | |
--n=1 front wall, 2=left, 3=right, 4=back, viewed from front perspective | |
--i is image name | |
function Building:AddFeature(n,x,y,w,h,i,d) | |
local img=self:GetTexture(i) | |
sc=w/img.width | |
h=sc*img.height | |
local v,t={},{} | |
local margin=.4 | |
if n==1 then --front | |
local yy=self.w.y1-margin | |
self:AddWall(self.w.x1+x,yy,y,self.w.x1+x+w,yy,y+h,img,sc,v,t) | |
if d then | |
self:AddWall(self.w.x1+x,self.w.y1+margin,y,self.w.x1+x+w,self.w.y1+margin,y+h,img,sc,v,t) | |
self:MakeGapInLevel(self.w.x1+x,self.w.y1,w,0) | |
end | |
elseif n==2 then --left | |
local xx=self.w.x1-margin | |
self:AddWall(xx,self.w.y2-x-w,y,xx,self.w.y2-x,y+h,img,sc,v,t) | |
if d then | |
self:AddWall(self.w.x1+margin,self.w.y2-x-w,y,self.w.x1+margin,self.w.y2-x,y+h,img,sc,v,t) | |
self:MakeGapInLevel(self.w.x1,self.w.y2-x-w,0,w) | |
end | |
elseif n==3 then --right | |
local xx=self.w.x2+margin | |
self:AddWall(xx,self.w.y1+x,y,xx,self.w.y1+x+w,y+h,img,sc,v,t) | |
if d then | |
self:AddWall(self.w.x2-margin,self.w.y1+x,y,self.w.x2-margin,self.w.y1+x+w,y+h,img,sc,v,t) | |
self:MakeGapInLevel(self.w.x2,self.w.y1+x,0,w) | |
end | |
elseif n==4 then --back | |
local yy=self.w.y2+margin | |
self:AddWall(self.w.x2-x-w,yy,y,self.w.x2-x,yy,y+h,img,sc,v,t) | |
if d then | |
self:AddWall(self.w.x2-x-w,self.w.y2-margin,y,self.w.x2-x,self.w.y2-margin,y+h,img,sc,v,t) | |
self:MakeGapInLevel(self.w.x1+x,self.w.y2,w,0) | |
end | |
end | |
--add to mesh | |
self:AddToMesh(1,v,t,img) | |
end | |
function Building:MakeGapInLevel(x,y,w,d) | |
if 1==1 then return end | |
if w>0 then | |
for i=x,x+w do | |
if self.Layout[i]==nil then self.Layout[i]={} end | |
self.Layout[i][y]=nil | |
end | |
end | |
if self.Layout[x]==nil then self.Layout[x]={} end | |
if d>0 then for i=y,y+d do self.Layout[x][i]=nil end end | |
end | |
function Building:Move(x,y,dx,dy,speed) | |
xx,yy=math.floor(x+dx),math.floor(y+dy) | |
if self.Layout[xx]==nil or self.Layout[xx][yy]==nil then return x+dx,y+dy,speed | |
else return x,y,0 end | |
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 Building:Roof(o,i,s) | |
local p=self.w.pitch*math.pi/180 --15 degree roof pitch | |
o=o or 3 | |
s=s or 1 | |
local img=self:GetTexture(i) | |
--self:Level(self.w.x1-o,self.w.y1-o,self.w.x2-self.w.x1+o*2,self.w.y2-self.w.y1+o*2,self.w.z2,i,s) | |
local iw,id,ih | |
iw=math.max(self.w.w,self.w.d)+o*2 | |
id=(iw/2)/math.cos(p) | |
ih=(iw/2)*math.tan(p) | |
local roof=image(iw,id) | |
setContext(roof) | |
local nw,nd=math.floor(iw/(s*roof.width)+.999),math.floor(id/(s*roof.height)+.999) | |
local ss=s*roof.width | |
for i=1,iw,nw do | |
for j=1,ih,nd do | |
sprite(img,i,j,ss) | |
end | |
end | |
setContext() | |
local v,t={},{} | |
if self.w.w>self.w.d then | |
v[1]=vec3(self.w.x1-o,self.w.y1-o,self.w.h) t[1]=vec2(0,0) | |
v[2]=vec3(self.w.x1-o,self.w.y1+self.w.d/2,self.w.h+ih) t[2]=vec2(0,1) | |
v[3]=vec3(self.w.x2+o,self.w.y1-o,self.w.h) t[3]=vec2(1,0) | |
v[4]=vec3(self.w.x2+o,self.w.y1-o,self.w.h) t[4]=vec2(1,0) | |
v[5]=vec3(self.w.x2+o,self.w.y1+self.w.d/2,self.w.h+ih) t[5]=vec2(1,1) | |
v[6]=vec3(self.w.x1-o,self.w.y1+self.w.d/2,self.w.h+ih) t[6]=vec2(0,1) | |
v[7]=vec3(self.w.x1-o,self.w.y2+o,self.w.h) t[7]=vec2(0,0) | |
v[8]=vec3(self.w.x1-o,self.w.y2-self.w.d/2,self.w.h+ih) t[8]=vec2(0,1) | |
v[9]=vec3(self.w.x2+o,self.w.y2+o,self.w.h) t[9]=vec2(1,0) | |
v[10]=vec3(self.w.x2+o,self.w.y2+o,self.w.h) t[10]=vec2(1,0) | |
v[11]=vec3(self.w.x2+o,self.w.y2-self.w.d/2,self.w.h+ih) t[11]=vec2(1,1) | |
v[12]=vec3(self.w.x1-o,self.w.y2-self.w.d/2,self.w.h+ih) t[12]=vec2(0,1) | |
else | |
v[1]=vec3(self.w.x1-o,self.w.y1-o,self.w.h) t[1]=vec2(0,0) | |
v[2]=vec3(self.w.x1+self.w.w/2,self.w.y1-o,self.w.h+ih) t[2]=vec2(0,1) | |
v[3]=vec3(self.w.x1-o,self.w.y2+o,self.w.h) t[3]=vec2(1,0) | |
v[4]=vec3(self.w.x1-o,self.w.y2+o,self.w.h) t[4]=vec2(1,0) | |
v[5]=vec3(self.w.x1+self.w.w/2,self.w.y2+o,self.w.h+ih) t[5]=vec2(1,1) | |
v[6]=vec3(self.w.x1+self.w.w/2,self.w.y1-o,self.w.h+ih) t[6]=vec2(0,1) | |
v[7]=vec3(self.w.x2+o,self.w.y1-o,self.w.h) t[7]=vec2(0,0) | |
v[8]=vec3(self.w.x2-self.w.w/2,self.w.y1-o,self.w.h+ih) t[8]=vec2(0,1) | |
v[9]=vec3(self.w.x2+o,self.w.y2+o,self.w.h) t[9]=vec2(1,0) | |
v[10]=vec3(self.w.x2+o,self.w.y2+o,self.w.h) t[10]=vec2(1,0) | |
v[11]=vec3(self.w.x2-self.w.w/2,self.w.y2+o,self.w.h+ih) t[11]=vec2(1,1) | |
v[12]=vec3(self.w.x2-self.w.w/2,self.w.y1-o,self.w.h+ih) t[12]=vec2(0,1) | |
end | |
--add to mesh | |
self:AddToMesh(1,v,t,img) | |
end | |
function Building:AddRoofEnds(img,sc) | |
local v,t={},{} --vertices and texture mappings | |
local p=self.w.pitch*math.pi/180 --15 degree roof pitch | |
local imax,imin,iw,ih | |
imax=math.max(self.w.w,self.w.d) | |
imin=math.min(self.w.w,self.w.d) | |
ih=(imax/2)*math.tan(p) | |
local e=image(imin,ih) | |
setContext(e) | |
local nw,nd=sc*img.width,sc*img.height | |
for i=1,imin+nw,nw do | |
for j=1,ih+nd,nd do | |
sprite(img,i,j,nw) | |
end | |
end | |
setContext() | |
--print(self.w.w,self.w.d,e.width,e.height) | |
--self:AddImage(self.w.x1+self.w.y1,self.w.y1+self.w.x1+300,e.width,e) | |
if self.w.w>self.w.d then | |
v[1]=vec3(self.w.x1,self.w.y1,self.w.h) t[1]=vec2(0,0) | |
v[2]=vec3(self.w.x1,self.w.y1+self.w.d/2,self.w.h+ih) t[2]=vec2(.5,1) | |
v[3]=vec3(self.w.x1,self.w.y2,self.w.h) t[3]=vec2(1,0) | |
v[4]=vec3(self.w.x2,self.w.y1,self.w.h) t[4]=vec2(0,0) | |
v[5]=vec3(self.w.x2,self.w.y1+self.w.d/2,self.w.h+ih) t[5]=vec2(.5,1) | |
v[6]=vec3(self.w.x2,self.w.y2,self.w.h) t[6]=vec2(1,0) | |
else | |
v[1]=vec3(self.w.x1,self.w.y1,self.w.h) t[1]=vec2(0,0) | |
v[2]=vec3(self.w.x1+self.w.w/2,self.w.y1,self.w.h+ih) t[2]=vec2(.5,1) | |
v[3]=vec3(self.w.x2,self.w.y1,self.w.h) t[3]=vec2(1,0) | |
v[4]=vec3(self.w.x1,self.w.y2,self.w.h) t[4]=vec2(0,0) | |
v[5]=vec3(self.w.x1+self.w.w/2,self.w.y2,self.w.h+ih) t[5]=vec2(.5,1) | |
v[6]=vec3(self.w.x2,self.w.y2,self.w.h) t[6]=vec2(1,0) | |
end | |
--add to mesh | |
self:AddToMesh(1,v,t,e,colr) | |
end | |
--adds tint to outside of building | |
function Building:AddTint(c) | |
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() | |
self:BuildingWalls( self.w.x1-margin, | |
self.w.y1-margin, | |
self.w.z1, | |
self.w.w+margin*2, | |
self.w.d+margin*2, | |
self.w.h, | |
e,1,self.w.pitch) | |
end | |
--adds stand alone image | |
--x,y 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 | |
function Building:AddImage(xx,yy,w,i,r) | |
local img | |
img=self:GetTexture(i) | |
local h=w*img.height/img.width | |
local v,t={},{} | |
local x,y | |
if r then x,y=0,0 else x,y=xx,yy end | |
--identify the location and calculate height | |
local z=self:HeightFromXY(xx+w/2,yy) | |
v[1]=vec3(x,y,z) t[1]=vec2(0,0) | |
v[2]=vec3(x,y,z+h) 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,z+h) t[5]=vec2(1,1) | |
v[6]=vec3(x,y,z+h) t[6]=vec2(0,1) | |
if r then | |
self:AddToMesh(2,v,t,img,nil,xx,yy,w) | |
else | |
self:AddToMesh(2,v,t,img,nil,nil,nil,w) | |
end | |
end | |
--formula taken from: http://en.wikipedia.org/wiki/Bilinear_interpolation | |
function Building:HeightFromXY(x,y) | |
--identify the location and calculate height | |
local mx,my=math.floor(x/self.SquareSize)+1,math.floor(y/self.SquareSize)+1 | |
if mx<0 or my<0 then return 0 end | |
local px,py=x/self.SquareSize-mx+1,y/self.SquareSize-my+1 | |
local h=self.HeightMap[mx][my]*(1-px)*(1-py)+ | |
self.HeightMap[mx+1][my]*px*(1-py)+ | |
self.HeightMap[mx+1][my+1]*px*py+ | |
self.HeightMap[mx][my+1]*(1-px)*py | |
return h | |
end | |
function Building:GetTexture(i) | |
if type(i)=="string" then | |
if self.exists(i)==false then print("ERROR: "..i.." does NOT exist") end | |
return readImage(imageFolder..":"..i) | |
else | |
return i | |
end | |
end | |
function Building:ImageExists(i) | |
local s=spriteList(imageFolder) | |
local sList={} | |
for i=1,#s do | |
if string.sub(s[i],1,4)=="map-" then sList[s[i]]=true end | |
end | |
return function(i) | |
if sList[i]==true then return true else return false end | |
end | |
end | |
function Building:AddToMesh(n,v,t,img,c,x,y,w) | |
local mm={} | |
mm.m=mesh() | |
local colr=c or color(255,255,255,255) | |
mm.m:setColors(colr) | |
mm.m.vertices=v | |
mm.m.texCoords=t | |
mm.m.texture=img | |
mm.type=type or "b" | |
mm.x,mm.y,mm.w=x,y,w | |
table.insert(self.MeshTable[n],mm) | |
collectgarbage() | |
end | |
-- x,y = bottom left corner | |
-- z = min vertical height (ie can't fall below this) | |
-- w,d = width and depth of area to be terrained (pixels) | |
-- h = max height (pixels) | |
-- p = pixels per tile | |
-- n = noise level (bigger numbers result in bumpy terrian, smaller gives smoother results) | |
--i = image or string name of image (do not include library name) | |
--sc = image scaling factor (see above) 0-1 | |
function Building:CreateTerrain(nx,ny,z,nw,nd,h,n,i,sc) | |
local tiles = {} | |
local min,max=9,-9 | |
--calculate noise level | |
for i = 1, nw do | |
tiles[i] = {} | |
for j = 1, nd do | |
tiles[i][j] = noise(i * n, j * n) | |
if tiles[i][j] > max then max=tiles[i][j] end | |
if tiles[i][j] < min then min=tiles[i][j] end | |
end | |
end | |
local range=max-min | |
--rescale all values between 0 and 1 | |
--also make the effect smaller at the edges so we don't get cliffs (the f factor below) | |
for i = 1, nw do | |
for j = 1, nd do | |
--f is close to 1 at the middle and close to 0 at the edges | |
local f=(math.min(1,math.max(0,1-(math.abs(i-nw/2)+math.abs(j-nd/2))*4/(nw+nd))))^.33 | |
tiles[i][j] = f*(tiles[i][j]-min)/range | |
end | |
end | |
--add a row/col of zero's all the way round | |
tiles[0],tiles[#tiles+1]={},{} | |
for i=0,#tiles do | |
tiles[0][i],tiles[#tiles][i]=0,0 | |
end | |
local n=#tiles[1] | |
for i=0,n+1 do | |
tiles[i][0],tiles[i][n+1]=0,0 | |
end | |
--create image by tiling image provided | |
local w,d=nw*self.SquareSize,nd*self.SquareSize | |
local img=self:GetTexture(i) | |
local e=image(w,d) | |
setContext(e) | |
local ww,dd=sc*img.width,sc*img.height | |
for i=0,w,ww do | |
for j=0,d,dd do | |
sprite(img,i,j,ww) | |
end | |
end | |
setContext() | |
--use noise to generate terrain vertices | |
local x,y=(nx-1)*self.SquareSize+1,(ny-1)*self.SquareSize+1 | |
local v,t={},{} | |
local x1=x | |
local z1,z2,z3,z4 | |
for i=1,#tiles-1 do | |
local y1=y | |
for j=1,#tiles[1]-1 do | |
--calculate height at corner by averaging centre values | |
z1=z+h*(tiles[i-1][j-1]+tiles[i-1][j]+tiles[i][j]+tiles[i][j-1])/4 --bottom left | |
z2=z+h*(tiles[i+1][j-1]+tiles[i][j-1]+tiles[i][j]+tiles[i+1][j])/4 --bottom right | |
z3=z+h*(tiles[i+1][j+1]+tiles[i+1][j]+tiles[i][j]+tiles[i][j+1])/4 --top right | |
z4=z+h*(tiles[i-1][j+1]+tiles[i][j+1]+tiles[i][j]+tiles[i-1][j])/4 --top left | |
self.HeightMap[nx+i-1][ny+j-1],self.HeightMap[nx+i][ny+j-1]=z1,z2 | |
self.HeightMap[nx+i][ny+j],self.HeightMap[nx+i-1][ny+j]=z3,z4 | |
--print(i,j,self.HeightMap[i][j]) | |
if i==1 then z1,z4=z,z elseif i==#tiles-1 then z2,z3=z,z end | |
if j==1 then z1,z2=z,z elseif j==#tiles[1]-1 then z3,z4=z,z end | |
local x2,y2=x1+self.SquareSize,y1 --bottom right | |
local x3,y3=x1+self.SquareSize,y1+self.SquareSize --top right | |
local x4,y4=x1,y1+self.SquareSize --top left | |
v[#v+1]=vec3(x1,y1,z1) t[#t+1]=vec2((x1-x)/w,(y1-y)/d) --bottom left | |
v[#v+1]=vec3(x2,y2,z2) t[#t+1]=vec2((x2-x)/w,(y2-y)/d) --bottom right | |
v[#v+1]=vec3(x3,y3,z3) t[#t+1]=vec2((x3-x)/w,(y3-y)/d) --top right | |
v[#v+1]=vec3(x3,y3,z3) t[#t+1]=vec2((x3-x)/w,(y3-y)/d) --top right | |
v[#v+1]=vec3(x4,y4,z4) t[#t+1]=vec2((x4-x)/w,(y4-y)/d) --top left | |
v[#v+1]=vec3(x1,y1,z1) t[#t+1]=vec2((x1-x)/w,(y1-y)/d) --bottom left | |
y1=y1+self.SquareSize | |
end | |
x1=x1+self.SquareSize | |
end | |
self:AddToMesh(1,v,t,e) | |
end | |
--# Map | |
--[[ | |
Everything on this tab is a user setting. Just follow the instructions. | |
--]] | |
function SetupMap() | |
mapWidth,mapHeight=2400,3600 --map width/height in pixels, max is probably 4096 or when Codea collapses | |
--x is width, y is depth, z is vertical height | |
posX,posY,posZ=140,20,15 --starting position, z is about head height | |
angle=0 --initial angle, 0 faces forward | |
imageFolder="Dropbox" -- or Documents, if you prefer | |
squareSize=48 --used for terrain, see Notes tab | |
b=Building(Mesh,Layout,mapWidth,mapHeight,squareSize) | |
--tile the ground | |
b:Level(0,0,mapWidth,mapHeight,0,"map-gravel11",.05) | |
--terrain | |
--have deliberately included squareSize in the calcs below to remind myself the width and height | |
--must be divisible by it | |
b:CreateTerrain(1,576/squareSize,1,960/squareSize,960/squareSize,100,0.2,"map-grass10",.05) | |
--make wall surrounding map | |
local i="map-hedge2" | |
b:BuildingWalls(0,0,0,mapWidth,1,15,i,.15) | |
b:BuildingWalls(0,0,0,1,mapHeight,15,i,.15) | |
b:BuildingWalls(mapWidth,0,0,1,mapHeight,15,i,.15) | |
b:BuildingWalls(0,mapHeight,0,mapWidth,1,15,i,.15) | |
--first building | |
b:BuildingWalls(20,20,0,100,101,30,"map-stone1",.1,15) | |
b:AddFeature(3,50,0,10,20,"map-door5") | |
b:AddFeature(3,25,10,10,20,"map-window2") | |
b:AddFeature(3,75,10,10,20,"map-window2") | |
b:Roof(3,"map-roof1",.5) | |
--second building | |
b:BuildingWalls(20,140,0,100,70,30,"map-whitewall2",.05,15) | |
b:AddTint(color(255,255,0,50)) | |
b:AddFeature(3,30,0,10,20,"map-door1") | |
b:AddFeature(3,5,5,15,10,"map-poster1") | |
b:AddFeature(4,1,0.5,8,10,"map-Ignatz1") | |
b:AddFeature(4,80,0,8,10,"map-door10") | |
b:AddFeature(4,30,5,25,10,"map-window3") | |
b:Roof(2,"map-roof1",.5) | |
--third building | |
b:BuildingWalls(150,20,0,100,150,25,"map-whitewall1",.05,15) | |
b:BuildingInteriorWalls("map-whitewall1",.05,true) | |
b:AddTint(color(0,0,255,20)) | |
b:AddFeature(2,30,8,10,15,"map-window5",true) | |
b:AddFeature(2,120,8,10,15,"map-window5",true) | |
b:AddFeature(2,80,0,10,20,"map-door10",true) | |
b:Floor("map-carpet1",.1) | |
b:Roof(2,"map-roof1") | |
b:AddImage(180,140,10,"map-dog4",true) | |
b:BuildingWalls(150,200,0,1,1,25,"map-bark1",.02) | |
b:BuildingWalls(200,200,0,1,1,25,"map-bark1",.02) | |
b:BuildingWalls(250,200,0,1,1,25,"map-bark1",.02) | |
b:Level(148,170,104,32,25,"map-roof1",.05) | |
--fourth building | |
b:BuildingWalls(150,230,0,70,60,20,"map-whitewall2",.05,15) | |
b:AddTint(color(231, 137, 20, 25)) | |
b:AddFeature(2,15,5,6,15,"map-window7") | |
b:AddFeature(2,40,0,10,20,"map-door2") | |
b:Roof(2,"map-roof2") | |
b:Level(153,205,65,27,.1,"map-garden1",.05) | |
--extras | |
b:Level(130,150,10,10,.1,"map-grate1",.05) | |
b:Level(2,235,90,300,.1,"map-grass1",.1) | |
b:AddImage(60,450,10,"map-dog4",true) | |
b:BuildingWalls(92,235,0,0.5,300,10,"map-fence1",.2) | |
--castle | |
local cw,cd,ch,cc=550,550,70,50 --width, depth, height, wall thickness | |
local cx,cy=30,1500 --x,y of bottom left | |
local cg,ct=100,15 --gaps between towers, tower height | |
b:BuildingWalls(cx,cy,0,cw,cd,ch,"map-stonewall1",.1) | |
b:AddFeature(1,100,0,50,50,"map-castledoor2",.2,true) | |
b:BuildingWalls(cx+cc,cy+cc,0,cw-cc,cd-cc,ch,"map-stonewall1",.1) | |
b:AddFeature(1,50,0,50,50,"map-castledoor2",.2,true) | |
for i=0,cw,cg do --mini towers on front wall | |
b:BuildingWalls(cx+i,cy,ch,cc,cc,ct,"map-stonewall1",.1) | |
end | |
for i=cg,cd,cg do --mini towers on left wall | |
b:BuildingWalls(cx,cy+i,ch,cc,cc,ct,"map-stonewall1",.1) | |
end | |
--forest | |
for i=1,100 do | |
b:AddImage(math.random(10,950),math.random(600,1500), | |
math.random(40,80),"map-tree"..math.random(27,33).."c",true) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment