Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Created May 12, 2013 03:28
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/5562307 to your computer and use it in GitHub Desktop.
Save dermotbalson/5562307 to your computer and use it in GitHub Desktop.
Town225
--# 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