Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Created February 2, 2014 13:46
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/8768669 to your computer and use it in GitHub Desktop.
Save dermotbalson/8768669 to your computer and use it in GitHub Desktop.
Flying 7
--# Main
--Flying demo
--by Andrew_Stacey and ignatz
--February 2014
function setup()
--NOTE - there are lots of settings in this setup function, but you don't have to
--keep all this code. You will find that the draw and RotateAndDraw functions use "if" tests to
--fly the plane, depending on what you chose. So if you want to choose just one camera viewing
--position, and one camera tracking option, you can delete all these choices and just slightly
--modify the code in RotateAndDraw to do just what you want.
--So these options are only provided to show you what choices you have.
--Also note that the Joystick and Flight classes have a lot of options you can set.
--This demo just uses all the defaults.
--** Plane settings **
plane={}
plane.m=Plane() --plane mesh
plane.q=Flight()--plane quaternion
plane.p=vec3(0,0,0) --starting position
plane.speed=60
enemy={}
enemy.m=Plane()
enemy.q=Flight()
enemy.p=vec3(math.random(-500,500),math.random(-100,100),math.random(-500,500))
enemy.speed=40
--** camera viewing positions **
--set up 3 alternative camera viewing positions, relative to the plane
camOffset={vec3(0,3,-9),vec3(0,0,50),vec3(0,40,100)} --camera position relative to plane
--** camera tracking options **
parameter.integer("CamView",1,#camOffset,2) --this chooses the camera viewing position
parameter.integer("CamFollow",1,3,2) --this chooses how the camera tracks the plane
--1 = don't track at all, camera sits still (or you can set its position yourself)
--2 = follow the plane using the viewing position selected above
--3 = as for 2, but lag behind any changes to rotation, this can give a nice effect
--** movement **
--radius of joysticks and radar screen
--if you select a movement joystick, an on screen joystick will appear
--this is useful for things like spaceships, which don't automatically move all the time
parameter.boolean("MovementJoystick",true,SetupJoysticks)
parameter.integer("Speed",0,300,plane.speed,function(s) plane.speed=s end) --pixels per second
parameter.boolean("Radar",true,SetupRadar)
table.insert(radar.objects,enemy) --add enemy to radar screen
--set up sky globe
sky=SkyGlobe("Documents:SkyDome","Documents:SkyDome")
end
function SetupJoysticks()
local radius=100 --radius of joysticks
--rotation joystick
rotjoy = JoyStick({centre = vec2(radius+5,radius+5)})
--movement joystick
if MovementJoystick then mvjoy = JoyStick({centre = vec2(radius*3+10,radius+5)}) end
end
function draw()
rotjoy:update()
if MovementJoystick then mvjoy:update() end
background(179, 205, 220, 255)
fill(255)
perspective(45,WIDTH/HEIGHT,0,0.1,2000)
camera(0,0,100,0,0,0)
RotateAndDraw()
rotjoy:draw()
if MovementJoystick then mvjoy:draw() end
if Radar then DrawRadar() end
end
--this function adjusts the rotation based on joystick position, and draws the plane
function RotateAndDraw()
if rotjoy.mytouch then
plane.q:Turn(rotjoy)
end
if MovementJoystick and mvjoy:isMoving() then
plane.p=plane.p+plane.q:Position(vec3(0,0,mvjoy.value.y*-plane.speed*DeltaTime))
else
plane.p=plane.p+plane.q:Position(vec3(0,0,-plane.speed*DeltaTime))
end
enemy.p=enemy.p+enemy.q:Position(vec3(0,0,-enemy.speed*DeltaTime))
if CamFollow == 1 then -- stationary camera
--user can set it any way they want, this is not part of the flight library
camera(0,0,50,0,0,-1000) --this just looks straight ahead
elseif CamFollow == 2 then -- slavishly follows plane
plane.q:SetCamera(plane.p,camOffset[CamView],0)
elseif CamFollow == 3 then -- follows, but with a delay
plane.q:SetCamera(plane.p,camOffset[CamView],1)
end
pushMatrix()
translate(plane.p)
sky:draw()
plane.q:Rotate()
plane.m:draw()
popMatrix()
pushMatrix()
translate(enemy.p)
enemy.q:Rotate()
enemy.m:draw()
popMatrix()
end
--** Utility functions - joystick and touch ***
--manage joystick
function touched(t)
rotjoy:touched(t)
if MovementJoystick then mvjoy:touched(t) end
end
--# Demo2
--Flying demo
--by Andrew_Stacey and ignatz
--February 2014
function setup()
--NOTE - there are lots of settings in this setup function, but you don't have to
--keep all this code. You will find that the draw and RotateAndDraw functions use "if" tests to
--fly the plane, depending on what you chose. So if you want to choose just one camera viewing
--position, and one camera tracking option, you can delete all these choices and just slightly
--modify the code in RotateAndDraw to do just what you want.
--So these options are only provided to show you what choices you have.
--** Plane settings **
plane={}
plane.m=Plane() --plane mesh
plane.q=Flight()--plane quaternion
plane.p=vec3(0,0,0) --starting position
plane.speed=60
enemy={}
enemy.m=Plane()
enemy.q=Flight()
enemy.p=vec3(math.random(-500,500),math.random(-100,100),math.random(-500,500))
enemy.speed=40
--** camera viewing positions **
--set up 3 alternative camera viewing positions, relative to the plane
camOffset={vec3(0,3,-9),vec3(0,0,50),vec3(0,40,100)} --camera position relative to plane
--** camera tracking options **
parameter.integer("CamView",1,#camOffset,2) --this chooses the camera viewing position
parameter.integer("CamFollow",1,3,2) --this chooses how the camera tracks the plane
--1 = don't track at all, camera sits still (or you can set its position yourself)
--2 = follow the plane using the viewing position selected above
--3 = as for 2, but lag behind any changes to rotation, this can give a nice effect
--** movement **
--radius of joysticks and radar screen
--if you select a movement joystick, an on screen joystick will appear
--this is useful for things like spaceships, which don't automatically move all the time
parameter.boolean("MovementJoystick",true,SetupJoysticks)
parameter.integer("Speed",0,300,plane.speed,function(s) plane.speed=s end) --pixels per second
parameter.boolean("Radar",true,SetupRadar)
table.insert(radar.objects,enemy) --add enemy to radar screen
--set up sky globe
sky=SkyGlobe("Documents:SkyDome","Documents:SkyDome")
end
function SetupJoysticks()
local radius=100 --radius of joysticks
--rotation joystick
rotjoy = JoyStick({centre = vec2(radius+5,radius+5)})
--movement joystick
if MovementJoystick then mvjoy = JoyStick({centre = vec2(radius*3+10,radius+5)}) end
end
function draw()
rotjoy:update()
if MovementJoystick then mvjoy:update() end
background(179, 205, 220, 255)
fill(255)
perspective(45,WIDTH/HEIGHT,0,0.1,2000)
camera(0,0,100,0,0,0)
RotateAndDraw()
rotjoy:draw()
if MovementJoystick then mvjoy:draw() end
if Radar then DrawRadar() end
end
--this function adjusts the rotation based on joystick position, and draws the plane
function RotateAndDraw()
if rotjoy.mytouch then
plane.q:Turn(rotjoy)
end
if MovementJoystick and mvjoy:isMoving() then
plane.p=plane.p+plane.q:Position(vec3(0,0,mvjoy.value.y*-plane.speed*DeltaTime))
else
plane.p=plane.p+plane.q:Position(vec3(0,0,-plane.speed*DeltaTime))
end
enemy.p=enemy.p+enemy.q:Position(vec3(0,0,-enemy.speed*DeltaTime))
if CamFollow == 1 then -- stationary camera
--user can set it any way they want, this is not part of the flight library
camera(0,0,50,0,0,-1000) --this just looks straight ahead
elseif CamFollow == 2 then -- slavishly follows plane
plane.q:SetCamera(plane.p,camOffset[CamView],0)
elseif CamFollow == 3 then -- follows, but with a delay
plane.q:SetCamera(plane.p,camOffset[CamView],1)
end
pushMatrix()
translate(plane.p)
sky:draw()
plane.q:Rotate()
plane.m:draw()
popMatrix()
pushMatrix()
translate(enemy.p)
enemy.q:Rotate()
enemy.m:draw()
popMatrix()
end
--** Utility functions - joystick and touch ***
--manage joystick
function touched(t)
rotjoy:touched(t)
if MovementJoystick then mvjoy:touched(t) end
end
--# Demo1
--Flying demo1
--by Andrew_Stacey and ignatz
--February 2014
--this is the basic version, using joysticks
--you don't need the Joystick tab if you don't use joysticks, nor do you need the Radar tab for this project
function setup()
--NOTE - there are lots of settings in this setup function, but you don't have to
--keep all this code. You will find that the draw and RotateAndDraw functions use "if" tests to
--fly the plane, depending on what you chose. So if you want to choose just one camera viewing
--position, and one camera tracking option, you can delete all these choices and just slightly
--modify the code in RotateAndDraw to do just what you want.
--So these options are only provided to show you what choices you have.
--** Plane settings **
plane={}
plane.m=Plane() --plane mesh
plane.q=Flight()--plane quaternion
plane.p=vec3(0,0,0) --starting position
plane.speed=60 --pixels/sec
--** camera viewing positions **
--set up 3 alternative camera viewing positions, relative to the plane
camOffset={vec3(0,3,-9),vec3(0,0,50),vec3(0,40,100)} --camera position relative to plane
--** camera tracking options **
parameter.integer("CamView",1,#camOffset,2) --this chooses the camera viewing position
parameter.integer("CamFollow",1,3,2) --this chooses how the camera tracks the plane
--1 = don't track at all, camera sits still (or you can set its position yourself)
--2 = follow the plane using the viewing position selected above
--3 = as for 2, but lag behind any changes to rotation, this can give a nice effect
--** movement **
--radius of joysticks and radar screen
--if you select a movement joystick, an on screen joystick will appear
--this is useful for things like spaceships, which don't automatically move all the time
--if the movement joystick is turned off, then the speed is set by you (using a parameter, in this project)
parameter.boolean("MovementJoystick",true,SetupJoysticks)
parameter.integer("Speed",0,300,plane.speed,function(s) plane.speed=s end) --pixels per second
parameter.boolean("Radar",true,SetupRadar)
table.insert(radar.objects,enemy) --add enemy to radar screen
--set up sky globe
sky=SkyGlobe("Documents:SkyDome","Documents:SkyDome")
end
function SetupJoysticks()
local radius=100 --radius of joysticks
--rotation joystick
rotjoy = JoyStick({centre = vec2(radius+5,radius+5)})
--movement joystick
if MovementJoystick then mvjoy = JoyStick({centre = vec2(radius*3+10,radius+5)}) end
end
function draw()
rotjoy:update()
if MovementJoystick then mvjoy:update() end
background(179, 205, 220, 255)
fill(255)
perspective()
RotateAndDraw()
rotjoy:draw()
if MovementJoystick then mvjoy:draw() end
end
--this function adjusts the rotation based on joystick position, and draws the plane
function RotateAndDraw()
if rotjoy.mytouch then --turn plane if user has used joystick
plane.q:Turn(rotjoy)
end
--calculate new position of plane
if MovementJoystick and mvjoy:isMoving() then
--joystick controls speed and forward/back direction of movement (good for spacecraft)
plane.p=plane.p+plane.q:Position(vec3(0,0,mvjoy.value.y*-plane.speed*DeltaTime))
else
--no joystick, plane goes at whatever speed you have set
plane.p=plane.p+plane.q:Position(vec3(0,0,-plane.speed*DeltaTime))
end
if CamFollow == 1 then -- stationary camera
--user can set it any way they want, this is not part of the flight library
camera(0,0,50,0,0,-1000) --this just looks straight ahead
elseif CamFollow == 2 then -- slavishly follows plane
plane.q:SetCamera(plane.p,camOffset[CamView],0)
elseif CamFollow == 3 then -- follows, but with a delay
plane.q:SetCamera(plane.p,camOffset[CamView],1)
end
pushMatrix()
translate(plane.p)
sky:draw()
plane.q:Rotate()
plane.m:draw()
popMatrix()
end
--** Utility functions - joystick and touch ***
--manage joystick
function touched(t)
rotjoy:touched(t)
if MovementJoystick then mvjoy:touched(t) end
end
--# Flight
--Flight class
Flight=class()
--Note all the options you can set below
function Flight:init(t)
t = t or {}
--yaw is kept separate from pitch and roll
--don't mess with this unless you REALLY know what you are doing
self.yaw = t.yaw or vec3(0,1,0)
self.pitchroll = t.pitchroll or vec3(1,0,1)
self.mode= t.mode or vec3(0,1,0)
self.sens=t.sens or vec3(1,1,1) --sensitivity to incremental changes
self.limit=t.limit or vec3(90,180,45)*math.pi/180 --rotation limit
self.steady=t.steady or 5
self:Reset()
end
function Flight:Reset()
self.y=vec4(1,0,0,0)
self.pr=vec4(1,0,0,0)
self.dv=vec3(0,0,0)
self.cam = {
at = vec3(0,0,1),
frame = vec4(1,0,0,0),
vel = vec3(0,0,0),
avel = vec3(0,0,0)}
end
--[[
self.turning needs to use a ternary logic so we can detect not only its state but also when it flips
--]]
function Flight:Turn(r)
local dv = vec3(-r.delta.y,-r.delta.x,-r.delta.x)
local vv = vec3(-r.value.y,-r.value.x,-r.value.x)
local v = (1 - self.mode) * dv * self.limit + self.mode * vv * self.sens * DeltaTime
self.dv = v
self.y = self.y * qTangent(v * self.yaw)
self.pr = self.pr * qTangent(v * self.pitchroll)
self.turning = 1
end
function Flight:Position(s) --d is vec3 distance in pixels
return s^(self.y*self.pr)
end
function Flight:Rotate()
self:Interpolate() --RRRR update interpolation when rotating
rotate(self.y*self.pr)
end
function Flight:rotation()
return self.y*self.pr
end
function Flight:InvertRotation()
local q=self:rotation()
rotate(q:conjugate())
end
function Flight:Interpolate()
--don't update if we've done it already in this timeslice
if self.stime==ElapsedTime then return end
--interpolate
if self.turning == 2 then
self.interpolating = true
self.stime = ElapsedTime
self.slerpQ = self:InterpolateSlerp()
self.turning = 0
elseif self.turning == 1 then
self.turning = 2
elseif self.interpolating then
self.interpolating = self.slerpQ(ElapsedTime)
end
end
function Flight:InterpolateSlerp()
local target, stime, l, sl, dy
if self.pr:toreal() < 0 then
target = vec4(-1,0,0,0)
else
target = vec4(1,0,0,0)
end
stime = ElapsedTime
l = self.pr:sdist(target) * self.steady
sl = self.pr:make_slerp(target)
dy = self.dv * self.yaw
return function(t)
t = t - stime
if t > l then
self.pr = target
return false
end
self.pr = sl(smootherstep(t,0,l))
self.y = self.y * qTangent(edge(t,l,0) * dy)
return true
end
end
--p = vec3, plane position
--offset = vec3, location of camera relative to self, in object space
--lag = scalar, camera follow lag, from 0 (no lag) to 5 (very slow)
function Flight:SetCamera(p,offset,lag)
if not lag or lag==0 then --slavishly follow plane
self.cam.at = p + self:Position(offset)
self.cam.frame= self:rotation()
else --lag plane rotation
local ca,cq = self.cam.at,self.cam.frame
self.cam.at = self.cam.at + self.cam.vel * DeltaTime/lag
self.cam.frame = self.cam.avel:exp(DeltaTime/lag) * self.cam.frame
self.cam.vel = (p + self:Position(offset) - ca)
self.cam.avel = (self:rotation() * cq^""):log()
end
local up = vec3(0,1,0)^self.cam.frame
--NOTE - addition to end of next line, so camera looks in front of plane (and not at it)
local look = p + self:Position(vec3(0,0,-20))
camera(self.cam.at,look,up)
end
--# Plane
--plane
Plane=class()
function Plane:init()
a={}
c1=color(107, 139, 184, 255)
c2=color(72, 148, 195, 255)
c3=color(176, 159, 101, 255)
c4=color(255,0,0)
a[1]=Block(3,3,25,c1,vec3(0,0,0)) --fuselage
a[2]=Block(15,1,5,c2,vec3(-9,1,-6)) --left wing
a[3]=Block(15,1,5,c2,vec3(9,1,-6)) --right wing
a[4]=Block(1,4,3,c2,vec3(0,3.5,11)) --vertical tail
a[5]=Block(4,1,2,c2,vec3(-3.5,1,11)) --left tail
a[6]=Block(4,1,2,c2,vec3(3.5,1,11)) --right tail
a[7]=Block(3,2,5,c3,vec3(0,2.5,-6)) --cockpit
a[8]=Block(3,3,1,c4,vec3(0,0,-13)) --nose
self.blocks=a
end
function Plane:draw()
for i=1,#self.blocks do
self.blocks[i]:draw()
end
end
Block = class() --taken from 3D lab project
function Block:init(w,h,d,c,p,t,r) --width,height,depth,colour,position,texture, (optional) texture range (see above)
self.width=w
self.height=h
self.depth=d
self.color=c
self.pos=p
self.tex=t
--if no limits specified on which part of image to draw, set to 0,1
if r~=nil then self.texR=r else self.texR={0,0,1,1} end
self.blk=self:createBlock()
end
function Block:createBlock()
-- all the unique vertices that make up a block
--There are only 8 corners in a cube - we define them as vertices
--all measurements are taken from the centre of the block
--so bottom left front has x of -1/2 width, y of -1/2 height, and z of 1/2 depth
local w,h,d=self.width,self.height,self.depth
local x,y,z=self.pos.x,self.pos.y,self.pos.z
local v = {
vec3(x-0.5*w, y-0.5*h, z+0.5*d), -- Left bottom front
vec3(x+0.5*w, y-0.5*h, z+0.5*d), -- Right bottom front
vec3(x+0.5*w, y+0.5*h, z+0.5*d), -- Right top front
vec3(x-0.5*w, y+0.5*h, z+0.5*d), -- Left top front
vec3(x-0.5*w, y-0.5*h, z-0.5*d), -- Left bottom back
vec3(x+0.5*w, y-0.5*h, z-0.5*d), -- Right bottom back
vec3(x+0.5*w, y+0.5*h, z-0.5*d), -- Right top back
vec3(x-0.5*w, y+0.5*h, z-0.5*d), -- Left top back
}
local cubeverts = {
-- Front, Right, Back, Left, Top, Bottom
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 cc={}
--assign colors
local c=self.color
local x=0.5
local BL=color(c.r,c.g,c.b) --bottom left
local BR=color(c.r,c.g,c.b) --bottom right
local TR=color(c.r*x,c.g*x,c.b*x) --top right
local TL=color(c.r*x,c.g*x,c.b*x) --top left
for i=1,6 do
cc[#cc+1]=BL
cc[#cc+1]=BR
cc[#cc+1]=TR
cc[#cc+1]=BL
cc[#cc+1]=TR
cc[#cc+1]=TL
end
--put it all together
local ms = mesh()
ms.vertices = cubeverts
if self.tex then
ms.texture = self.tex
ms.texCoords = cubetexCoords
else
ms.colors=cc
end
--ms:setColors(self.color)
return ms
end
function Block:draw()
self.blk:draw()
end
--# Joystick
JoyStick = class()
--Note all the options you can set below
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.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 = 60
self.moving = 0
end
function JoyStick:draw()
-- Codea does not automatically call this method
ortho()
viewMatrix(matrix())
pushStyle()
fill(160, 182, 191, 50)
stroke(118, 154, 195, 100)
strokeWidth(1)
ellipse(self.centre.x,self.centre.y,2*self.radius)
fill(78, 131, 153, 50)
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.mytouch = t.id
end
end
if t.id == self.mytouch 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.mytouch = false
end
end
end
function JoyStick:update()
local p = self.target - self.position
if p:len() < DeltaTime * self.mspeed then
self.position = self.target
if not self.mytouch then
if self.moving ~= 0 then
self.moving = self.moving - 1
end
else
self.moving = 2
end
else
self.position = self.position + p:normalize() * DeltaTime * self.mspeed
self.moving = 2
end
local v = self.position/(self.radius - self.stick)
self.delta = v - self.value
self.value = v
end
function JoyStick:isMoving()
return self.moving
end
--# Quaternion
-- Extensions to the native Codea vector and matrix types
-- Author: Andrew Stacey
-- Website: http://loopspace.mathforge.com
-- Licence: CC0 http://wiki.creativecommons.org/CC0
local ab,ac,ad,ae,af,ag,am,as,ay,ca,ch,co,cp,er,eu,ex,fl,ip,is,lo,m,ma,mi,mm,mr,mu,mv,mw,mx,pi,pm,po,pr,qp,ra,ro,rp,sc,sh,si,sq,su,sv,sw,sy,ta,tc,th,tp,tr,ts,vm
er,m = error or print,math
ab,po,sq,si,co,ac,as,pi,fl,mi,ma,ex,lo,ta,sh,ch,th = m.abs,m.pow,m.sqrt,m.sin,m.cos,m.acos,m.asin,m.pi,m.floor,m.min,m.max,m.exp,m.log,m.tan,m.sinh,m.cosh,m.tanh
mm,am,vm,pm,ro,tr,sc,ca = modelMatrix,applyMatrix,viewMatrix,projectionMatrix,rotate,translate,scale,camera
function is(a,b)
if type(b) == "function" then b = b() end
if type(b) == "string" then return type(a) == b end
if type(b) == "table" and type(a) == "table" and a.is_a then return a:is_a(b) end
if type(b) == "userdata" and type(a) == "userdata" then return getmetatable(a) == getmetatable(b) end
return false
end
function edge(t,a,b)
a,b = a or 0,b or 1
return mi(1,ma(0,(t-a)/(b-a)))
end
function smoothstep(t,a,b)
a,b = a or 0,b or 1
t = mi(1,ma(0,(t-a)/(b-a)))
return t * t * (3 - 2 * t)
end
function smootherstep(t,a,b)
a,b = a or 0,b or 1
t = mi(1,ma(0,(t-a)/(b-a)))
return t * t * t * (t * (t * 6 - 15) + 10)
end
sy = readLocalData("Complex Symbol","i")
ra = readLocalData("Complex Angle","rad")
if ra == "rad" then ag,ay = 1,"π" else ag,ay = 180,"°" end
pr = readLocalData("Complex Precision",2)
function setComplex(t)
ag,ay,pr,sy,ts = t.angle or ag,t.angsym or ay,t.precision or pr,t.symbol or sy,t.tostring or ts
end
m = getmetatable(vec2())
m["clone"] = function (c) return vec2(c.x,c.y) end
m["is_finite"] = function (c) if c.x < math.huge and c.x > -math.huge and c.y < math.huge and c.y > -math.huge then return true end return false end
m["is_real"] = function (c) return c.y == 0 end
m["is_imaginary"] = function (c) return c.x == 0 end
m["normalise"] = function (c) c=c:normalize() if c:is_finite() then return c else return vec2(1,0) end end
ad,su,mu = m["__add"],m["__sub"],m["__mul"]
m["__add"] = function (a,b)
if is(a,"number") then a = vec2(a,0) end
if is(b,"number") then b = vec2(b,0) end
return ad(a,b)
end
m["__sub"] = function (a,b)
if is(a,"number") then a = vec2(a,0) end
if is(b,"number") then b = vec2(b,0) end
return su(a,b)
end
m["__mul"] = function (a,b)
if is(a,"number") then a = vec2(a,0) end
if is(b,"number") then b = vec2(b,0) end
return vec2(a.x*b.x - a.y*b.y,a.x*b.y+a.y*b.x)
end
m["conjugate"] = function (c) return vec2(c.x, - c.y) end
m["co"] = m["conjugate"]
function rp(c,n,k)
k = k or 0
local r,t = pow(c:len(),n), (k*2*pi-c:angleBetween(vec2(1,0)))*n
return vec2(r*cos(t),r*sin(t))
end
function cp(c,w,k)
if is(w,"number") then return rp(c,w,k) end
if c == vec2(0,0) then
er("Taking powers of 0 is somewhat dubious")
return false
end
local r,t = c:len(),-c:angleBetween(vec2(1,0))
k = k or 0
local nr,nt = pow(r,w.x) * exp(-w.y*t), (t + k * 2 * pi) * w.x + log(r) * w.y
return vec2(nr*cos(nt),nr*sin(nt))
end
m["__pow"] = function (c,n)
if is(n,"number") then return rp(c,n)
elseif is(n,vec2) then return cp(c,n)
else return c:co() end
end
m["__div"] = function (c,q)
if is(q,"number") then return vec2(c.x/q,c.y/q)
elseif is(c,"number") then return c/q:lenSqr()*vec2(q.x,-q.y)
else return vec2(c.x*q.x+c.y*q.y,c.y*q.x-c.x*q.y)/q:lenSqr() end
end
m["real"] = function (c) return c.x end
m["imaginary"] = function (c) return c.y end
m["__concat"] = function (c,v) if is(v,vec2) then return c .. v:tostring() else return c:tostring() .. v end end
m["tostring"] = function (c) return ts(c) end
function tc(c)
local s
local x,y = fl(c.x * 10^pr +.5)/10^pr,fl(c.y * 10^pr +.5)/10^pr
if x ~= 0 then s = x end
if y ~= 0 then if s then if y > 0 then
if y == 1 then s = s .. " + " .. sy
else s = s .. " + " .. y .. sy end
else
if y == -1 then s = s .. " - " .. sy
else s = s .. " - " .. (-y) .. sy end
end
else
if y == 1 then s = sy
elseif y == - 1 then s = "-" .. sy
else s = y .. sy end
end
end
s = s or "0"
return s
end
function tp (c)
local t,r = fl(ag *nc:arg() * 10^pr/pi +.5)/10^pr,fl(c:len() * 10^pr +.5)/10^pr
return "(" .. r .. "," .. t .. angsym .. ")"
end
ts = tc
m["topolarstring"] = tp
m["tocartesianstring"] = tc
m["arg"] = function (c) return -c:angleBetween(vec2(1,0)) end
function Complex_unit() return vec2(1,0) end
function Complex_zero() return vec2(0,0) end
function Complex_i() return vec2(0,-1) end
function math.abs(n)
if is(n,"number") then return ab(n) end
if is(n,vec2) then return n:len() end
er("Cannot take the length of " .. n)
end
function math.pow(n,e)
if is(n,"number") then
if is(e,"number") then return po(n,e)
elseif is(e,vec2) then return rp(vec2(n,0),e,0) end
end
if is(n,vec2) then return cp(n,e,0) end
er("Cannot take the power of " .. n .. " by " .. e)
end
function math.sqrt(n)
if is(n,"number") then return sq(n) end
if is(n,vec2) then return rp(n,.5,0) end
er("Cannot take the square root of " .. n)
end
function math.exp(n)
if is(n,"number") then return ex(n) end
if is(n,vec2) then
local r = ex(n.x)
return vec2(r*cos(n.y),r*sin(n.y))
end
er("Cannot exponentiate " .. n)
end
function math.cos(n)
if is(n,"number") then return co(n) end
if is(n,vec2) then return vec2(co(n.x)*ch(n.y),-si(n.x)*sh(n.y)) end
er("Cannot take the cosine of " .. n)
end
function math.sin(n)
if is(n,"number") then return si(n) end
if is(n,vec2) then return vec2(si(n.x)*ch(n.y),co(n.x)*sh(n.y)) end
error("Cannot take the sine of " .. n)
end
function math.cosh(n)
if is(n,"number") then return ch(n) end
if is(n,vec2) then return vec2(ch(n.x)*co(n.y), sh(n.x)*si(n.y)) end
er("Cannot take the hyperbolic cosine of " .. n)
end
function math.sinh(n)
if is(n,"number") then return sh(n) end
if is(n,vec2) then return vec2(sh(x)*co(y), ch(x)*si(y)) end
er("Cannot take the hyperbolic sine of " .. n)
end
function math.tan(n)
if is(n,"number") then return ta(n) end
if is(n,vec2) then
local cx,sx,chy,shy = co(n.x),si(n.x),ch(n.y),sh(n.y)
local d = cx^2 * chy^2 + sx^2 * shy^2
if d == 0 then return false end
return vec2(sx*cx/d,shy*chy/d)
end
er("Cannot take the tangent of " .. n)
end
function math.tanh(n)
if is(n,"number") then return th(n) end
if is(n,vec2) then
local cx,sx,chy,shy = co(n.x),si(n.x),ch(n.y),sh(n.y)
local d = cx^2 * chy^2 + sx^2 * shy^2
if d == 0 then return false end
return vec2(shx*chx/d,sy*cy/d)
end
er("Cannot take the hyperbolic tangent of " .. n)
end
function math.log(n,k)
if is(n,"number") then
if k then return vec2(log(n), 2*k*pi)
else return log(n) end
end
k = k or 0
if is(n,vec2) then return vec2(lo(n:len()),n:arg() + 2*k*pi) end
er("Cannot take the logarithm of " .. n)
end
m = getmetatable(vec4())
m["is_finite"] = function(q)
if q.x < math.huge and q.x > -math.huge and q.y < math.huge and q.y > -math.huge and q.z < math.huge and q.z > -math.huge and q.w < math.huge and q.w > -math.huge then return true end
return false
end
m["is_real"] = function (q)
if q.y ~= 0 or q.z ~= 0 or q.w ~= 0 then return false end
return true
end
m["is_imaginary"] = function (q) return q.x == 0 end
m["normalise"] = function (q) q = q:normalize() if q:is_finite() then return q else return vec4(1,0,0,0) end
end
m["slen"] = function(q)
q = q:normalise()
q.x = q.x - 1
return 2*as(q:len()/2)
end
m["sdist"] = function(q,qq)
q = q:normalise()
qq = qq:normalise()
return 2*as(q:dist(qq)/2)
end
ae,sv,mv,dv = m["__add"],m["__sub"],m["__mul"],m["__div"]
m["__add"] = function (a,b)
if is(a,"number") then a = vec4(a,0,0,0) end
if is(b,"number") then b = vec4(b,0,0,0) end
return ae(a,b)
end
m["__sub"] = function (a,b)
if is(a,"number") then a = vec4(a,0,0,0) end
if is(b,"number") then b = vec4(b,0,0,0) end
return sv(a,b)
end
m["__mul"] = function (a,b)
if is(a,"number") then return mv(a,b) end
if is(b,"number") then return mv(a,b) end
if is(a,matrix) then return a:__mul(b:tomatrixleft()) end
if is(b,matrix) then return a:tomatrixleft():__mul(b) end
return vec4(a.x*b.x-a.y*b.y-a.z*b.z-a.w*b.w,a.x*b.y+a.y*b.x+a.z*b.w-a.w*b.z,a.x*b.z-a.y*b.w+a.z*b.x+a.w*b.y,a.x*b.w+a.y*b.z-a.z*b.y+a.w*b.x)
end
m["conjugate"] = function (q) return vec4(q.x, - q.y, - q.z, - q.w) end
m["co"] = m["conjugate"]
m["__div"] = function (a,b)
if is(b,"number") then return dv(a,b) end
local l = b:lenSqr()
b = vec4(b.x/l,-b.y/l,-b.z/l,-b.w/l)
if is(a,"number") then return vec4(a*b.x,a*b.y,a*b.z,a*b.w) end
return vec4(a.x*b.x-a.y*b.y-a.z*b.z-a.w*b.w,a.x*b.y+a.y*b.x+a.z*b.w-a.w*b.z,a.x*b.z-a.y*b.w+a.z*b.x+a.w*b.y,a.x*b.w+a.y*b.z-a.z*b.y+a.w*b.x)
end
function ip(q,n)
if n == 0 then return vec4(1,0,0,0)
elseif n > 0 then return q:__mul(ip(q,n-1))
elseif n < 0 then
local l = q:lenSqr()
q = vec4(q.x/l,-q.y/l,-q.z/l,-q.w/l)
return q:ip(-n)
end
end
function qp(q,n)
if n == fl(n) then return ip(q,n) end
local l = q:len()
q = q:normalise()
return l^n * q:slerp(n)
end
m["__pow"] = function (q,n)
if is(n,"number") then return qp(q,n)
elseif is(n,vec4) then return n:__mul(q):__div(n)
else return q:conjugate() end
end
m["lerp"] = function (q,qq,t)
if not t then q,qq,t = vec4(1,0,0,0),q,qq end
if (q + qq):len() == 0 then q = (1-2*t)*q+(1-ab(2*t-1))*vec4(q.y,-q.x,q.w,-q.z)
else q = (1-t)*q + t*qq end
return q:normalise()
end
m["slerp"] = function (q,qq,t)
if not t then q,qq,t = vec4(1,0,0,0),q,qq end
if (q + qq):len() == 0 then qq,t = vec4(q.y,-q.x,q.w,-q.z),2*t
elseif (q - qq):len() == 0 then return q end
local ca = q:dot(qq)
local sa = sq(1 - po(ca,2))
if sa == 0 or sa ~= sa then return q end
local a = ac(ca)
sa = si(a*t)/sa
return (co(a*t)-ca*sa)*q+sa*qq
end
m["make_lerp"] = function (q,qq)
if not qq then q,qq = vec4(1,0,0,0),q end
q,qq = q:normalise(),qq:normalise()
if (q + qq):len() == 0 then
qq = vec4(q.y,-q.x,q.w,-q.z)
return function(t) return ((1-2*t)*q+(1-ab(2*t-1))*qq):normalise() end
else return function(t) return ((1-t)*q+t*qq):normalise() end
end
end
m["make_slerp"] = function (q,qq)
if not qq then q,qq = vec4(1,0,0,0),q end
q,qq = q:normalise(),qq:normalise()
local f
if (q + qq):len() == 0 then qq,f = vec4(q.y,-q.x,q.w,-q.z),2
elseif (q - qq):len() == 0 then return function(t) return q end
else f = 1 end
local ca = q:dot(qq)
local sa = sq(1 - po(ca,2))
if sa == 0 or sa ~= sa then return function(t) return q end end
local a = ac(ca)
qq = (qq - ca*q)/sa
return function(t) return co(a*f*t)*q + si(a*f*t)*qq end
end
m["toreal"] = function (q) return q.x end
m["vector"] = function (q) return vec3(q.y, q.z, q.w) end
m["tovector"] = m["vector"]
m["log"] = function (q)
local l = q:slen()
q = q:tovector():normalize()
if not q:is_finite() then return vec3(0,0,0)
else return q * l end
end
m["tostring"] = function (q)
local s
local im = {{q.y,"i"},{q.z,"j"},{q.w,"k"}}
if q.x ~= 0 then s = string.format("%.3f",q.x) end
for k,v in pairs(im) do
if v[1] ~= 0 then if s then if v[1] > 0 then
if v[1] == 1 then s = s.." + "..v[2]
else s = s.." + "..string.format("%.3f",v[1])..v[2]
end
else if v[1] == -1 then s = s.." - "..v[2]
else s = s.." - "..string.format("%.3f",-v[1])..v[2] end
end
else if v[1] == 1 then s = v[2]
elseif v[1] == - 1 then s = "-" .. v[2]
else s = string.format("%.3f",v[1]) .. v[2] end
end end end
s = s or "0"
return s
end
m["__concat"] = function (q,s)
if is(s,"string") then return q:tostring() .. s else return q .. s:tostring() end
end
m["tomatrixleft"] = function (q)
q = q:normalise()
local a,b,c,d = q.x,q.y,q.z,q.w
local ab,ac,ad,bb,bc,bd,cc,cd,dd = 2*a*b,2*a*c,2*a*d,2*b*b,2*b*c,2*b*d,2*c*c,2*c*d,2*d*d
return matrix(1-cc-dd,bc-ad,ac+bd,0,bc+ad,1-bb-dd,cd-ab,0,bd-ac,cd+ab,1-bb-cc,0,0,0,0,1)
end
m["tomatrixright"] = function (q)
q = q:normalise()
local a,b,c,d = q.x,-q.y,-q.z,-q.w
local ab,ac,ad,bb,bc,bd,cc,cd,dd = 2*a*b,2*a*c,2*a*d,2*b*b,2*b*c,2*b*d,2*c*c,2*c*d,2*d*d
return matrix(1-cc-dd,bc-ad,ac+bd,0,bc+ad,1-bb-dd,cd-ab,0,bd-ac,cd+ab,1-bb-cc,0,0,0,0,1)
end
m["tomatrix"] = m["tomatrixright"]
m["toangleaxis"] = function (q)
q = q:normalise()
local a = q.x
q = vec3(q.y,q.z,q.w)
if q == vec3(0,0,0) then return 0,vec3(0,0,1) end
return 2*ac(a),q:normalise()
end
function qGravity()
if Gravity.x == 0 and Gravity.y == 0 then return vec4(1,0,0,0)
else
local gxy, gy, gygxy, a, b, c, d
gy,gxy = - Gravity.y,sq(po(Gravity.x,2) + po(Gravity.y,2))
gygxy = gy/gxy
a,b,c,d = sq(1 + gxy - gygxy - gy)/2,sq(1 - gxy - gygxy + gy)/2,sq(1 - gxy + gygxy - gy)/2,sq(1 + gxy + gygxy + gy)/2
if Gravity.z < 0 then b,c = - b,-c end
if Gravity.x > 0 then c,d = - c,-d end
return vec4(a,b,c,d)
end
end
function qRotation(a,x,y,z)
local q,c,s
if not y then x,y,z = x.x,x.y,x.z end
q = vec4(0,x,y,z):normalise()
if q == vec4(1,0,0,0) then return q end
return q:__mul(si(a/2)):__add(co(a/2))
end
eu = {}
eu.x = function(q) return vec3(1,0,0)^q end
eu.X = function(q) return vec3(1,0,0) end
eu.y = function(q) return vec3(0,1,0)^q end
eu.Y = function(q) return vec3(0,1,0) end
eu.z = function(q) return vec3(0,0,1)^q end
eu.Z = function(q) return vec3(0,0,1) end
function qEuler(a,b,c,v)
if c then a = {a,b,c}
else if is(a,vec3) then a = {a.x,a.y,a.z} end v = b end
v = v or {"x","y","z"}
local q = vec4(1,0,0,0)
for k,u in ipairs(v) do
q = q * qRotation(a[k],eu[u](q))
end
return q
end
function qTangent(x,y,z,t)
local q
if is(x,"number") then q,t = vec4(0,x,y,z),t or 1
else q,t = vec4(0,x.x,x.y,x.z),y or 1 end
local qn = q:normalise()
if qn == vec4(1,0,0,0) then return qn end
t = t * q:len()
return co(t)*vec4(1,0,0,0) + si(t)*qn
end
function qRotationRate() return qTangent(DeltaTime * RotationRate) end
function modelMatrix(m)
if m then if is(m,vec4) then m = m:tomatrixright() end return mm(m) else return mm() end
end
function applyMatrix(m)
if m then if is(m,vec4) then m = m:tomatrixright() end return am(m) else return am() end
end
function viewMatrix(m)
if m then if is(m,vec4) then m = m:tomatrixright() end return vm(m) else return vm() end
end
function projectionMatrix(m)
if m then if is(m,vec4) then m = m:tomatrixright() end return pm(m) else return pm() end
end
function rotate(a,x,y,z)
if is(a,vec4) then
local v
a,v = a:toangleaxis()
x,y,z,a = v.x,v.y,v.z,a*180/pi
end
if x then return ro(a,x,y,z) end
return ro(a)
end
function translate(x,y,z)
if not y then x,y,z = x.x,x.y,x.z end
if z then return tr(x,y,z) end
return tr(x,y)
end
function scale(a,b,c)
if is(a,vec3) then a,b,c = a.x,a.y,a.z end
if c then return sc(a,b,c) end
if b then return sc(a,b) end
if a then return sc(a) end
return sc()
end
function camera(a,b,c,d,e,f,g,h,i)
if is(a,vec3) then a,b,c,d,e,f,g,h,i = a.x,a.y,a.z,b,c,d,e,f,g end
if is(d,vec3) then d,e,f,g,h,i = d.x,d.y,d.z,e,f,g end
if is(g,vec3) then g,h,i = g.x,g.y,g.z end
if g then return ca(a,b,c,d,e,f,g,h,i)
elseif d then return ca(a,b,c,d,e,f)
elseif a then return ca(a,b,c)
else return ca end
end
m = getmetatable(vec3())
m["is_finite"] = function(v)
if v.x < math.huge and v.x > -math.huge and v.y < math.huge and v.y > -math.huge and v.z < math.huge and v.z > -math.huge then return true end
return false
end
m["toQuaternion"] = function (v) return vec4(0,v.x,v.y,v.z) end
m["applyQuaternion"] = function (v,q) return q:__mul(v:toQuaternion()):__mul(q:conjugate()):vector() end
m["rotate"] = function(v,q,x,y,z)
if is(q,"number") then q = qRotation(q,x,y,z) end
return v:applyQuaternion(q)
end
m["__pow"] = function (v,q)
if is(q,vec4) then return v:applyQuaternion(q) end
return false
end
m["__concat"] = function (u,s)
if is(s,"string") then return u:__tostring() .. s
else return u .. s:__tostring() end
end
m["rotateTo"] = function (u,v)
if v:lenSqr() == 0 or u:lenSqr() == 0 then return vec4(1,0,0,0) end
u = u:normalise()
v = u + v:normalise()
if v:lenSqr() == 0 then
local a,b,c = abs(u.x), abs(u.y), abs(u.z)
if a < b and a < c then v = vec3(0,-u.z,u.y)
elseif b < c then v = vec3(u.z,0,-u.x)
else v = vec3(u.y,-u.x,0) end
end
v = v:normalise()
local d = u:dot(v)
u = u:cross(v)
return vec4(d,u.x,u.y,u.z)
end
m["normalise"] = function (v)
v = v:normalize()
if v:is_finite() then return v
else return vec3(0,0,1) end
end
mw,af,sw = m["__mul"],m["__add"],m["__sub"]
m["__mul"] = function(m,v)
if is(m,vec3) and is(v,"number") then return mw(m,v) end
if is(m,"number") and is(v,vec3) then return mw(m,v) end
if is(m,vec3) and is(v,vec3) then return vec3(m.x*v.x,m.y*v.y,m.z*v.z) end
if is(m,matrix) and is(v,vec3) then
local l = m[13]*v.x+m[14]*v.y+m[15]*v.z+m[16]
return vec3((m[1]*v.x + m[2]*v.y + m[3]*v.z + m[4])/l,(m[5]*v.x + m[6]*v.y + m[7]*v.z + m[8])/l,(m[9]*v.x + m[10]*v.y + m[11]*v.z + m[12])/l)
end
if is(m,vec3) and is(v,matrix) then
local l = v[4]*m.x+v[8]*m.y+v[12]*m.z+v[16]
return vec3((v[1]*m.x + v[5]*m.y + v[9]*m.z + v[13])/l,(v[2]*m.x + v[6]*m.y + v[10]*m.z + v[14])/l,(v[3]*m.x + v[7]*m.y + v[11]*m.z + v[15])/l)
end
end
m["__add"] = function(a,b)
if is(a,"number") then a = vec3(a,a,a) end
if is(b,"number") then b = vec3(b,b,b) end
return af(a,b)
end
m["__sub"] = function(a,b)
if is(a,"number") then a = vec3(a,a,a) end
if is(b,"number") then b = vec3(b,b,b) end
return sw(a,b)
end
m["exp"] = qTangent
m = getmetatable(matrix())
mx,mr = m["__mul"],m["rotate"]
m["__mul"] = function (m,mm)
if is(m,matrix) and is(mm,matrix) then return mx(m,mm) end
if is(m,matrix) and is(mm,vec4) then return mx(m,mm:tomatrix()) end
if is(m,vec4) and is(mm,matrix) then return mx(m:tomatrix(),mm) end
if is(m,matrix) and is(mm,vec3) then
local l = m[13]*mm.x + m[14]*mm.y + m[15]*mm.z + m[16]
return vec3((m[1]*mm.x + m[2]*mm.y + m[3]*mm.z + m[4])/l,(m[5]*mm.x + m[6]*mm.y + m[7]*mm.z + m[8])/l,(m[9]*mm.x + m[10]*mm.y + m[11]*mm.z + m[12])/l)
end
if is(m,vec3) and is(mm,matrix) then
local l = mm[4]*m.x + mm[8]*m.y + mm[12]*m.z + mm[16]
return vec3((mm[1]*m.x + mm[5]*m.y + mm[9]*m.z + mm[13])/l,(mm[2]*m.x + mm[6]*m.y + mm[10]*m.z + mm[14])/l,(mm[3]*m.x + mm[7]*m.y + mm[11]*m.z + mm[15])/l)
end
end
m["rotate"] = function(m,a,x,y,z)
if is(a,vec4) then
a,x = a:toangleaxis()
x,y,z = x.x,x.y,x.z
end
return mr(m,a,x,y,z)
end
--# Radar
--Radar
--call this in setup
function SetupRadar()
radar={}
radar.range=1000 --number of pixels covered by scope radius
radar.radius=105
radar.pos=vec2(WIDTH-radar.radius-5,radar.radius+5)
radar.image=CreateRadarImage()
radar.objects={}
--table.insert(radar.objects,enemy) --insert objects which need to be tracked, like this
end
--create radar screen
function CreateRadarImage()
-- Codea does not automatically call this method
local img=image(radar.radius*2,radar.radius*2)
local r=radar.radius
setContext(img)
pushStyle()
fill(160, 182, 191, 50)
stroke(118, 154, 195, 100)
strokeWidth(1)
ellipse(r,r,2*r)
stroke(0,0,0,100)
line(r,r-10,r,r+5)
line(r-10,r,r+10,r)
line(r-4,r-10,r+4,r-10)
popStyle()
setContext()
return img
end
function DrawRadar()
ortho()
viewMatrix(matrix())
pushStyle()
spriteMode(CENTER)
sprite(radar.image,radar.pos.x,radar.pos.y)
fill(0,0,0,100)
local pv=plane.q:Position(vec3(0,0,-1))
pv=vec2(pv.x,-pv.z):normalize()
for i,o in pairs(radar.objects) do
local d=vec2(plane.p.x,plane.p.z):dist(vec2(o.p.x,o.p.z)) --distance
if d<=radar.range then --only display if on screen
d=d*radar.radius/radar.range --calculate distance scaled to radar screen
local ov=vec2(o.p.x-plane.p.x,-o.p.z+plane.p.z):normalize()
local v=vec2(ov.x-pv.x,1+ov.y-pv.y):normalize()*d
--print(v)
ellipse(radar.pos.x+v.x,radar.pos.y+v.y,6)
end
end
popStyle()
end
--# Sky
-- Sky globe
-- wraps two images around the top and bottom halves of a sphere to create sky and horizons
-- if you only want the sky (ie top half), leave out the second image
-- the images need to be twice as wide as they are high, and specially stretched to fit on a sphere
-- so search for them on the internet
--the code will uses the width of the first image to create an image with half that height
SkyGlobe = class()
function SkyGlobe:init(i1,i2) --i1 and i2 are the names of two images
--create image to wrap on sphere
local img1=readImage(i1) --get first image
local sky=image(img1.width,img1.width/2) --set up image to wrap on sphere
setContext(sky) --start drawing on it
spriteMode(CORNER)
sprite(img1,0,sky.height-img1.height) --draw one or two images, as provided
if i2 then sprite(i2,0,sky.height-img1.height*2) end
setContext()
--now the sphere
--the code below comes from Jmv38, who explains it here
--http://jmv38.comze.com/CODEAbis/server.php (look for 3D tutorial)
self.r=500 --arbitrary radius, it doesn't matter how big you make it, it looks the same
-- create mesh and colors
local vertices,tc = {},{}
vertices,tc = self:optimMesh({ nx=40, ny=20 })
vertices = self:warpVertices({verts=vertices, xangle=180, yangle=180 })
-- create the mesh itself
self.ms = mesh()
self.ms.vertices = vertices
self.ms.texture = sky
self.ms.texCoords = tc
end
function SkyGlobe:optimMesh(input)
-- create the mesh tables
local vertices = {}
local texCoords = {}
local k = 0
local s = 1
-- create a set of triangles with approx constant surface on a sphere
local x,y
local x1,x2 = {},{}
local i1,i2 = 0,0
local nx,ny = input.nx,input.ny
local sx, sy = nx/ny, 1/ny
local center = vec3(1,0.5,0)
local m1,m2,c
local flip = -1
for y=0,ny-1 do -- for each horizontal band
-- number of points on each side of the band
local nx1 = math.floor( nx * math.abs(math.cos(( y*sy-0.5)*2 * math.pi/2)))
if nx1<6 then nx1=6 end
local nx2 = math.floor( nx * math.abs(math.cos(((y+1)*sy-0.5)*2 * math.pi/2)))
if nx2<6 then nx2=6 end
-- points on each side of the band
x1,x2 = {},{}
for i1 = 1,nx1 do x1[i1] = (i1-1)/(nx1-1)*sx end
for i2 = 1,nx2 do x2[i2] = (i2-1)/(nx2-1)*sx end
x1[nx1+1] = x1[nx1] -- just a trick to manage last triangle without thinking
x2[nx2+1] = x2[nx2]
-- start on the left
local i1,i2 = 1,1
local continue = true
local n,nMax = 0,0
nMax = nx*2+1
while continue do
-- center of the 2 current segments
m1 = (x1[i1]+x1[i1+1])/2
m2 = (x2[i2]+x2[i2+1])/2
if m1<=m2 then -- the less advanced base makes the triangle
vertices[k+1] = vec3( x1[i1], sy*y , 1) - center
vertices[k+2] = vec3( x1[i1+1], sy*y , 1) - center
vertices[k+3] = vec3( x2[i2], sy*(y+1), 1) - center
texCoords[k+1] = vec2( x1[i1]/2*flip, sy*y )
texCoords[k+2] = vec2( x1[i1+1]/2*flip, sy*y )
texCoords[k+3] = vec2( x2[i2]/2*flip, sy*(y+1))
if i1<nx1 then i1 = i1 +1 end
else
vertices[k+1] = vec3( x1[i1], sy*y , 1) - center
vertices[k+2] = vec3( x2[i2], sy*(y+1), 1) - center
vertices[k+3] = vec3( x2[i2+1], sy*(y+1), 1) - center
texCoords[k+1] = vec2( x1[i1]/2*flip, sy*y )
texCoords[k+2] = vec2( x2[i2]/2*flip, sy*(y+1))
texCoords[k+3] = vec2( x2[i2+1]/2*flip, sy*(y+1))
if i2<nx2 then i2 = i2 +1 end
end
if c==1 then c=2 else c=1 end
if i1==nx1 and i2==nx2 then continue=false end
-- increment index for next triangle
k = k + 3
n = n + 1
if n>nMax then continue=false end -- just in case of infinite loop
end
end
return vertices,texCoords
end
function SkyGlobe:warpVertices(input)
-- move each vector to its position on sphere
local verts = input.verts
local xangle = -input.xangle/180*math.pi
local yangle = -input.yangle/180*math.pi
for i,v in ipairs(verts) do
verts[i] = vec3(0,0,1):rotate(xangle*v[2],1,0,0):rotate(yangle*v[1],0,1,0)
end
return verts
end
function SkyGlobe:draw()
pushMatrix()
scale(self.r)
self.ms:draw()
popMatrix()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment