Skip to content

Instantly share code, notes, and snippets.

@loopspace
Created January 19, 2014 22:42
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 loopspace/8511991 to your computer and use it in GitHub Desktop.
Save loopspace/8511991 to your computer and use it in GitHub Desktop.
Flying Release v1.7 -Using quaternions to fly a plane.
Flying Tab Order Version: 1.7
------------------------------
This file should not be included in the Codea project.
#Main
#JoyStick
#Quaternion
#Flight
#Plane
#Sky
--Flight class
Flight=class()
local yaw = vec3(0,1,0)
local pitchroll = vec3(1,0,1)
function Flight:init(m) --m is mode 1-3: 1=Absolute, 2=Increment, 3=Delta
self:Reset(m)
end
function Flight:Reset(m) --m is mode 1-3: 1=Absolute, 2=Increment, 3=Delta
self.mode=m
self.y=vec4(1,0,0,0)
self.pr=vec4(1,0,0,0)
end
function Flight:Turn(v,m)
m = m or self.mode
self.y = self.y * qTangent(v * yaw)
self.pr = self.pr * qTangent(v * pitchroll)
end
function Flight:Position(s) --d is vec3 distance in pixels
return s^(self.y*self.pr)
end
function Flight:Rotate()
rotate(self.y*self.pr)
end
function Flight:rotation()
return self.y*self.pr
end
function Flight:Interpolate()
local l = self.pr:slen()
local sl = self.pr:make_slerp()
return function(t)
if t > l then
self.pr = vec4(1,0,0,0)
return false
end
self.pr = sl(1-t/l)
return true
end
end
JoyStick = class()
function JoyStick:init(t)
t = t or {}
self.radius = t.radius or 100
self.stick = t.stick or 30
self.centre = t.centre or self.radius * vec2(1,1) + vec2(5,5)
self.position = vec2(0,0)
self.value = vec2(0,0)
self.oldvalue = vec2(0,0)
self.delta = vec2(0,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.position=v - self.centre
self.value=self.position/(self.radius-self.stick)
else --reset joystick to centre when touch ends
self.position=vec2(0,0)
self.value=vec2(0,0)
self.mytouch = false
end
return true
end
return false
end
function JoyStick:update()
self.delta = self.value - self.oldvalue
self.oldvalue = self.value
end
--v1.6
--the RotateAndDraw function needs code
VERSION = 1.7
function setup()
-- [[
if AutoGist then
autogist = AutoGist("Flying","Using quaternions to fly a plane.",VERSION)
autogist:backup(true)
end
-- ]]
--**Flight controls **
-- yaw = 0
-- mode not currently used
mode=vec3(0,1,0)
speed=vec3(0,0,-60) --pixels/sec --XXXXX
--** Plane settings **
plane=Plane() --plane mesh
planeQ=Flight(mode)--plane quaternion
planePos=vec3(0,0,0) --starting position
camOffset={vec3(0,3,-9),vec3(0,0,50),vec3(0,40,100)} --camera position relative to plane
camDir = {vec3(0,3,-10),vec3(0,0,-1),vec3(0,0,0)}
parameter.integer("state",1,3,1)
parameter.integer("View",1,#camOffset,1)
--** Joystick settings **
rotjoy = JoyStick({centre = vec2(105,105)})
mvjoy = JoyStick({centre = vec2(WIDTH - 105,105)})
--** rotation settings and limits **
--x, y, z map to pitch, yaw, roll
--limits for slave rotation
limit=vec3(90,90,90)*math.pi/180
--sensitivity of rotation for incremental rotation
sens=vec3(1,1,1)
-- planeQ:Position(speed*DeltaTime)
SetupSky()
cam = {
at = vec3(0,0,1),
frame = vec4(1,0,0,0),
vel = vec3(0,0,0),
avel = vec3(0,0,0)
}
-- parameter.watch("cam.avel")
parameter.watch("planeQ:rotation()")
parameter.watch("cam.frame")
--[[
local n = 2
local q = v:exp(1/n)
print(q^n)
]]
end
function draw()
rotjoy:update()
mvjoy:update()
background(179, 205, 220, 255)
fill(255)
perspective()
camera(0,0,100,0,0,0)
RotateAndDraw()
rotjoy:draw()
mvjoy:draw()
end
--this function adjusts the rotation based on joystick position, and draws the plane
function RotateAndDraw()
if rotjoy.mytouch then
local dv = vec3(-rotjoy.delta.y,-rotjoy.delta.x,-rotjoy.delta.x)
local vv = vec3(-rotjoy.value.y,-rotjoy.value.x,-rotjoy.value.x)
local v = (1 - mode) * dv * limit + mode * vv * sens * DeltaTime
planeQ:Turn(v,mode)
turning = true
elseif turning then
interpolating = true
turning = false
stime = ElapsedTime
slerpQ = planeQ:Interpolate()
elseif interpolating then
interpolating = slerpQ(ElapsedTime - stime)
end
if mvjoy.mytouch then
planePos=planePos+planeQ:Position(mvjoy.value.y*speed*DeltaTime)
end
-- --XXXX plane position, going in wrong vertical direction!
if state == 1 then
-- stationary camera
cam.at = camOffset[View] --planePos+camOffset --XXXX camera position - incorrect
cam.frame = vec4(1,0,0,0)
elseif state == 2 then
-- slavishly follows plane
cam.at = planePos + planeQ:Position(camOffset[View])
cam.frame = planeQ:rotation()
elseif state == 3 then
-- follows, but with a delay
local ca,cq = cam.at,cam.frame
cam.at = cam.at + cam.vel * DeltaTime
cam.frame = cam.avel:exp(DeltaTime) * cam.frame
-- cam.vel = cam.vel + (planePos + planeQ:Position(camOffset[View]) - ca) * DeltaTime - 1.5*cam.vel*DeltaTime
cam.vel = (planePos + planeQ:Position(camOffset[View]) - ca)
-- cam.avel = cam.avel + (planeQ:rotation() * cq^""):log()*DeltaTime - 1.5*cam.avel*DeltaTime
cam.avel = (planeQ:rotation() * cq^""):log() --*DeltaTime
end
local up,look = vec3(0,1,0)^cam.frame,planePos + planeQ:Position(camDir[View])
camera(cam.at,look,up)
pushMatrix()
DrawOtherStuff()
translate(planePos)
planeQ:Rotate()
plane:draw()
popMatrix()
end
--** Utility functions - joystick and touch ***
--manage joystick
function touched(t)
if not rotjoy:touched(t) and not mvjoy:touched(t) and t.state == BEGAN then
print(planeQ:rotation())
print(cam.frame)
print(cam.avel)
print(cam.avel:exp())
print(cam.avel:exp(DeltaTime^2))
print(DeltaTime^2)
end
end
function DrawOtherStuff() --XXXXX added just so we have frame of reference to see how the camera rotates
pushMatrix()
translate(0,0,-1000)
-- sprite("Planet Cute:Character Princess Girl",0,0,400)
popMatrix()
planet1:draw(planePos)
end
--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))
a[2]=Block(15,1,5,c2,vec3(-9,1,-6))
a[3]=Block(15,1,5,c2,vec3(9,1,-6))
a[4]=Block(1,4,3,c2,vec3(0,3.5,11))
a[5]=Block(4,1,2,c2,vec3(-3.5,1,11))
a[6]=Block(4,1,2,c2,vec3(3.5,1,11))
a[7]=Block(3,2,5,c3,vec3(0,2.5,-6))
a[8]=Block(3,3,1,c4,vec3(0,0,-13))
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
-- Quaternion library wherein vec4s are promoted to quaternions
-- Author: Andrew Stacey
-- Website: http://www.math.ntnu.no/~stacey/HowDidIDoThat/iPad/Codea.html
-- Licence: CC0 http://wiki.creativecommons.org/CC0
--[[
This provides code for handling rotations. Internally, the rotations
are implemented as quaternions which are in turn implemented by
extending the methods available for vec4 userdata.
Due to how vec4s are implemented, we cannot have methods beginning with
letters a, b, g, r, w, x, y, z
--]]
-- Simplistic error handling
local error = error or print
-- Localise the maths functions/constants that we use for faster lookup.
local abs = math.abs
local pow = math.pow
local sqrt = math.sqrt
local sin = math.sin
local cos = math.cos
local acos = math.acos
local asin = math.asin
local pi = math.pi
local floor = math.floor
--[[
The function "is_a" extends the capabilities of the method "is_a" which is automatically defined by Codea for classes.
Parameters:
a: object to be tested
b: test
The tests work as follows.
1. If the type of b is a string, it is taken as the name of a type to test a against.
2. If the type of b is a table, it is assumed to be a class to test if a is an instance thereof.
3. If the type of b is a userdata, the test is to see if a is the same type of object.
4. If b is a function, then it is replaced by the value of that function.
--]]
local function is_a(a,b)
if type(b) == "function" then
b = b()
end
if type(b) == "string" then
return type(a) == b
end
if type(b) == "table" then
if type(a) == "table"
and a.is_a
and a:is_a(b) then
return true
else
return false
end
end
if type(b) == "userdata" then
if type(a) == "userdata" then
a = getmetatable(a)
b = getmetatable(b)
return a == b
else
return false
end
end
return false
end
-- Import the metatable of vec4s for extending
local mtq = getmetatable(vec4())
-- We run into difficulties if our vec4s contain NaNs or infs. This
-- tests for those.
mtq["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
--[[
Test if we are real.
--]]
mtq["is_real"] = function (self)
if self.y ~= 0 or self.z ~= 0 or self.w ~= 0 then
return false
end
return true
end
--[[
Test if the real part is zero.
--]]
mtq["is_imaginary"] = function (self)
if self.x ~= 0 then
return false
end
return true
end
--[[
Normalise a quaternion to have length 1, safely. Here "safely" means
that we ensure that we do not have NaNs or infs. If we do, the
returned quaternion is the unit.
--]]
mtq["normalise"] = function (q)
q = q:normalize()
if q:is_finite() then
return q
else
return vec4(1,0,0,0)
end
end
--[[
Spherical length and distance.
--]]
mtq["slen"] = function(q)
q = q:normalise()
q.x = q.x - 1
return 2*asin(q:len()/2)
end
mtq["sdist"] = function(q,qq)
q = q:normalise()
qq = qq:normalise()
return 2*asin(q:dist(qq)/2)
end
--[[
Add two quaternions inline, including promotion of a number.
q + p
--]]
local __add = mtq["__add"]
mtq["__add"] = function (a,b)
if is_a(a,"number") then
a = vec4(a,0,0,0)
end
if is_a(b,"number") then
b = vec4(b,0,0,0)
end
return __add(a,b)
end
--[[
Same for inline subtraction.
q - p
--]]
local __sub = mtq["__sub"]
mtq["__sub"] = function (a,b)
if is_a(a,"number") then
a = vec4(a,0,0,0)
end
if is_a(b,"number") then
b = vec4(b,0,0,0)
end
return __sub(a,b)
end
--[[
For inline multiplication, we also allow multiplication by a matrix.
In this case, we use the fact that we're viewing quaternions as
rotations and matrices as affine transformations, so the best result
of multiplication is as the matrix representing the appropriate
composition.
q * p
--]]
local __mul = mtq["__mul"]
mtq["__mul"] = function (a,b)
if is_a(a,"number") then
return __mul(a,b)
end
if is_a(b,"number") then
return __mul(a,b)
end
if is_a(a,matrix) then
return a:__mul(b:tomatrixleft())
end
if is_a(b,matrix) then
a = a:tomatrixleft()
return a:__mul(b)
end
local x,y,z,w
x = a.x * b.x - a.y * b.y - a.z * b.z - a.w * b.w
y = a.x * b.y + a.y * b.x + a.z * b.w - a.w * b.z
z = a.x * b.z - a.y * b.w + a.z * b.x + a.w * b.y
w = a.x * b.w + a.y * b.z - a.z * b.y + a.w * b.x
return vec4(x,y,z,w)
end
--[[
Conjugation (corresponds to inverting a rotation).
--]]
mtq["conjugate"] = function (q)
return vec4(q.x, - q.y, - q.z, - q.w)
end
mtq["co"] = mtq["conjugate"]
--[[
Inline division, including division of and by a number.
--]]
local __div = mtq["__div"]
mtq["__div"] = function (a,b)
if is_a(b,"number") then
return __div(a,b)
end
local l = b:lenSqr()
b = vec4(b.x/l,-b.y/l,-b.z/l,-b.w/l)
if is_a(a,"number") then
return vec4(a*b.x,a*b.y,a*b.z,a*b.w)
end
local x,y,z,w
x = a.x * b.x - a.y * b.y - a.z * b.z - a.w * b.w
y = a.x * b.y + a.y * b.x + a.z * b.w - a.w * b.z
z = a.x * b.z - a.y * b.w + a.z * b.x + a.w * b.y
w = a.x * b.w + a.y * b.z - a.z * b.y + a.w * b.x
return vec4(x,y,z,w)
end
--[[
Powers. Integer powers are defined iteratively. Non-integer powers
use the slerp function.
TODO: test whether the slerp is faster than the iterative method for
integer powers as well.
--]]
local function intpower(q,n)
if n ~= floor(n) then
error("Only able to do integer powers")
return false
end
if n == 0 then
return vec4(1,0,0,0)
elseif n > 0 then
return q:__mul(intpower(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:intpower(-n)
end
end
local function power(q,n)
if n == floor(n) then
return intpower(q,n)
end
local l = q:len()
q = q:normalise()
return l^n * q:slerp(n)
end
--[[
Inline exponentiation.
q^n
The behaviour depends on the type of the exponent:
* number: compute the power.
* vec4: conjugate by the exponent.
* other: return the conjugate.
--]]
mtq["__pow"] = function (q,n)
if is_a(n,"number") then
return power(q,n)
elseif is_a(n,vec4) then
return n:__mul(q):__div(n)
else
return q:conjugate()
end
end
--[[
Interpolation functions, we assume the input to be already normalised
for speed. If you cannot guarantee this, renormalise the input first.
The constructor functions do do the renormalisation.
Parameters:
q initial rotation
qq final rotation
t parameter (only for the direct functions)
If the input does not provide enough parameters, it is assumed that
the missing one is the initial rotation and that this should be taken
to be the identity rotation (represented by vec4(1,0,0,0)).
--]]
--[[
Linear interpolation, renormalised.
--]]
mtq["lerp"] = function (q,qq,t)
if not t then
return vec4(1,0,0,0):lerp(q,qq)
end
local v
if (q + qq):len() == 0 then
-- antipodal points, need a midpoint
v = vec4(q.y,-q.x,q.w,-q.z)
v = (1 - 2*t)*q + (1-abs(2*t-1))*v
else
v = (1-t)*q + t*qq
end
return v:normalise()
end
--[[
Spherical interpolation.
We have to be quite careful here not to emit anything with NaNs or
infs as there are several opportunities to create them due to the
limits of finite precision mathematics.
--]]
mtq["slerp"] = function (q,qq,t)
if not t then
return vec4(1,0,0,0):slerp(q,qq)
end
local v
if (q + qq):len() == 0 then
-- antipodal points, need a midpoint
v = vec4(q.y,-q.x,q.w,-q.z)
t = 2*t
elseif (q - qq):len() == 0 then
return q
else
v = qq
end
local ca = q:dot(v)
local sa = sqrt(1 - pow(ca,2))
if sa == 0 or sa ~= sa then
return q
end
local a = acos(ca)
sa = sin(a*t)/sa
v = (cos(a*t) - ca*sa)*q+ sa*v
return v
end
--[[
Constructor for normalised linear interpolation.
--]]
mtq["make_lerp"] = function (q,qq)
if not qq then
return vec4(1,0,0,0):make_lerp(q)
end
local v,w
w = q:normalise()
if (q + qq):len() == 0 then
-- antipodal points, need a midpoint
v = vec4(w.y,-w.x,w.w,-w.z)
return function(t)
local u = (1 - 2*t)*w + (1-abs(2*t-1))*v
return u:normalise()
end
else
v = qq:normalise()
return function(t)
local u = (1-t)*w + t*v
return u:normalise()
end
end
end
--[[
Spherical interpolation
--]]
mtq["make_slerp"] = function (q,qq)
if not qq then
q,qq = vec4(1,0,0,0),q
end
local v,f,u
if (q + qq):len() == 0 then
-- antipodal points, need a midpoint
v = vec4(q.y,-q.x,q.w,-q.z)
f = 2
elseif (q - qq):len() == 0 then
return function(t)
return q
end
else
v = qq
f = 1
end
v = v:normalise()
u = q:normalise()
local ca = u:dot(v)
local sa = sqrt(1 - pow(ca,2))
if sa == 0 or sa ~= sa then
return function(t)
return q
end
end
local a = acos(ca)
v = (v - ca*q)/sa
return function(t)
return cos(a*f*t)*q + sin(a*f*t)*v
end
end
--[[
Returns the real part.
--]]
mtq["toreal"] = function (q)
return q.x
end
--[[
Returns the vector (imaginary) part as a vec3 object.
--]]
mtq["vector"] = function (q)
return vec3(q.y, q.z, q.w)
end
mtq["tovector"] = mtq["vector"]
mtq["log"] = function (q)
local l = q:slen()
local v = q:tovector()
v = v:normalize()
if not v:is_finite() then
return vec3(0,0,0)
else
return v * l
end
end
--[[
Represents a quaternion as a string.
--]]
mtq["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
if s then
return s
else
return "0"
end
end
mtq["__concat"] = function (q,s)
if is_a(s,"string") then
return q:tostring() .. s
else
return q .. s:tostring()
end
end
--[[
Converts the quaternion to a matrix.
We distinguish between left and right actions. A rotation matrix
acting on the right is the transpose of the matrix on the left.
--]]
mtq["tomatrixleft"] = function (q)
q = q:normalise()
local a,b,c,d = q.x,q.y,q.z,q.w
local ab = 2*a*b
local ac = 2*a*c
local ad = 2*a*d
local bb = 2*b*b
local bc = 2*b*c
local bd = 2*b*d
local cc = 2*c*c
local cd = 2*c*d
local dd = 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
mtq["tomatrixright"] = function (q)
q = q:normalise()
local a,b,c,d = q.x,-q.y,-q.z,-q.w
local ab = 2*a*b
local ac = 2*a*c
local ad = 2*a*d
local bb = 2*b*b
local bc = 2*b*c
local bd = 2*b*d
local cc = 2*c*c
local cd = 2*c*d
local dd = 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
mtq["tomatrix"] = mtq["tomatrixright"]
--[[
Converts the quaternion to an "angle-axis" representation.
--]]
mtq["toangleaxis"] = function (q)
q = q:normalise()
local a = 2*acos(q.x)
local v = vec3(q.y,q.z,q.w)
if v == vec3(0,0,0) then
return 0,vec3(0,0,1)
end
return a,v:normalise()
end
--[[
The following code modifies various of Codea's functions to enable
them to take a quaternion as input. We have to be careful as for some
passing nil is distinct to passing an empty input.
The other issue is as to the distinction between left and right
actions of matrices. In OpenGL, matrices act on the right. However,
in standard linear algebra, the convention is for matrices to act on
the left.
--]]
local __modelMatrix = modelMatrix
function modelMatrix(m)
if m then
if is_a(m,vec4) then
m = m:tomatrixright()
end
return __modelMatrix(m)
else
return __modelMatrix()
end
end
local __applyMatrix = applyMatrix
function applyMatrix(m)
if m then
if is_a(m,vec4) then
m = m:tomatrixright()
end
return __applyMatrix(m)
else
return __applyMatrix()
end
end
local __viewMatrix = viewMatrix
function viewMatrix(m)
if m then
if is_a(m,vec4) then
m = m:tomatrixright()
end
return __viewMatrix(m)
else
return __viewMatrix()
end
end
local __projectionMatrix = projectionMatrix
function projectionMatrix(m)
if m then
if is_a(m,vec4) then
m = m:tomatrixright()
end
return __projectionMatrix(m)
else
return __projectionMatrix()
end
end
local __rotate = rotate
function rotate(a,x,y,z)
if is_a(a,vec4) then
local v
a,v = a:toangleaxis()
x,y,z = v.x,v.y,v.z
a = a*180/pi
end
__rotate(a,x,y,z)
end
local __translate = translate
function translate(x,y,z)
if not y then
x,y,z = x.x,x.y,x.z
end
__translate(x,y,z)
end
local __scale = scale
function scale(a,b,c)
if is_a(a,vec3) then
a,b,c = a.x,a.y,a.z
end
if c then
return __scale(a,b,c)
end
if b then
return __scale(a,b)
end
if a then
return __scale(a)
end
end
local __camera = camera
function camera(a,b,c,d,e,f,g,h,i)
if is_a(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_a(d,vec3) then
d,e,f,g,h,i = d.x,d.y,d.z,e,f,g
end
if is_a(g,vec3) then
g,h,i = g.x,g.y,g.z
end
if g then
return __camera(a,b,c,d,e,f,g,h,i)
elseif d then
return __camera(a,b,c,d,e,f)
elseif a then
return __camera(a,b,c)
else
return __camera()
end
end
--[[
We also define some extensions to the vec3 type.
--]]
local mt = getmetatable(vec3())
-- Same as for vec4.
mt["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
--[[
Promote to a quaternion with 0 real part.
--]]
mt["toQuaternion"] = function (v)
return vec4(0,v.x,v.y,v.z)
end
--[[
Apply a quaternion as a rotation (assumes unit quaternion for speed)
using conjugation.
--]]
mt["applyQuaternion"] = function (v,q)
v = v:toQuaternion()
v = q:__mul(v)
v = v:__mul(q:conjugate())
return v:vector()
end
-- There is no native rotate method for a vec3.
mt["rotate"] = function(v,q,x,y,z)
if is_a(q,"number") then
q = qRotation(q,x,y,z)
end
return v:applyQuaternion(q)
end
-- We use the exponential notation for writing the action of a
-- quaternion on a vector (this is consistent with group theory
-- notation).
mt["__pow"] = function (v,q)
if is_a(q,vec4) then
return v:applyQuaternion(q)
end
return false
end
mt["__concat"] = function (u,s)
if is_a(s,"string") then
return u:__tostring() .. s
else
return u .. s:__tostring()
end
end
--[[
The rotateTo method produces a rotation that rotates the first vector
to the second about an axis that is perpendicular to both. So long as
the two are not collinear, the axis is unique. The angle is taken to
be the smallest angle. If the vectors point in opposite directions,
an orthogonal axis is chosen in such a way as to minimise precision
error.
TODO: Allow for a third parameter to specify the axis in case of
ambiguity.
--]]
mt["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
-- Opposite vectors, no canonical direction
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
--[[
Safe renormalisation, as for quaternions. Except that there is no
"cannonical" unit vec3, so we return the unit vector in the z
direction. The rationale for that is that if this is an axis, this
vector represents the axis out of the screen.
--]]
mt["normalise"] = function (v)
v = v:normalize()
if v:is_finite() then
return v
else
return vec3(0,0,1)
end
end
--[[
Inline multiplication extended to allow for multiplication by a matrix.
We interpret the vec3 as vertical or horizontal depending on whether
the matrix is on the right or left. Multiplication by a matrix is
viewed as applying an affine transformation.
--]]
local __mulv = mt["__mul"]
mt["__mul"] = function(m,v)
if is_a(m,vec3) and is_a(v,"number") then
return __mulv(m,v)
end
if is_a(m,"number") and is_a(v,vec3) then
return __mulv(m,v)
end
if is_a(m,vec3) and is_a(v,vec3) then
return vec3(m.x*v.x,m.y*v.y,m.z*v.z)
end
if is_a(m,matrix) and is_a(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_a(m,vec3) and is_a(v,matrix) then
m,v = v,m
local l = m[4]*v.x + m[8]*v.y + m[12]*v.z + m[16]
return vec3(
(m[1]*v.x + m[5]*v.y + m[9]*v.z + m[13])/l,
(m[2]*v.x + m[6]*v.y + m[10]*v.z + m[14])/l,
(m[3]*v.x + m[7]*v.y + m[11]*v.z + m[15])/l
)
end
end
local __addv = mt["__add"]
mt["__add"] = function(a,b)
if is_a(a,"number") then
a = vec3(a,a,a)
end
if is_a(b,"number") then
b = vec3(b,b,b)
end
return __addv(a,b)
end
local __subv = mt["__sub"]
mt["__sub"] = function(a,b)
if is_a(a,"number") then
a = vec3(a,a,a)
end
if is_a(b,"number") then
b = vec3(b,b,b)
end
return __subv(a,b)
end
--[[
Extensions to the matrix class.
--]]
local mtm = getmetatable(matrix())
--[[
Inline multiplication by either quaternion or vector.
--]]
local __mulm = mtm["__mul"]
mtm["__mul"] = function (m,mm)
if is_a(m,matrix) and is_a(mm,matrix) then
return __mulm(m,mm)
end
if is_a(m,matrix) and is_a(mm,vec4) then
return __mulm(m,mm:tomatrix())
end
if is_a(m,vec4) and is_a(mm,matrix) then
return __mulm(m:tomatrix(),mm)
end
if is_a(m,matrix) and is_a(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_a(m,vec3) and is_a(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
-- Extending the rotate method to take a quaternion.
local __mrotate = mtm["rotate"]
mtm["rotate"] = function(m,a,x,y,z)
if is_a(a,vec4) then
a,x = a:toangleaxis()
x,y,z = x.x,x.y,x.z
end
return __mrotate(m,a,x,y,z)
end
--[[
The following functions are intended to be more "user friendly" and
provide solutions to common problems.
The primary functions construct a rotation dependent on some initial
data. The simplest are to define a rotation from some other method of
specifying rotations: angle-axis or Euler angles. More complicated
are methods to define a rotation by giving two (orthogonal) frames and
computing the rotation to get from the first to the last. In
particular, there are certain common frames that might be expected to
be used frequently:
1. The initial orientation of the iPad.
2. A gravitational frame wherein the Gravity vector is "straight
down". This is not completely defined as there is currently no way to
choose a corresponding horizontal direction (access to the compass
would provide this). Two possibilities are: to choose the x-axis so
that it is always in the plane of the iPad, and to use RotationRate to
try to keep track of the initial x-axis.
3. A "DeltaOrientation" in which the change in orientation from one
frame to the next is used (this uses RotationRate).
There are obvious limits in accuracy in using these.
--]]
--[[
Returns a quaternion corresponding to the current gravitational vector
so that after applying the corresponding rotation, the y-axis points
in the gravitational direction and the x-axis is in the plane of the
iPad screen.
When we have access to the compass, the x-axis behaviour might change.
--]]
local function qGravity()
local gxy, gy, gygxy, a, b, c, d
if Gravity.x == 0 and Gravity.y == 0 then
return vec4(1,0,0,0)
else
gy = - Gravity.y
gxy = sqrt(pow(Gravity.x,2) + pow(Gravity.y,2))
gygxy = gy/gxy
a = sqrt(1 + gxy - gygxy - gy)/2
b = sqrt(1 - gxy - gygxy + gy)/2
c = sqrt(1 - gxy + gygxy - gy)/2
d = sqrt(1 + gxy + gygxy + gy)/2
if Gravity.y > 0 then
a = a
b = b
end
if Gravity.z < 0 then
b = - b
c = - c
end
if Gravity.x > 0 then
c = - c
d = - d
end
return vec4(a,b,c,d)
end
end
function qrGravity(q)
local qg,qx
if not q then
qx = ReferenceFrame()
else
qx = q
end
local y = vec3(0,-1,0)^qx
qg = y:rotateTo(Gravity)
return qg*qx
end
mtq["gravity"] = qrGravity
local frame = {
vec3(1,0,0),
vec3(0,1,0),
vec3(0,0,1)
}
local qzyx = vec4(1,0,0,0)
--[[
Needs to be run once every frame!
--]]
function updateReferenceFrame(q)
local x,y,z
--[[
if CurrentOrientation == PORTRAIT then
x,y,z = -frame[1],-frame[2],-frame[3]
elseif CurrentOrientation == PORTRAIT_UPSIDE_DOWN then
x,y,z = frame[1],frame[2],-frame[3]
elseif CurrentOrientation == LANDSCAPE_LEFT then
x,y,z = frame[2],-frame[1],-frame[3]
elseif CurrentOrientation == LANDSCAPE_RIGHT then
x,y,z = -frame[2],frame[1],-frame[3]
end
--]]
x,y,z = unpack(frame)
local qz = qRotation(RotationRate.z*DeltaTime,z.x,z.y,z.z)
local qy = qRotation(RotationRate.y*DeltaTime,y.x,y.y,y.z)
local qx = qRotation(RotationRate.x*DeltaTime,x.x,x.y,x.z)
if q then
local qq = qz * qy * qx * q
q.x,q.y,q.z,q.w = qq.x,qq.y,qq.z,qq.w
else
qzyx = qz * qy * qx * qzyx
return qzyx
end
end
function ReferenceFrame(q)
return q or qzyx
end
mtq["updateReferenceFrame"] = updateReferenceFrame
mtq["ReferenceFrame"] = ReferenceFrame
--[[
Converts a rotation to a quaternion. The first argument is the angle
to rotate, the rest must specify an axis, either as a Vec3 object or
as three numbers.
--]]
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)
q = q:normalise()
if q == vec4(1,0,0,0) then
return q
end
c = cos(a/2)
s = sin(a/2)
q = q:__mul(s)
q = q:__add(c)
return q
end
--[[
The qEuler function handles conversion from Euler angles. As there
are many ways to specify Euler angles, we have to allow for quite a
variety. The table __euler contains the code necessary for allowing
all of this flexibility.
The last parameter is the specification, which is a table taken from
the alphabet "xXyYzZ". This ought to correspond to the axes of
rotation, using lowercase to mean the absolute frame and uppercase to
mean the transformed frame.
TODO: Test this.
If only two parameters are passed, the first is taken to be a vec3 or
a table containing the Euler angles.
--]]
local __euler = {}
__euler.x = function(q)
return vec3(1,0,0)^q
end
__euler.X = function(q)
return vec3(1,0,0)
end
__euler.y = function(q)
return vec3(0,1,0)^q
end
__euler.Y = function(q)
return vec3(0,1,0)
end
__euler.z = function(q)
return vec3(0,0,1)^q
end
__euler.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(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)
local w
for k,u in ipairs(v) do
w = __euler[u](q)
q = q * qRotation(a[k],w)
end
return q
end
-- v is a tangent vector at the identity
function qTangent(x,y,z,t)
local q
if is_a(x,"number") then
q = vec4(0,x,y,z)
t = t or 1
else
q = vec4(0,x.x,x.y,x.z)
t = y or 1
end
local qn = q:normalise()
if qn == vec4(1,0,0,0) then
return qn
end
local a = t * q:len() --/2
return cos(a)*vec4(1,0,0,0) + sin(a)*qn
end
mt["exp"] = qTangent
function qRotationRate()
return qTangent(DeltaTime * RotationRate)
end
local iGravity = vec3(0,-1,0)
Frame = {
Roll = vec4(1,0,0,0),
Pitch = vec4(1,0,0,0),
Yaw = vec4(1,0,0,0),
Gravity = vec4(1,0,0,0),
Rotation = vec4(1,0,0,0),
AdjustedRotation = vec4(1,0,0,0),
RotationRate = vec4(1,0,0,0)
}
local update_fn
update_fn = function()
local q = vec3(iGravity.x,iGravity.y,0):rotateTo(
vec3(Gravity.x,Gravity.y,0))
Frame.Roll.x = q.x
Frame.Roll.y = q.y
Frame.Roll.z = q.z
Frame.Roll.w = q.w
q = vec3(0,iGravity.y,iGravity.z):rotateTo(
vec3(0,Gravity.y,Gravity.z))
Frame.Pitch.x = q.x
Frame.Pitch.y = q.y
Frame.Pitch.z = q.z
Frame.Pitch.w = q.w
q = vec3(iGravity.x,0,iGravity.z):rotateTo(
vec3(Gravity.x,0,Gravity.z))
Frame.Yaw.x = q.x
Frame.Yaw.y = q.y
Frame.Yaw.z = q.z
Frame.Yaw.w = q.w
q = iGravity:rotateTo(Gravity)
Frame.Gravity.x = q.x
Frame.Gravity.y = q.y
Frame.Gravity.z = q.z
Frame.Gravity.w = q.w
q = vec4(0,RotationRate.x,RotationRate.y,RotationRate.z)
local qn = q:normalise()
if qn ~= vec4(1,0,0,0) then
local a = q:len()*DeltaTime/2
qn = cos(a)*vec4(1,0,0,0) + sin(a)*qn
end
Frame.RotationRate.x = qn.x
Frame.RotationRate.y = qn.y
Frame.RotationRate.z = qn.z
Frame.RotationRate.w = qn.w
q = qn*Frame.Rotation
Frame.Rotation.x = q.x
Frame.Rotation.y = q.y
Frame.Rotation.z = q.z
Frame.Rotation.w = q.w
q = qn*Frame.AdjustedRotation
local g = iGravity^(q)
qn = g:rotateTo(iGravity)
q = q*qn
Frame.AdjustedRotation.x = q.x
Frame.AdjustedRotation.y = q.y
Frame.AdjustedRotation.z = q.z
Frame.AdjustedRotation.w = q.w
tween.delay(0,update_fn)
end
tween.delay(.5,function()
iGravity = vec3(Gravity.x,Gravity.y,Gravity.z)
tween.delay(0,update_fn)
end)
--[[
A suite of test functions.
--]]
function testRotations()
print("Rotation tests:")
local tolerance = 10^(-6)
local vars = [[local q1,q2,i,j,k
q1 = vec4(1,2,3,4)
q2 = vec4(.5,.5,-.5,-.5)
i = vec4(0,1,0,0)
j = vec4(0,0,1,0)
k = vec4(0,0,0,1)
q3 = vec4(1/math.sqrt(2),0,0,1/math.sqrt(2))
v1 = vec3(1,0,0)
v2 = vec3(0,1,0)
v3 = vec3(1,-1,2)
m = matrix(
10,10,5,1,
4,6,4,1,
1,3,3,1,
0,1,2,1
)
return ]]
local tests = {
{"q1 * q2", vec4(3,2,4,-1)},
{"q1 + q2", vec4(1.5,2.5,2.5,3.5)},
{"q1 - q2", vec4(.5,1.5,3.5,4.5)},
{"q1 / q2", vec4(-2,0,-1,5)},
{"q1^q2", vec4(1,-4,-2,3)},
{"q1^\"\"", vec4(1,-2,-3,-4)},
{"i * j", vec4(0,0,0,1)},
{"j * k", vec4(0,1,0,0)},
{"k * i", vec4(0,0,1,0)},
{"v1^q3", vec3(0,1,0)},
{"q3:tomatrixleft()*v1", vec3(0,1,0)},
{"v1:rotate(q3)", vec3(0,1,0)},
{"v2^q3", vec3(-1,0,0)},
{"q3:tomatrixleft()*v2", vec3(-1,0,0)},
{"v2:rotate(q3)", vec3(-1,0,0)},
{"qRotation(math.pi/2,0,0,1)", vec4(1/math.sqrt(2),0,0,1/math.sqrt(2))},
{"v1:rotateTo(v2)", vec4(1/math.sqrt(2),0,0,1/math.sqrt(2))},
{"v3 * m", vec3(8,11,9)/3},
{"m * v3", vec3(11,7,5)/4}
}
local t,a,m,n,tn
tn,n = 0,0
for k,u in ipairs(tests) do
tn = tn + 1
t = loadstring(vars .. u[1])
a = t()
if a == u[2] then
m = "OK"
n = n + 1
elseif a.dist and a:dist(u[2]) < tolerance then
m = "OK (" .. a:dist(u[2]) .. ")"
n = n + 1
else
m = "not OK (expected " .. u[2] .. ")"
end
print(u[1] .. " = " .. a .. " : " .. m)
end
print(n .. " tests passed out of " .. tn)
end
-- Sky globe
--displayMode(FULLSCREEN)
-- supportedOrientations(LANDSCAPE_ANY)
function SetupSky()
img1=readImage("Documents:SkyDome")
img2=image(img1.width,img1.width/2)
setContext(img2)
spriteMode(CORNER)
sprite(img1,0,img2.height-img1.height)
sprite(img1,0,img2.height-img1.height-img1.height)
setContext()
local color1 = color(255, 255, 255, 255)
--this sphere code comes from Jmv38, see bottom
planet1 = Sphere({
nx = 40, ny = 20 , -- mesh definition
meshOptimize = true, -- optimize mesh for sphere
c1 = color1 , c2 = color1 , -- mesh colors
centre = vec3(0,0,0) , -- sphere center
r = 500 , -- radius of the sphere
rotTime1 = 20 , -- rotation time in s
hflip = true, -- to flip image horozontally
})
cam = vec3(0, 50, 300)
planet1.ms.texture=img2
--parameter.number("FPS",0,60,60)
end
--the code below comes from Jmv38, who explains it here
--http://jmv38.comze.com/CODEAbis/server.php (look for 3D tutorial)
Sphere = class()
function Sphere:init(input)
-- spatial position of sphere
self.centre = input.centre or vec3(0,0,0)
-- angular position of sphere, defined by angles around x,y,z axis
self.angle = vec3(0,0,0)
-- sphere radius and rotation
self.radius = input.r
self.tRot = input.rotTime1
-- sphere rotation 2
self.tRot2 = input.rotTime2
self.centre2 = input.centre2 or vec3(0,0,0) -- center of rotation 2
self.axis2 = input.angle2 or vec3(0,1,0) -- axis of rotation 2
-- mesh definition
self.nx = input.nx -- number of triangles in x
self.ny = input.ny -- and in y
self.c1 = input.c1 -- 2 color() objects, to see the triangles
self.c2 = input.c2
self.optimized = input.meshOptimize -- boolean
-- sphere decoration
self.url = input.url -- texture as a url (text)
self.hflip = input.hflip -- to flip image horizontally
if input.lightDir then
self.lightDir = input.lightDir:normalize() -- a vec3 pointing to the sun
end
self.shadowRatio = input.shadowRatio or 1.05 -- close to 1.05
-- create mesh and colors
local vertices,colors,tc = {},{},{}
if self.optimized then
vertices,colors,tc = self:optimMesh({ nx=self.nx, ny=self.ny, c1=self.c1, c2=self.c2 })
else
vertices,colors,tc = self:simpleMesh({ nx=self.nx, ny=self.ny, c1=self.c1, c2=self.c2 })
end
-- if a radius is given, warp to a sphere
if self.radius then
vertices = self:warpVertices({
verts=vertices,
xangle=180,
yangle=180
}) end
-- create the mesh itself
self.ms = mesh()
self.ms.vertices = vertices
self.ms.colors = colors
-- add the texture from internet
--if self.url then
-- self:load( self.url ) -- this will not be instantaneous!
--end
self.ms.texture = self.url
self.ms.texCoords = tc
-- add some shadows
if self.lightDir then self:shadows() end
end
function Sphere:shadows()
self.ms2 = mesh()
local dir = self.lightDir
local vertices2,colors2 = {},{}
local d = 0
for i,v in ipairs(self.ms.vertices) do
vertices2[i] = v
d = v:dot(dir)
d = 128 - 4*(d-0.1)*128
if d<0 then d=0 end
if d>255 then d=255 end
colors2[i] = color(0,0,0,d)
end
self.ms2.vertices = vertices2
self.ms2.colors = colors2
end
function Sphere:simpleMesh(input)
-- create the mesh tables
local vertices = {}
local colors = {}
local texCoords = {}
--local w,h = img.width/10, img.height/10
local k = 0
local s = 1
-- create a rectangular set of triangles
local x,y
local nx,ny = input.nx,input.ny
local opt = input.opt
local sx, sy = 1/ny, 1/ny
local color1 = input.c1
local color2 = input.c2
local center = vec3(1,0.5,0)
for y=0,ny-1 do
for x=0,nx-1 do
vertices[k+1] = vec3( sx*x , sy*y , 1) - center
vertices[k+2] = vec3( sx*(x+1), sy*y , 1) - center
vertices[k+3] = vec3( sx*(x+1), sy*(y+1), 1) - center
vertices[k+4] = vec3( sx*x , sy*y , 1) - center
vertices[k+5] = vec3( sx*x , sy*(y+1), 1) - center
vertices[k+6] = vec3( sx*(x+1), sy*(y+1), 1) - center
colors[k+1] = color1
colors[k+2] = color1
colors[k+3] = color1
colors[k+4] = color2
colors[k+5] = color2
colors[k+6] = color2
k = k + 6
end
end
return vertices,colors
end
function Sphere:optimMesh(input)
-- create the mesh tables
local vertices = {}
local colors = {}
local texCoords = {}
--local w,h = img.width/10, img.height/10
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 color1 = input.c1
local color2 = input.c2
local center = vec3(1,0.5,0)
local m1,m2,c
local flip = 1
if self.hflip then flip=-1 end
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
c = 1 -- starting color
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
-- set the triangle color
if c==1 then col=color1 else col=color2 end
colors[k+1] = col
colors[k+2] = col
colors[k+3] = col
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,colors,texCoords
end
function Sphere:warpVertices(input)
-- move each vector to its position on sphere
local verts = input.verts
local rad = 180/math.pi
local xangle = -input.xangle/rad
local yangle = -input.yangle/rad
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 Sphere:draw(c,s)
c = c or self.centre
s = s or self.radius or 100
pushMatrix()
translate(c)
scale(s)
self.ms:draw()
popMatrix()
end
function SetupCloud()
cloud=mesh()
local x,y=300,200
cloud:addRect(0,0,x,y)
cc=1
counter=125
end
function DrawCloud()
if counter+cc>240 or counter+cc<125 or math.random()<0.01 then cc=-cc end
counter=counter+cc
cloud:setColors(color(255,255,255,counter))
pushMatrix() translate(0,0,-40) cloud:draw() popMatrix()
pushMatrix() translate(0,0,10) cloud:draw() popMatrix()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment