Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Created February 17, 2016 03:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dermotbalson/eddcd793534eedf34c03 to your computer and use it in GitHub Desktop.
Save dermotbalson/eddcd793534eedf34c03 to your computer and use it in GitHub Desktop.
Tank 7b
--# Main
-- Tiger7b
displayMode(FULLSCREEN)
function setup()
backup("WoT","ver 107b","visibility")
Settings()
end
function Settings()
backgroundColor=color(152, 180, 208, 255)
mapWidth,mapHeight=2000,2000
fogRange=150000
LoadImages()
MakeTerrain(mapWidth,mapHeight)
SetupTanks()
camAngle=math.pi/2
speed=0.5
FPS=60
joy=JoyStick()
joyAiming=JoyStick{centre=vec2(310,105)}
firePos,fireRadius=vec2(WIDTH-100,105),100
zoomPos,zoomRadius=vec2(WIDTH-105,600),50
zoomLevels,currZoom,zoomDefault={90,60,45,30,10},3,3
SetZoom(zoomDefault)
end
function SetupTanks()
local player=Tank(true)
player.pos=vec3(350,0.5,-50)
player.turretAngle=0
player:rotate(0)
player.gunAngle=0
player.viewAngle=180
player.team="A"
T={player=player}
for j=1,20 do
local t=Tank()
local keepGoing=true
while keepGoing do
keepGoing=false
t.pos=vec3((0.05+.9*math.random())*mapWidth,0.5,-(0.05+0.9*math.random())*mapHeight)
for _,tt in pairs(T) do
if t.pos:dist(tt.pos)<Tank.collisionRange then keepGoing=true break end
end
end
t:rotate(math.random(0,360),true)
t.turretAngle=0
t.gunAngle=0
t.turretAngleChange=0.25
t.gunAngleChange=0.1
t.team="B"
T[t]=t
end
---for _,t in pairs(T) do
-- if t.team=="B" then t.shader.teamColour=color(102, 220, 37, 50) end
--end
end
function draw()
background(backgroundColor)
FPS=FPS*0.8+0.2/DeltaTime
perspective(zoomLevels[currZoom])
local d=joy:update()
local aim=joyAiming:update()
--T.player:update(d.x, d.y, aim.x, aim.y)
if joy:isTouched() or joyAiming:isTouched() then
T.player.viewAngle=-90+T.player.angle+T.player.turretAngle
end
SetCamera()
qq=0
--for i=1,#Terrain.trees do Terrain.trees[i].occ=1 end
for _,t in pairs(T) do
if t.player then t:update(d.x, d.y, aim.x, aim.y)
else t:update(0,0.5,0,0) end
t:draw(camPos,camLook,camDir,T)
end
--T.player:draw(camPos,camLook,camDir)
DrawShots(T)
DrawTerrain(camPos)
joy:draw()
joyAiming:draw()
DrawHUD()
end
function DrawHUD()
ortho()
viewMatrix(matrix())
pushStyle()
if T.player:ready() then fill(109, 150, 205, 100) else fill(255,0,0,100) end
ellipse(firePos.x,firePos.y,fireRadius)
fill(0)
fontSize(18)
text("FPS: "..math.floor(FPS),50,HEIGHT-25)
text("Speed: "..math.floor(T.player.speed),50,HEIGHT-50)
text("Tanks:"..qq,50,HEIGHT-75)
--text("Viz: "..math.floor(viz*100),50,HEIGHT-100)
if hitText then
fill(255)
text(hitText,WIDTH-100,HEIGHT-200)
end
fill(255,255,0,100)
if drawTouch then ellipse(drawTouch.x,drawTouch.y,25) drawTouch=nil end
if aiming then
pushStyle()
stroke(0)
strokeWidth(2)
line(WIDTH/2,0,WIDTH/2,HEIGHT)
line(0,HEIGHT/2,WIDTH,HEIGHT/2)
noFill()
ellipse(WIDTH/2,HEIGHT/2,200)
popStyle()
--draw zoom button
fill(196, 118, 193, 255)
ellipse(zoomPos.x,zoomPos.y,zoomRadius*2)
end
popStyle()
end
function SetCamera()
if aiming then
local b1,b2=T.player:SetAim()
local d=(b2-b1):normalize()
camPos=b2+d*2.3
camLook=camPos+d
else
camPos=T.player.pos+RotateVector(vec3(0,20,-20),T.player.viewAngle,0)
camLook=T.player.pos+vec3(0,17,0)
end
camDir=(camLook-camPos):normalize()
--camPos=TTT.pos+vec3(0,9,0) ---zzzz
--camLook=(player.pos-camPos):normalize()+camPos
camera(camPos.x,camPos.y,camPos.z,camLook.x,camLook.y,camLook.z)
PrepareVisibilityTests(camPos,camDir)
end
function SetLighting(m,s)
s=s or TankShader
m.shader=shader(s.v,s.f)
m.shader.directDirection=vec4(-2,2,1,0):normalize()
m.shader.ambientColor=color(255)*0.4
m.shader.directColor=color(255)*0.5
m.shader.fog=fogRange
m.shader.mistColor=backgroundColor
m.shader.offset=0
m.shader.mModel=modelMatrix()
m.shader.teamColour=color(102, 220, 37, 50) --tint to apply to tank colour, default is nil color(102, 220, 37, 50)
return m
end
function touched(t)
drawTouch=vec2(t.x,t.y)
if joy:touched(t) or joyAiming:touched(t) or FireButtonTouched(t) or ZoomButtonTouched(t) then return
elseif t.state==MOVING then
T.player.viewAngle=T.player.viewAngle+2*t.deltaX/WIDTH*zoomLevels[currZoom]
aiming=nil
SetZoom(zoomDefault)
end
end
function FireButtonTouched(t)
if t.state==ENDED and vec2(t.x,t.y):dist(firePos)<fireRadius then
if not aiming then
aiming=true
else
T.player:Shoot()
aiming=nil
SetZoom(zoomDefault)
end
return true
end
end
function ZoomButtonTouched(t)
if t.state==ENDED and vec2(t.x,t.y):dist(zoomPos)<zoomRadius then
local n=currZoom+1
if n>#zoomLevels then n=1 end
SetZoom(n)
return true
end
end
function SetZoom(n)
currZoom=n
--cosFOV=math.cos(math.rad(zoomLevels[n]*1.34/2))
cosFOV=math.cos(math.rad(zoomLevels[n]/2))
cosFOV2=cosFOV*cosFOV
sinFOV=math.sin(math.rad(zoomLevels[n]/2))
end
--# Model
--Model
local sin,cos,atan2=math.sin,math.cos,math.atan2
local min,max,pi,abs,rad,deg=math.min,math.max,math.pi,math.abs,math.rad,math.deg
Tank=class()
Tank.turretMaxAngle=40
Tank.barrelMaxAngles=vec2(-5,20)
Tank.maxSpeed=vec2(5,16) --pixels/sec
Tank.maxSpeedChange=0.1
Tank.reloadTime=5
Tank.width=19
Tank.count=0
Tank.shotRadius=15
Tank.bounceDot=0.35
Tank.damageReductionWithDistance=1/10000
Tank.shotForce=50
Tank.turnRate=0.5
Tank.turretTurnRate=1
Tank.collisionRange=30
--Tank.avoidStrength=50
Tank.colors={color(158, 171, 48, 255),color(74, 81, 45, 255)}
Tank.minRange=30
Tank.maxRange=200
Tank.minShootFOV=0.1
--visibility checks
Tank.tgtFOV=rad(45/2) --the field of view used for detecting other tanks
Tank.tgtFOV_centre,Tank.tgtFOV_edge=.05,.1
--Tank.tgtMinAngle=0.1
Tank.movingAdjust=0.5 --adjustment if other tank is moving and easier to see
function Tank:init(p)
if not Tank.model then
--Tank.LoadTextures()
Tank.Shot=MakeShell()
Tank.CreateModel()
end
self.trackOffset=0
self.turretAngle=0
self.gunAngle=0
self.startOfBarrel=vec3(-3.8,5.75,0)
self.endOfBarrel=vec3(-10,0,0) --position of end of barrel relative to its base
self.muzzleSpeed=2000 --px/s
self.health=100
self.speed=0.25
self.angle=-90
self.angleTarget=self.angle
self.rad=math.rad(self.angle)
self.pos=vec3(0,0,0)
self.offset2=math.random()
self.wobble=math.random()
self.lastShot=-99
Tank.count=Tank.count+1
self.player=p
self.id=self
self.ammoStatus,self.speedStatus,self.aimStatus,self.visStatus,self.rotationStatus=1,1,1,1,1
self.health=1
end
function Tank:draw(camPos,camLook,camDir,tanks)
self.trackOffset=self.trackOffset-self.speed/200
self:Drive()
if not self.player then
self:Detect(tanks)
if IsVisible(self.pos,camPos,camDir,Tank.width/2,sinFOV)==false then return end
end
qq=qq+1
local m=Tank.model
m.track.shader.offset=self.trackOffset
pushMatrix()
translate(self.pos:unpack())
rotate(self.angle,0,1,0)
if not self.player then self:Wobble(self.speed) end
m.wheels.shader.mModel=modelMatrix()
m.wheels.shader.camPos=camPos
m.wheels:draw()
m.track.shader.mModel=modelMatrix()
m.track.shader.camPos=camPos
m.track:draw()
m.body.shader.mModel=modelMatrix()
m.body.shader.camPos=camPos
m.body.shader.offset2=self.offset2
m.body:draw()
rotate(self.turretAngle,0,1,0)
m.turret.shader.mModel=modelMatrix()
m.turret.shader.camPos=camPos
m.turret.shader.offset2=self.offset2
m.turret:draw()
translate(self.startOfBarrel:unpack())
rotate(-self.gunAngle,0,0,1)
m.barrel.shader.mModel=modelMatrix()
m.barrel.shader.camPos=camPos
m.barrel.shader.offset2=self.offset2
m.barrel:draw()
--calculate position and angle of end of barrel
local mm=modelMatrix()
local a=vec3(mm[13],mm[14],mm[15])
mm=mm:translate(self.endOfBarrel:unpack())
self.muzzlePos=vec3(mm[13],mm[14],mm[15])
self.muzzleAngle=(self.muzzlePos-a):normalize()
popMatrix()
end
function Tank:Drive()
--update position
local dir=vec3(-math.cos(self.rad),0,math.sin(self.rad))
--temporary position value
local pos=self.pos+dir*self.speed*self.speedStatus*DeltaTime
--move player if not turning
if self.player then
if self.angleTarget==self.angle then self.pos=pos end
return
end
--manage the bot tanks
--if we are turning or hit the edge of the map, don't move
if self.angleTarget==self.angle then --not turning
--turn at the edges of the map
if pos.x<20 or pos.x>mapWidth-20 or pos.z>-20 or pos.z<20-mapHeight then
local a=math.random(75,105)
if math.random()<0.5 then self:rotate(-a) else self:rotate(a) end
else
if not self:CheckForCollisions(dir) then self.pos=pos end
end
end
end
function Tank:Detect(tanks) --check if the bot can see any enemies
local enemies={}
--now check the other tanks
for i,t in pairs(tanks) do
if t.team~=self.team then --only check enemies
--are they in our FOV?
local a=AngleBetweenVec(self.pos-t.pos,self.dir)
--if in FOV, get the occlusion % and target fov (width of target, as an angle)
if abs(a)<Tank.tgtFOV then
local percent,fov=self:GetOcclusion(t)
--is tank visible? This depends on percent, whether the other tank is moving, and where it
--is in our FOV
--calculate what percent needs to be for us to see the other tank
local tgtPercent=Tank.tgtFOV_centre+(Tank.tgtFOV_edge-Tank.tgtFOV_centre)*a/22.5
if t.speed~=0 then tgtPercent=tgtPercent*Tank.movingAdjust end
if percent*fov>=tgtPercent then --we see the tank
self:rotate(deg(a),false,true)
end
end
end
end
end
function Tank:CheckForCollisions(dir)
local minDist,minP,minT=Tank.collisionRange
local r=Tank.shotRadius*2
for _,t in pairs(T) do
if t~=self then
local d=self.pos:dist(t.pos)
if d<minDist then
local p=self.pos+d*dir
if p:dist(t.pos)<r then
minDist=d
minP=p
minT=t.pos
end
end
end
end
if minT then
if (minP.x-self.pos.x)*(minT.z-self.pos.z)-(minP.z-self.pos.z)*(minT.x-self.pos.x)>0 then
self:rotate(0.5) else self:rotate(-0.5) end
return true
end
end
function Tank:update(a1,s,a2,a3)
self:rotate(-a1/4)
local ss
if s<0 then ss=s*Tank.maxSpeed[1] else ss=s*Tank.maxSpeed[2] end
self.speed=self.speed+math.max(-Tank.maxSpeedChange,math.min(Tank.maxSpeedChange,ss-self.speed))
self.turretAngle=math.max(-Tank.turretMaxAngle,math.min(Tank.turretMaxAngle,self.turretAngle-a2/2))
self.gunAngle=math.max(Tank.barrelMaxAngles[1],math.min(Tank.barrelMaxAngles[2],self.gunAngle+a3/5))
end
--local testing=true
function Tank:GetOcclusion(t)
local tiles=GetTiles(self.pos,t.pos,tileSize,treeTiles)
local trees={}
for i,tt in pairs(tiles) do
for j=1,#tt do
local jj=tt[j]
if not trees[jj] then
---treesID[jj].occ=0.75
trees[jj]=treesID[jj]
end
end
end
if testing then print("self=",self.pos) print("player=",T.player.pos) end
local left,right=self:GetTankEdges(T.player) if testing then print(left,right) end
local w=right-left
local points={} --create table of test points
for i=1,19 do
points[i]=left+i*0.05*w
end
local n=19
--test trees in our list
for _,c in pairs(trees) do
local t1,t2=GetTangentAngles(c.pos,c.r*0.9,self.pos,t.pos) if testing then print(c.pos,c.r,t1,t2) end
---print(t1,t2)
if t2<left or t1>right then --c.occ=1
else
for i=#points,1,-1 do
if points[i]>t1 and points[i]<t2 then table.remove(points,i) n=n-1 end
end
end
end
testing=false
return n/19,w --return occlusion % and width of tank as an angle
end
local tankAngles={true,true,true,true}
function Tank:GetTankEdges(t)
local s,c=sin(t.rad),cos(t.rad)
local minA,maxA=999,-999
local corners=Tank.Test.corners
local px,pz=t.pos.x,t.pos.z
if testing then print("angle",t.rad) end
local vx,vz=t.pos.x-self.pos.x,t.pos.z-self.pos.z
for i=1,4 do
local x,z=corners[i].x,corners[i].z
local xx,zz=px+c*x-s*z,pz+s*x+c*z
local dx,dz=xx-self.pos.x,zz-self.pos.z
a=AngleBetween(vx,vz,dx,dz)
minA=min(minA,a)
maxA=max(maxA,a)
end
if testing then print("min,max=",minA,maxA) end
return minA,maxA
end
function Tank:rotate(a,NoLimit,ignoreTarget)
if ignoreTarget then self.angleTarget=self.angle+a
else self.angleTarget=self.angleTarget+a end
if NoLimit then
self.angle=self.angleTarget
else
self.angle=self.angle+Limit(self.angleTarget-self.angle,Tank.turnRate)
end
self.rad=math.rad(self.angle)
--calculate our direction vector
self.dir=vec3(cos(self.rad),0,-sin(self.rad))
end
function Tank:rotateTurret(a)
self.turretAngle=self.turretAngle+a*self.rotationStatus
end
function Tank:rotateBarrel(a)
self.barrelAngle=self.barrelAngle+a
end
function Tank:SetAim()
local m=matrix(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1)
m=m:translate(self.pos:unpack())
m=m:rotate(self.angle,0,1,0)
m=m:rotate(self.turretAngle,0,1,0)
m=m:translate(self.startOfBarrel:unpack())
local b1=vec3(m[13],m[14],m[15])
m=m:rotate(-self.gunAngle,0,0,1)
--calculate position and angle of end of barrel
m=m:translate(self.endOfBarrel:unpack())
local b2=vec3(m[13],m[14],m[15])
local a=(b2-b1):normalize()
self.muzzlePos,self.muzzleAngle=b2,a
return b1,b2,a
end
function Tank:Shoot()
if not self:ready() then return end
AddShot(self.muzzlePos,self.muzzleAngle,self.muzzleSpeed)
self.lastShot=ElapsedTime
end
function Tank:ready()
return ElapsedTime>self.lastShot+Tank.reloadTime
end
function Tank:Wobble(speed)
if speed==0 then return end
local f,e=math.abs(1.5*speed/60),ElapsedTime+self.wobble
local a,b=e,e*math.pi
rotate(10*noise(a,b)*f,1,0,0)
rotate(3*noise(b,a)*f,0,0,1)
end
--[[
function Tank.LoadTextures()
Tank.Shot=MakeShell()
Tank.texBody=readImage("Dropbox:Tank_Body")
if not Tank.texBody then
local w,h,f=400,400,5
local img1=image(w,h)
for i=1,w do
for j=1,h do
local m=(noise(i/w*f,j/h*f)+1)/2
img1:set(i,j,Tank.colors[1]:mix(Tank.colors[2],m))
end
end
saveImage("Dropbox:Tank_Body",img1)
Tank.texBody=img1
end
--Track-------
Tank.texTrack=readImage("Dropbox:Tank_Track")
if not Tank.texTrack then
local img=image(50,5)
setContext(img)
pushStyle()
noSmooth()
background(123, 122, 122, 255)
stroke(25)
strokeWidth(0.5)
for i=1,img.width do
line(i,0,i,5)
end
popStyle()
setContext()
saveImage("Dropbox:Tank_Track",img)
Tank.texTrack=img
end
--Wheel-------
Tank.texWheel=readImage("Dropbox:Tank_Wheel")
if not Tank.texWheel then
img=image(100,10)
local col=Tank.colors[1]*2/3+Tank.colors[2]/3 col.a=255
print(col)
setContext(img)
background(0)
pushStyle()
noSmooth()
fill(col)
stroke(Tank.colors[2])
strokeWidth(2)
for i=1,4 do
ellipse(i*26-11,10,20)
end
fill(Tank.colors[2])
for i=1,4 do
ellipse(i*26-11,10,4)
end
fill(col)
for i=1,3 do
ellipse(2+i*26,9,20)
end
fill(Tank.colors[2])
for i=1,4 do
ellipse(2+i*26,9,4)
end
popStyle()
setContext()
saveImage("Dropbox:Tank_Wheel",img)
Tank.texWheel=img
end
end
--]]
function Tank.CreateModel()
local x1,x2,x3,y1,y2,y3,z1,z2,z3,z4,f1,f2
--body of tank
local body=mesh()
local v,t={},{}
x1,x2,y,z1,z2=-8.25,8.25,4.5,3.5,-3.5 --deck
v[#v+1]=vec3(x1,y,z1) v[#v+1]=vec3(x2,y,z1) v[#v+1]=vec3(x2,y,z2)
v[#v+1]=vec3(x2,y,z2) v[#v+1]=vec3(x1,y,z2) v[#v+1]=vec3(x1,y,z1)
x,y1,y2,z1,z2=8.25,2,4.5,3.5,-3.5 --back
v[#v+1]=vec3(x,y1,z1) v[#v+1]=vec3(x,y1,z2) v[#v+1]=vec3(x,y2,z2)
v[#v+1]=vec3(x,y2,z2) v[#v+1]=vec3(x,y2,z1) v[#v+1]=vec3(x,y1,z1)
x1,x2,y1,y2,z1,z2,z3,z4=-11,-8.25,3,4.5,-4.5,4.5,-3.5,3.5 --front #2 angle
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z4)
v[#v+1]=vec3(x2,y2,z4) v[#v+1]=vec3(x2,y2,z3) v[#v+1]=vec3(x1,y1,z1)
x1,x2,y1,y2,y3,z1,z2=-8.25,8.25,2,2,3.5,4.5,-4.5 --sides
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x2,y3,z1)
v[#v+1]=vec3(x2,y3,z1) v[#v+1]=vec3(x1,y3,z1) v[#v+1]=vec3(x1,y1,z1)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x1,y3,z2)
v[#v+1]=vec3(x1,y3,z2) v[#v+1]=vec3(x2,y3,z2) v[#v+1]=vec3(x2,y2,z2)
x1,x2,y1,y2,z1,z2=-8.25,8.25,3.5,4.5,4.5,3.5 --sloping sides
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y1,z1) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x1,y2,z2) v[#v+1]=vec3(x1,y1,z1)
x1,x2,y1,y2,z1,z2=8.25,-8.25,3.5,4.5,-4.5,-3.5
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y1,z1) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x1,y2,z2) v[#v+1]=vec3(x1,y1,z1)
x1,x2,y1,z1,z2=-10,7.25,1,-2.5,2.5 --underneath
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y1,z1) v[#v+1]=vec3(x2,y1,z2)
v[#v+1]=vec3(x2,y1,z2) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x1,y1,z1)
x,y1,y2,z1,z2=-11,2,3,-4.5,4.5 --front bottom
v[#v+1]=vec3(x,y1,z1) v[#v+1]=vec3(x,y1,z2) v[#v+1]=vec3(x,y2,z2)
v[#v+1]=vec3(x,y2,z2) v[#v+1]=vec3(x,y2,z1) v[#v+1]=vec3(x,y1,z1)
x1,x2,y1,y2,z1,z2=-10,-11,1,2,-2.5,2.5 --front under slant
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y1,z1)
x1,x2,y1,y2,z1,z2=7.25,8.25,1,2,2.5,-2.5 --back under slant
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y1,z1)
x1,x2,x3,y1,y2,y3,z1,z2=-11,-8.25,-8.25,3,2,3.5,4.5,3.5 --fill the cracks
v[#v+1]=vec3(x,y1,z1) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x3,y3,z1)
z1=-4.5
v[#v+1]=vec3(x,y1,z1) v[#v+1]=vec3(x3,y3,z1) v[#v+1]=vec3(x2,y2,z1)
x1,x2,x3,y1,y2,y3,z1=-11,-11,-8.25,3,2,2,4.5
v[#v+1]=vec3(x,y1,z1) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x3,y3,z1)
z1=-4.5
v[#v+1]=vec3(x,y1,z1) v[#v+1]=vec3(x3,y3,z1) v[#v+1]=vec3(x2,y2,z1)
x1,x2,y1,y2,y3,z1,z2=-11,-8.25,3,3.5,4.5,4.5,3.5
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x2,y3,z2)
v[#v+1]=vec3(x1,y1,-z1) v[#v+1]=vec3(x2,y3,-z2) v[#v+1]=vec3(x2,y2,-z1)
x1,y1,y2,z1,z2=8.25,5.5,6.5,3.5,-3.5
x1,y1,y2,z1,z2=8.25,2,3.5,4.5,3.5
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x1,y2,z2)
v[#v+1]=vec3(x1,y2,z2) v[#v+1]=vec3(x1,y2,z1) v[#v+1]=vec3(x1,y1,z1)
v[#v+1]=vec3(x1,y1,-z1) v[#v+1]=vec3(x1,y2,-z2) v[#v+1]=vec3(x1,y1,-z2)
v[#v+1]=vec3(x1,y2,-z2) v[#v+1]=vec3(x1,y1,-z1) v[#v+1]=vec3(x1,y2,-z1)
x1,y1,y2,z1,z2=8.25,3.5,4.5,4.5,3.5
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x1,y2,z2)
z1,z2=-3.5,-4.5
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x1,y2,z1)
body.vertices=v
body.texture=Tank.texBody
body.name="body"
local testBody={v}
--tracks
local track,v,t=mesh(),{},{}
local f=.05
x1,x2,y1,y2,z1,z2,f1,f2=8.25,8.25,1,2,4.5,2.5,0,f*2 --back right vert
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y1,z1)
t[#t+1]=vec2(f1,0) t[#t+1]=vec2(f1,1) t[#t+1]=vec2(f2,1)
t[#t+1]=vec2(f2,1) t[#t+1]=vec2(f2,0) t[#t+1]=vec2(f1,0)
z1,z2=-2.5,-4.5 --back left vert
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y1,z1)
t[#t+1]=vec2(f1,0) t[#t+1]=vec2(f1,1) t[#t+1]=vec2(f2,1)
t[#t+1]=vec2(f2,1) t[#t+1]=vec2(f2,0) t[#t+1]=vec2(f1,0)
x1,x2,y1,y2,z1,z2,f1,f2=7.25,8.25,0,1,4.5,2.5,f*2,f*4 --back right slant
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y1,z1)
t[#t+1]=vec2(f1,0) t[#t+1]=vec2(f1,1) t[#t+1]=vec2(f2,1)
t[#t+1]=vec2(f2,1) t[#t+1]=vec2(f2,0) t[#t+1]=vec2(f1,0)
z1,z2=-2.5,-4.5 --back left slant
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y1,z1)
t[#t+1]=vec2(f1,0) t[#t+1]=vec2(f1,1) t[#t+1]=vec2(f2,1)
t[#t+1]=vec2(f2,1) t[#t+1]=vec2(f2,0) t[#t+1]=vec2(f1,0)
x1,x2,y1,y2,z1,z2,f1,f2=-9,-11,0,2,2.5,4.5,0,f*2 --front right slant
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y1,z1)
t[#t+1]=vec2(f2,0) t[#t+1]=vec2(f2,1) t[#t+1]=vec2(f1,1)
t[#t+1]=vec2(f1,1) t[#t+1]=vec2(f1,0) t[#t+1]=vec2(f2,0)
z1,z2=-4.5,-2.5 --front left slant
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y2,z2)
v[#v+1]=vec3(x2,y2,z2) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y1,z1)
t[#t+1]=vec2(f2,0) t[#t+1]=vec2(f2,1) t[#t+1]=vec2(f1,1)
t[#t+1]=vec2(f1,1) t[#t+1]=vec2(f1,0) t[#t+1]=vec2(f2,0)
x1,x2,y1,y2,z1,z2,f1,f2=-9,7.25,0,0,4.5,2.5,4*f,20.25*f --bottom right track
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y1,z2)
v[#v+1]=vec3(x2,y1,z2) v[#v+1]=vec3(x2,y1,z1) v[#v+1]=vec3(x1,y1,z1)
t[#t+1]=vec2(f1,0) t[#t+1]=vec2(f1,1) t[#t+1]=vec2(f2,1)
t[#t+1]=vec2(f2,1) t[#t+1]=vec2(f2,0) t[#t+1]=vec2(f1,0)
z1,z2=-2.5,-4.5 --bottom left track
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y1,z2) v[#v+1]=vec3(x2,y1,z2)
v[#v+1]=vec3(x2,y1,z2) v[#v+1]=vec3(x2,y1,z1) v[#v+1]=vec3(x1,y1,z1)
t[#t+1]=vec2(f1,0) t[#t+1]=vec2(f1,1) t[#t+1]=vec2(f2,1)
t[#t+1]=vec2(f2,1) t[#t+1]=vec2(f2,0) t[#t+1]=vec2(f1,0)
track.vertices=v
track.texCoords=t
track.texture=Tank.texTrack
track.name="track"
wheels,v,t=mesh(),{},{}
x1,x2,y1,y2,z1=-11,-9,2,0,4
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x2,y1,z1)
x1,x2,y1,y2,z1=-9,7.25,2,0,4
v[#v+1]=vec3(x1,y2,z1) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x2,y1,z1)
v[#v+1]=vec3(x2,y1,z1) v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y2,z1)
x1,x2,y1,y2,y3,z1=7.25,8.25,0,1,2,4
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y3,z1)
v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x2,y3,z1) v[#v+1]=vec3(x1,y3,z1)
x1,x2,y1,y2,z1=-11,-9,2,0,-4
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y1,z1) v[#v+1]=vec3(x2,y2,z1)
x1,x2,y1,y2,z1=-9,7.25,2,0,-4
v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y2,z1) v[#v+1]=vec3(x1,y1,z1)
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x2,y1,z1) v[#v+1]=vec3(x2,y2,z1)
x1,x2,y1,y2,y3,z1=7.25,8.25,0,1,2,-4
v[#v+1]=vec3(x1,y1,z1) v[#v+1]=vec3(x1,y3,z1) v[#v+1]=vec3(x2,y2,z1)
v[#v+1]=vec3(x2,y2,z1) v[#v+1]=vec3(x1,y3,z1) v[#v+1]=vec3(x2,y3,z1)
for i=1,#v/2 do t[#t+1]=vec2((v[i].x+11)/19,v[i].y/2) end
for i=#v/2+1,#v do t[#t+1]=vec2(-(8.25-v[i].x)/19,v[i].y/2) end
wheels.vertices=v
wheels.texCoords=t
wheels.texture=Tank.texWheel
wheels.name="wheels"
local turret,v=mesh(),{}
local h,y,slope=2.5,4.5,0.5
local p={vec3(-4,0,1.5),vec3(0,0,3.5),vec3(2,0,3),vec3(4,0,1),vec3(4,0,-1),
vec3(2,0,-3),vec3(0,0,-3.5),vec3(-4,0,-1.5)}
for i=1,#p do p[i].y=y+(p[i].x/8+0.5)*slope end
for i=1,#p do
local a,b=p[i],p[i+1] or p[1]
v[#v+1]=vec3(a.x,y,a.z)
v[#v+1]=vec3(b.x,y,b.z)
v[#v+1]=vec3(b.x,b.y+h,b.z)
v[#v+1]=vec3(b.x,b.y+h,b.z)
v[#v+1]=vec3(a.x,a.y+h,a.z)
v[#v+1]=vec3(a.x,y,a.z)
end
local norm={}
for i=1,#v do norm[i]=v[i]:normalize() end
--roof
for i=1,#p do
local a,b=p[i],p[i+1] or p[1]
v[#v+1]=vec3(a.x,a.y+h,a.z)
v[#v+1]=vec3(b.x,b.y+h,b.z)
v[#v+1]=vec3(0,y+h+slope/2,0)
for j=1,3 do norm[#norm+1]=vec3(0,1,0) end
end
turret.vertices=v
--turret.normals=norm
turret.texture=Tank.texBody
turret.name="turret"
local testTurret={v}
barrel,p,v,norm=mesh(),{},{},{}
local x1,x2,r1,r2,y=-4,-7,0.75,0.4,7.75
for i=1,8 do p[i]=vec3(0,math.cos(i*math.pi/4),math.sin(i*math.pi/4)) end
for i=1,8 do
local p1,p2=p[i],p[i+1] or p[1]
v[#v+1]=vec3(x1,y+p1.y*r1,p1.z*r1)
v[#v+1]=vec3(x2,y+p1.y*r2,p1.z*r2)
v[#v+1]=vec3(x2,y+p2.y*r2,p2.z*r2)
v[#v+1]=vec3(x2,y+p2.y*r2,p2.z*r2)
v[#v+1]=vec3(x1,y+p2.y*r1,p2.z*r1)
v[#v+1]=vec3(x1,y+p1.y*r1,p1.z*r1)
end
local x1,x2,r1,r2,y=-7,-17,0.4,0.3,7.75
for i=1,8 do p[i]=vec3(0,math.cos(i*math.pi/4),math.sin(i*math.pi/4)) end
for i=1,8 do
local p1,p2=p[i],p[i+1] or p[1]
v[#v+1]=vec3(x1,y+p1.y*r1,p1.z*r1)
v[#v+1]=vec3(x2,y+p1.y*r2,p1.z*r2)
v[#v+1]=vec3(x2,y+p2.y*r2,p2.z*r2)
v[#v+1]=vec3(x2,y+p2.y*r2,p2.z*r2)
v[#v+1]=vec3(x1,y+p2.y*r1,p2.z*r1)
v[#v+1]=vec3(x1,y+p1.y*r1,p1.z*r1)
end
for i=1,#v do norm[i]=(v[i]-vec3(v[i].x,y,0)):normalize() end
for i=1,#v do v[i].x,v[i].y=v[i].x+4,v[i].y-7.75 end
barrel.vertices=v
barrel.normals=norm
barrel.texture=Tank.texBody
barrel.name="barrel"
local tank={body=body,track=track,turret=turret,barrel=barrel,wheels=wheels}
for _,m in pairs(tank) do
local norm={}
local nn=m:buffer("normal")
if nn[1]==nil then
local v=m:buffer("position")
for i=1,m.size,3 do
local v1,v2=v[i]-v[i+2],v[i+1]-v[i+2]
local n=(v1:cross(v2)):normalize()
norm[i],norm[i+1],norm[i+2]=n,n,n
end
m.normals=norm
end
if m.texture==Tank.texBody then
local t={}
local v=m:buffer("position")
for i=1,m.size do
t[i]=vec2(v[i].x/8+v[i].y/5,v[i].z/6+v[i].y/9)/2
end
m.texCoords=t
end
m=SetLighting(m)
m.shader.offset2=0
end
--make tables for testing of penetration and damage
Tank.Damage={Speed=1,Rotation=2,Ammo=3,Accuracy=4,Visibility=5}
Tank.DamageList={"Speed","Rotation","Ammo","Accuracy","Visibility"}
Tank.model=tank
local vert,norm,armor,damage={},{},{},{}
local v,n=body:buffer("position"),body:buffer("normal")
for i=1,body.size do
vert[i]=v[i]
norm[i]=n[i]
if norm[i]:dot(vec3(-1,0,0))>0 then
armor[i]=40
damage[i]=Tank.Damage.Visibility
else
armor[i]=20
if norm[i]:dot(vec3(1,0,0))>0 then damage[i]=Tank.Damage.Speed
elseif norm[i]:dot(vec3(0,0,-1))>0 then damage[i]=Tank.Damage.Ammo
else damage[i]=Tank.Damage.Accuracy end
end
end
local testBody={vert,norm,armor,damage}
local vert,norm,armor,damage={},{},{},{}
local v,n=turret:buffer("position"),turret:buffer("normal")
for i=1,turret.size do vert[i]=v[i] norm[i]=n[i] armor[i]=40 end
for i=1,turret.size do damage[i]=Tank.Damage.Rotation end
local testTurret={vert,norm,armor,damage}
--create bounding box for tracks for penetration testing
local b=CreateBlock(19,2,2)
local v=b:buffer("position")
local vert,norm,armor,damage={},{},{},{}
for i=1,b.size do vert[#vert+1]=v[i]+vec3(0,1,-3.5) norm[#norm+1]=vec3(0,0,-1) end
for i=1,b.size do vert[#vert+1]=v[i]+vec3(0,1, 3.5) norm[#norm+1]=vec3(0,0, 1) end
for i=1,#vert do armor[i]=20 end
for i=1,#vert do damage[i]=Tank.Damage.Speed end
local testTrack={vert,norm,armor,damage}
local corners={vec3(-11,0,-4.5),vec3(-11,0,4.5),vec3(8.25,0,-4.5),vec3(-8.25,0,4.5)}
Tank.Test={track=testTrack,turret=testTurret,body=testBody,corners=corners}
end
function Tank:PenetrationTest(pos,dir)
hitText=""
local min,h,a,ang,dam,nam=9999
local v=Tank.Test.body
local name="body"
for i=1,#v[1],3 do
local d,p=LineIntersectsTriangle(pos,dir,v[1][i],v[1][i+1],v[1][i+2],v[2][i])
if d and d<min then
ang=(-dir):dot(v[2][i])
min,h,a,dam,nam=d,p,v[3][i],v[4][i],name
end
end
local v=Tank.Test.turret
local name="turret"
for i=1,#v[1],3 do
local d,p=LineIntersectsTriangle(pos,dir,v[1][i],v[1][i+1],v[1][i+2],v[2][i])
if d and d<min then
ang=(-dir):dot(v[2][i])
min,h,a,dam,nam=d,p,v[3][i],v[4][i],name
end
end
local v=Tank.Test.track
local name="track"
for i=1,#v[1],3 do
local d,p=LineIntersectsTriangle(pos,dir,v[1][i],v[1][i+1],v[1][i+2],v[2][i])
if d and d<min then
ang=(-dir):dot(v[2][i])
min,h,a,dam,nam=d,p,v[3][i],v[4][i],name
end
end
if h then self:CalcDamage(min,h,a,ang,dam,nam) end
return min,h,a,ang,dam,nam
end
function Tank:CalcDamage(min,h,a,ang,dam,nam)
if ang<Tank.bounceDot then hitText="Bounce" return end
local f=(1-Tank.damageReductionWithDistance)*ang
if Tank.shotForce*f<a then hitText="No penetration" return end
local d=0.25+0.75*math.random()
self.health=self.health-d/2
if self.health<0 then self:Die() return
elseif dam==Tank.Damage.Speed then self.speedStatus=math.max(0,self.speedStatus-d)
elseif dam==Tank.Damage.Rotation then self.rotationStatus=math.max(0,self.rotationStatus-d)
elseif dam==Tank.Damage.Ammo then self.ammoStatus=math.max(0,self.ammoStatus-d)
elseif dam==Tank.Damage.Accuracy then self.aimStatus=math.max(0,self.aimStatus-d)
elseif dam==Tank.Damage.Visibility then self.visStatus=math.max(0,self.visStatus-d)
end
local f=function(a) return string.format("%.f%%", 100*a) end
hitText="health="..f(self.health).."\nspeed="..f(self.speedStatus).."\nrotate="..f(self.rotationStatus)..
"\nammo="..f(self.ammoStatus).."\naim="..f(self.aimStatus).."\nvis="..f(self.visStatus)
end
function Tank:Die()
T[self]=nil
hitText="Tank died"
end
--# Terrain
--Terrain2
local atan,rand,min,max,cos=math.atan,math.random,math.min,math.max,math.cos
local rad=math.pi/180
Terrain={}
function MakeTerrain(w,d,pic,f,t)
local w,d,img,f=w or 1000,d or 1000,pic or grassImage,f or .02
local floor=mesh()
--local img=readImage(pic)
floor.texture=img
local wn,dn=w/img.width/f,d/img.height/f
floor.vertices={vec3(0,0,0),vec3(w,0,0),vec3(w,0,-d),vec3(w,0,-d),vec3(0,0,-d),vec3(0,0,0)}
floor.texCoords={vec2(0,0),vec2(wn,0),vec2(wn,dn),vec2(wn,dn),vec2(0,dn),vec2(0,0)}
floor=SetLighting(floor,TileShader)
tileSize=t or 40
local trees={}
treesID={}
for i=1,200 do
trees[i]=AddBillboard(i,0,0,w,-d,treeImages,0.25)
treesID[i]=trees[i]
end
treeTiles=AllocateTrees(trees,tileSize)
Terrain={floor=floor,trees=trees}
end
local allocSeq={{-1,0},{0,0},{1,0},{0,-1},{0,1}}
function AllocateTrees(t,tileSize) --objects need a vec3 pos property and r(adius) value
local L={}
for p=1,#t do
local b=t[p]
local a={} --prevents dups
for u=1,#allocSeq do
local i,j=allocSeq[u][1],allocSeq[u][2]
local x,z=(b.pos.x+i*b.r)//tileSize,(-b.pos.z+j*b.r)//tileSize
L[x]=L[x] or {}
L[x][z]=L[x][z] or {}
local tt=L[x][z]
if not a[tt] then
a[tt]=tt
tt[#tt+1]=b.id
end
end
end
return L
end
function LoadImages()
local img=readImage("Dropbox:TankSheet")
treeImages={img:copy(409,287,211,141),img:copy(432,169,139,104),img:copy(427,54,140,107),
img:copy(641,219,188,175),img:copy(587,57,141,104)}
Tank.texBody=img:copy(1,35,400,398)
setContext(Tank.texBody)
fill(198, 85, 108, 100)
rect(0,0,400,400)
setContext()
Tank.texWheel=img:copy(1,9,200,19)
setContext(Tank.texWheel)
fill(198, 85, 108, 75)
rect(0,0,200,19)
setContext()
Tank.texTrack=img:copy(210,9,100,9)
grassImage=img:copy(849,68,342,341)
end
function AddBillboard(id,x1,z1,x2,z2,imgList,f)
local i=rand(1,#imgList)
local h=0.5+rand()
local w=imgList[i].width*h*f --width
local pos=vec3(rand(x1,x2),imgList[i].height*f/2*h,rand(min(z1,z2),max(z1,z2)))
local m=mesh()
m:addRect(0,0,w,imgList[i].height*h*f)
m.texture=imgList[i]
m.shader=shader(TreeShader.v,TreeShader.f)
m.shader.mistColor=backgroundColor --zzz
return {m=m,pos=pos,r=w/2,id=id}
end
function DrawTerrain()
Terrain.floor.shader.camPos=camPos
Terrain.floor:draw()
DrawBillboards(Terrain.trees,camPos)
end
function LookAt(source,target)
local z=(source-target):normalize()
local x=(vec3(0,1,0):cross(z)):normalize()
local y=(z:cross(x)):normalize()
return matrix(x.x,x.y,x.z,0, y.x,y.y,y.z,0, z.x,z.y,z.z,0, source.x,source.y,source.z,1)
end
function GetScreenRect(t1,t2,s) --t1 is camera, t2 is target, s is vec3 size of object
--get direction and distance
local v=(t2.pos-t1.pos)
local d=v:len()-u/2 --position of t2 is assumed to be its centre, so adjust by 1\2 depth
v=v/d
--get drawing rectangle
local w = u*WIDTH*projectionMatrix()[1]/d/2
local h = s.y*HEIGHT*projectionMatrix()[6]/d/2
--draw our tank
local m = modelMatrix()*viewMatrix()*projectionMatrix()
local X,Y=(m[13]/m[16]+1)*WIDTH/2, (m[14]/m[16]+1)*HEIGHT/2
return X-w/2,Y-h/2,w,h
end
--local testing1=true
function DrawBillboards(billboards)
local PM1=projectionMatrix()[1]
for _,b in pairs(billboards) do
b.pov=IsVisible(b.pos,camPos,camDir,b.r,sinFOV)
b.visibility=max(0,1-b.pos:dist(camPos)/fogRange)
end
--calc distance to camera and sort from furthest to nearest, every two seconds
if ElapsedTime>(lastSort or -9) +2 then
table.sort(billboards,function(a,b) return a.visibility<b.visibility end)
lastSort=ElapsedTime
end
for i=1,#billboards do
local b=billboards[i]
if b.pov and b.visibility>0 then
pushMatrix()
--move to the centre of our image
local p=b.pos
translate(p:unpack())
--calculate difference in x and z
local dx,dz=p.x-camPos.x,-p.z+camPos.z
--calculate angle we need to turn
local ang=atan(dx/-dz)/rad
if b.lastAng and math.abs(ang-b.lastAng)>90 and ElapsedTime<b.lastDraw+1 then ang=ang+180 end
b.lastAng=ang
b.lastDraw=ElapsedTime
--turn and draw
rotate(ang,0,1,0)
b.m.shader.visibility=b.visibility -- *(b.occ or 1)
b.m:draw()
popMatrix()
end
end
--testing1=false
end
--# Controls
--joystick
JoyStick = class()
local tick=60
--Note all the options you can set below. Pass them through in a named table
function JoyStick:init(t)
t = t or {}
self.radius = t.radius or 100 --size of joystick on screen
self.stick = t.stick or 30 --size of inner circle
self.centre = t.centre or self.radius * vec2(1,1) + vec2(5,5)
self.damp=vec2(0.2,0.2)
self.position = vec2(0,0) --initial position of inner circle
self.target = vec2(0,0) --current position of inner circle (used when we interpolate movement)
self.value = vec2(0,0)
self.delta = vec2(0,0)
self.mspeed = 30
self.moving = 0
end
function JoyStick:draw()
ortho()
viewMatrix(matrix())
pushStyle()
fill(160, 182, 191, 1)
stroke(0,0,0,50)
strokeWidth(3)
ellipse(self.centre.x,self.centre.y,2*self.radius)
fill(78, 131, 153, 1)
ellipse(self.centre.x+self.position.x, self.centre.y+self.position.y, self.stick*2)
popStyle()
end
function JoyStick:touched(t)
if t.state == BEGAN then
local v = vec2(t.x,t.y)
if v:dist(self.centre)<self.radius-self.stick then
self.touch = true
--else return false
end
end
if self.touch then
if t.state~=ENDED then
local v = vec2(t.x,t.y)
if v:dist(self.centre)>self.radius-self.stick then
v = (v - self.centre):normalize()*(self.radius - self.stick) + self.centre
end --set x,y values for joy based on touch
self.target=v - self.centre
else --reset joystick to centre when touch ends
self.target=vec2(0,0)
self.touch = false
end
else return false
end
return true
end
function JoyStick:update()
local p = self.target - self.position
if p:len() < tick * self.mspeed then
self.position = self.target
if not self.touch then
if self.moving ~= 0 then
self.moving = self.moving - 1
end
else
self.moving = 2
end
else
self.position = self.position + p:normalize() * tick * self.mspeed
self.moving = 2
end
local v=self.position/(self.radius - self.stick)
return self:Dampen(v)
end
function JoyStick:Dampen(v)
if not self.damp then return v end
if v.x>0 then v.x=math.max(0,(v.x-self.damp.x)/(1-self.damp.x))
else v.x=math.min(0,(v.x+self.damp.x)/(1-self.damp.x)) end
if v.y>0 then v.y=math.max(0,(v.y-self.damp.y)/(1-self.damp.y))
else v.y=math.min(0,(v.y+self.damp.y)/(1-self.damp.y)) end
return v
end
function JoyStick:isMoving()
return self.moving
end
function JoyStick:isTouched()
return self.touch
end
--# Utility
--Utility
function CreateBlock(w,h,d,col,pos,tex,ms) --width,height,depth,colour,position,texture
pos=pos or vec3(0,0,0)
local x,X,y,Y,z,Z=pos.x-w/2,pos.x+w/2,pos.y-h/2,pos.y+h/2,pos.z-d/2,pos.z+d/2
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)}
local vert={v[1],v[2],v[3],v[1],v[3],v[4],v[2],v[6],v[7],v[2],v[7],v[3],v[6],v[5],v[8],v[6],v[8],v[7],
v[5],v[1],v[4],v[5],v[4],v[8],v[4],v[3],v[7],v[4],v[7],v[8],v[5],v[6],v[2],v[5],v[2],v[1]}
local texCoords
if tex then
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)}
texCoords={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]}
end
local n={vec3(0,0,1),vec3(1,0,0),vec3(0,0,-1),vec3(-1,0,0),vec3(0,1,0),vec3(0,-1,0)}
local norm={}
for i=1,6 do for j=1,6 do norm[#norm+1]=n[i] end end
if not ms then ms=mesh() end
if ms.size==0 then
ms.vertices=vert
ms.normals=norm
if tex then ms.texture,ms.texCoords=tex,texCoords end
else
for i=1,#vert do
table.insert(ms.vertices,vert[i])
table.insert(ms.normals,norm[i])
if tex then table.insert(ms.texCoords,texCoords[i]) end
end
end
ms:setColors(col or color(255))
return ms
end
function AddBlock(w,h,d,col,pos,rot,texture,vertices,texCoords,normals,colors) --width,height,depth,colour,position,texture
local x,X,y,Y,z,Z=-w/2,w/2,-h/2,h/2,-d/2,d/2
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)}
local vert={v[1],v[2],v[3],v[1],v[3],v[4],v[2],v[6],v[7],v[2],v[7],v[3],v[6],v[5],v[8],v[6],v[8],v[7],
v[5],v[1],v[4],v[5],v[4],v[8],v[4],v[3],v[7],v[4],v[7],v[8],v[5],v[6],v[2],v[5],v[2],v[1]}
if rot then
m=modelMatrix()
m=m:translate(pos:unpack())
m=m:rotate(rot.x,1,0,0) m=m:rotate(r.y,0,1,0) m=m:rotate(r.z,0,0,1)
for i=1,#vert do vertices[#vertices+1]=m*vert[i] end
else
for i=1,#vert do vertices[#vertices+1]=pos+vert[i] end
end
local tex
if texture then
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)}
tex={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]}
for i=1,#tex do texCoords[#texCoords+1]=tex[i] end
end
local n={vec3(0,0,1),vec3(1,0,0),vec3(0,0,-1),vec3(-1,0,0),vec3(0,1,0),vec3(0,-1,0)}
for i=1,6 do
for j=1,6 do
normals[#normals+1]=n[i]
colors[#colors+1]=col
end
end
return vertices,texCoords,normals,colors
end
--o is start of line, d is normalised line direction
--v0, v1, v2 are triangle vertices
function LineIntersectsTriangle(o,d,v1,v2,v3,n)
if d:dot(n)>0 then return end
local e1,e2=v2-v1,v3-v1
local p=d:cross(e2)
local det=e1:dot(p)
if math.abs(det)<0.001 then return end
local inv_det=1/det
local t=o-v1
local u=t:dot(p)*inv_det
if u<0 or u>1 then return end
local q=t:cross(e1)
local v=d:dot(q)*inv_det
if v<0 or u+v>1 then return end
local a=e2:dot(q)*inv_det
if a>0 then return a,o+d*a end
end
function LookAtMatrix(source,target,up)
local Z=(source-target):normalize()
up=up or vec3(0,1,0)
local X=(up:cross(Z)):normalize()
local Y=(Z:cross(X)):normalize()
return matrix(X.x,X.y,X.z,0,Y.x,Y.y,Y.z,0,Z.x,Z.y,Z.z,0,source.x,source.y,source.z,1)
end
function RotateVector(v,y,z)
local m=matrix(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1)
if y then m=m:rotate(y,0,1,0) end
if z then m=m:rotate(z,0,0,1) end
return m*v,m
end
function Limit(a,L)
if a>0 then return math.min(a,L) else return -math.min(-a,L) end
end
function lineDistB(p,a,b)
return (p-a):dist(math.max(math.min((p-a):dot(b-a)/(b-a):lenSqr(),1),0)*(b-a))
end
function DistancePointToLine(p,a,b)
b=p+(b-a)*5000
return (p-a):dist(math.max(math.min((p-a):dot(b-a)/(b-a):lenSqr(),1),0)*(b-a))
end
local atan2=math.atan2
function GetTangentAngles(centre,radius,camPos,targetPos)
local cx,cz,cpx,cpz=centre.x,centre.z,camPos.x,camPos.z
--calculate offset from centre
local ax,az=cx-cpx,cz-cpz
local d=radius*(ax*ax+az*az)^-0.5
local dx,dz=ax*d,az*d --print("dx,dz",dx,dz)
local t1,t2=vec3(cx-dz,0,cz+dx),vec3(cx+dz,0,cz-dx) --print("t1",t1) print("t2",t2)
local d1,d2=t1-camPos,t2-camPos
local p=targetPos-camPos
--local ta1,ta2=atan2(-d1.z,d1.x),atan2(-d2.z,d2.x)
local ta1,ta2=AngleBetweenVec(p,d1),AngleBetweenVec(p,d2)
if ta2<ta1 then ta1,ta2=ta2,ta1 end
return ta1,ta2
end
function AngleBetween(x1,y1,x2,y2)
return atan2( x1*y2 - y1*x2, x1*x2 + y1*y2 )
end
function AngleBetweenVec(v1,v2)
return atan2( v1.x*v2.z - v1.z*v2.x, v1.x*v2.x + v1.z*v2.z )
end
--# Shaders
--Shaders
TankFogShader={
v=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform vec4 directColor;
uniform vec4 directDirection;
uniform vec4 ambientColor;
uniform float offset;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = vec2(texCoord.x+offset,texCoord.y);
vec4 norm = normalize(mModel * vec4( normal, 0.0 ));
float diffuse = max( 0.0, dot( norm, directDirection ));
vPosition = mModel*position;
vColor = diffuse * directColor + ambientColor;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform float offset2;
uniform vec4 mistColor;
uniform float fog;
uniform vec3 camPos;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
vec4 col = texture2D(texture, vec2(mod(vTexCoord.x+offset2,1.0),mod(vTexCoord.y,1.0)))*vColor;
float f = clamp(distance(vPosition.xyz,camPos)/fog,0.0,1.0);
col = mix( col, mistColor, f);
col.a=1.0;
gl_FragColor=col;
}
]]}
TankShader={
v=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform vec4 directColor;
uniform vec4 directDirection;
uniform vec4 ambientColor;
uniform vec4 teamColour;
uniform float offset;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = vec2(texCoord.x+offset,texCoord.y);
vec4 norm = normalize(mModel * vec4( normal, 0.0 ));
float diffuse = max( 0.0, dot( norm, directDirection ));
vPosition = mModel*position;
vColor = mix(diffuse * directColor + ambientColor,teamColour,teamColour.a);
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform float offset2;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
vec4 col = texture2D(texture, vec2(mod(vTexCoord.x+offset2,1.0),mod(vTexCoord.y,1.0)))*vColor;
col.a=1.0;
gl_FragColor=col;
}
]]}
TreeShader={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying highp vec2 vTexCoord;
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = texCoord;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform vec4 mistColor;
uniform float visibility;
varying highp vec2 vTexCoord;
void main()
{
vec4 col = texture2D(texture,vTexCoord);
if (col.a<0.05) discard;
else {
col=mix(mistColor,col,visibility);
gl_FragColor=col;
}
}
]]}
LightingShader={
v=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform vec4 directColor;
uniform vec4 directDirection;
uniform vec4 ambientColor;
uniform float fog;
uniform vec4 mistColor;
uniform vec3 camPos;
attribute vec4 position;
attribute vec4 color;
attribute vec3 normal;
varying lowp vec4 vColor;
void main()
{
gl_Position = modelViewProjection * position;
vec4 norm = normalize(mModel * vec4( normal, 0.0 ));
float diffuse = max( 0.0, dot( norm, directDirection ));
vec4 p=mModel*position;
float f = clamp(distance(p.xyz,camPos)/fog,0.0,1.0);
vColor = mix(color * ( diffuse * directColor + ambientColor ),mistColor,f);
vColor.a=1.0;
}
]],
f=[[
precision highp float;
varying lowp vec4 vColor;
void main()
{
gl_FragColor=vColor;
}
]]}
--Tile
TileShader = {
v = [[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
attribute vec4 position;
attribute vec2 texCoord;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
vTexCoord = texCoord;
vPosition=mModel*position;
gl_Position = modelViewProjection * position;
}
]],
f = [[
precision highp float;
uniform lowp sampler2D texture;
uniform float fog;
uniform vec4 mistColor;
uniform vec3 camPos;
varying highp vec2 vTexCoord;
varying highp vec4 vPosition;
void main()
{
lowp vec4 col = texture2D(texture, vec2(mod(vTexCoord.x,1.0),mod(vTexCoord.y,1.0)));
float f = clamp(distance(vPosition.xyz,camPos)/fog,0.0,1.0);
gl_FragColor = mix(col,mistColor,f);
}
]]}
--# Shot
--shot
ShotList={}
function AddShot(p,d,s)
table.insert(ShotList,{start=p,pos=p,dir=d,speed=s})
end
function DrawShots(Tanks)
for i,s in pairs(ShotList) do
local m=LookAtMatrix(s.pos,s.pos+s.dir)
pushMatrix()
modelMatrix(m)
Tank.Shot:draw()
popMatrix()
local newPos=s.pos+s.dir*(s.speed*DeltaTime)
for _,t in pairs(Tanks) do
--if t.pos:dist(newPos)<Tank.shotRadius or s.dir:dot(t.pos-newPos)<0 then
if DistancePointToLine(t.pos,s.pos,s.dir)<Tank.shotRadius and s.dir:dot(t.pos-newPos)<0 then
ShotList[i]=nil
local objPos,m=RotateVector((s.start-t.pos),-t.angle,0)
local objDir=m*s.dir
local d,h,a,ang,dam,nam=t:PenetrationTest(objPos,objDir)
if not ang then hitText="Shot missed!" end
--[[
if not ang then hitText="Shot missed!"
elseif ang<Tank.bounceDot then hitText="Shot bounced!"
else
local f=(1-d*Tank.damageReductionWithDistance)*ang
hitText=nam .. " hit!\n" ..
"distance: "..math.floor(d) .."\n" ..
"angle: "..math.floor(math.deg(math.acos(ang))) .. "\n"..
"adjusted power: "..string.format("%4.2f",f) .."\n" ..
"armour: ".. a .. "\n" ..
"damage to: "..Tank.DamageList[dam]
end
--]]
else
s.pos=newPos
end
end
end
end
--# Effects
--Effects
function MakeExplosion(s)
local sin,cos,rand,pi2=math.sin,math.cos,math.random,math.pi*2
local ss=s*1.5
local img=image(ss,ss)
pushMatrix()
setContext(img)
translate(ss/2,ss/2)
for i=1,1000 do
local f=i/2000
local m=0.4-0.2*f
local a,d,r=rand()*pi2,rand()*s*.5,rand()*s*m
local col=150+100*f
fill(color(col,col,col,50))
rotate(a)
ellipse(d*sin(a),d*cos(a),r)
end
setContext()
popMatrix()
local m=mesh()
local x1,y1,z,x2,y2=-s/2,-s/2,0,s/2,s/2
m.vertices={vec3(x1,y1,z),vec3(x2,y1,z),vec3(x2,y2,z),vec3(x2,y2,z),vec3(x1,y2,z),vec3(x1,y1,z)}
m.texture=img
m.texCoords={vec2(0,0),vec2(1,0),vec2(1,1),vec2(1,1),vec2(0,1),vec2(0,0)}
m:setColors(color(255))
m.shader=shader(TransparentFadeShader.v,TransparentFadeShader.f)
m.shader.a=1
return m,img
end
function MakeShell()
return CreateBlock(0.15,0.15,50,color(255,255,0,100),vec3(0,0,-5))
end
--# Backup
--backup
function backup(projectName,ver,comments,load)
local backupName=projectName..": "..ver..".txt" -- name of the backup file
local dds="/Documents/Dropbox.assetpack/"
local path = os.getenv("HOME").."/Documents/"
local file = os.getenv("HOME")..dds..backupName
local t=io.open(file,"r")
if t then
if load then
local txt=t:read('*a')
saveProjectTab("Recovered",txt)
end
io.close()
return
end
local wFd = io.open(file,"w")
local tabList=listProjectTabs()
data="--"..comments.."\n\n"
local txt
if #tabList>0 then
for _,tab in pairs(tabList) do
data=data..string.format("\n--# %s\n",tab)
local tt=readProjectTab(tab)
data=data..tt
if tab=="Versions" then txt=tt end
end
wFd:write(data)
end
wFd:close()
print(ver.." backup made")
--update version history
if not txt then txt="--[[VERSION HISTORY\n\n--]]" end
local i=txt:find("--]]")
txt=txt:sub(1,i-1)..""..ver.." ("..os.date("%c")..") - "..comments.."\n--]]"
saveProjectTab("Versions",txt)
end
--# Versions
--[[VERSION HISTORY
ver 101c (Sun Oct 25 17:18:22 2015) - Full controls working ver2
ver 102 (Mon Oct 26 07:37:20 2015) - Aiming v1
ver 102b (Mon Oct 26 08:15:19 2015) - Aiming v3
ver 102c (Mon Oct 26 08:35:48 2015) - Aiming blue/red v4
ver 103 (Mon Oct 26 14:23:31 2015) - Shooting seems to work v1
ver 103a (Mon Oct 26 21:47:14 2015) - Shooting with angles v2
ver 104 (Tue Oct 27 14:55:57 2015) - Shooting damage
ver 104a (Tue Oct 27 20:08:11 2015) - Shooting damage v2
ver 104b (Sat Oct 31 14:39:31 2015) - Shooting damage v3
ver 105 (Thu Nov 5 13:40:13 2015) - Collision
ver 106 (Mon Nov 16 23:00:11 2015) - culling 2
ver 107 (Tue Nov 24 11:21:27 2015) - visibility
ver 107b (Wed Feb 17 11:12:28 2016) - visibility
--]]
--# Visibility
--visibility
local dx,dz,f1x,f1z,f2x,f2z,PM1,far
function PrepareVisibilityTests(camPos,camDir,f)
far=f or 2000
PM1=1/projectionMatrix()[1]
dx,dz=camPos.x+camDir.x*far,camPos.z+camDir.z*far
local wx,wz=-far*PM1*camDir.z,far*PM1*camDir.x
f1x,f1z=camPos.x+camDir.x*far+wx,camPos.z+camDir.z*far+wz --far left of frustum
f2x,f2z=camPos.x+camDir.x*far-wx,camPos.z+camDir.z*far-wz --far right of frustum
end
function IsVisible(pos,camPos,camDir,radius,sinFOV)
local px,pz,pcx,pcz,pdx,pdz=pos.x,pos.z,camPos.x,camPos.z,camDir.x,camDir.z
--get tangent point
local u=radius/sinFOV
local tx,tz=px+pdx*u,pz+pdz*u --tangent point
--which side of LOS is our circle centre
local IsOnLeft=(px-pcx)*(dz-pcz)-(pz-pcz)*(dx-pcx)<0
local wx,wz=-far*PM1*pdz,far*PM1*pdx
if IsOnLeft then return (tx-pcx)*(f1z-pcz)-(tz-pcz)*(f1x-pcx)>0
else return (tx-pcx)*(f2z-pcz)-(tz-pcz)*(f2x-pcx)<0 end
end
function GetTangents(centre,radius,camPos)
local cx,cy,cpx,cpy=centre.x,centre.y,camPos.x,camPos.y
--calculate offset from centre
local ax,ay=cx-cpx,cy-cpy
local d=radius*(ax*ax+ay*ay)^-0.5
local dx,dy=ax*d,ay*d
return vec2(cx-dy,cy+dx),vec2(cx+dy,cy-dx)
end
--given a 2D table "tiles" with a cell for each tile on the ground, and each cell containing a list of objects
--in that cell,find all cells which are in line of sight between points A and T
--s is cellsize (square)
local floor,abs=math.floor,math.abs
--local testing=true
function GetTiles(T,A,s,tiles)
local x0,y0,x1,y1=T.x/s,-T.z/s,A.x/s,-A.z/s --tile positions of the two points
if testing then print("==================") print(T) print(x0,y0) print(A) print(x1,y1) end
local dx = abs(x1-x0)
local dy = abs(y1-y0)
local huge=99999
local tt={}
local x = floor(x0)
local y = floor(y0)
local n = 1
local x_inc, y_inc, error
if dx == 0 then
x_inc = 0
error = huge
elseif x1 > x0 then
x_inc = 1
n = n + floor(x1) - x
error = (floor(x0) + 1 - x0) * dy
else
x_inc = -1
n = n + x - floor(x1)
error = (x0 - floor(x0)) * dy
end
if dy == 0 then
y_inc = 0
error =error - huge
elseif (y1 > y0) then
y_inc = 1
n = n + floor(y1) - y
error = error - (floor(y0) + 1 - y0) * dx
else
y_inc = -1;
n = n + y - floor(y1)
error = error - (y0 - floor(y0)) * dx
end
for i = n,1,-1 do
--if testing then print("t=",x,y) end
if tiles[x] and tiles[x][y] then
tt[#tt+1]=tiles[x][y]
--if testing then print("a=",x,y,tiles[x][y][1]) end
end
if error > 0 then
y = y + y_inc
error = error - dx
else
x = x + x_inc
error = error + dy
end
end
testing=nil
return tt
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment