Skip to content

Instantly share code, notes, and snippets.

@sp4cemonkey
Created February 13, 2013 09:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sp4cemonkey/4943400 to your computer and use it in GitHub Desktop.
Save sp4cemonkey/4943400 to your computer and use it in GitHub Desktop.
--# Main
-- Use this function to perform your initial setup
function setup()
displayMode(STANDARD)
availableTests = { Test2(), Test1() }
currentTest = availableTests[1]
iparameter("SceneSelect",1,#availableTests,1)
parameter("Size",50,500,150)
iparameter("CamType", 1, 2, 1)
parameter("Angle",-360, 360, 0)
parameter("FieldOfView", 10, 140, 45)
the3DViewMatrix = viewMatrix()
watch("the3DViewMatrix")
-- These watches are evaluated after draw() is finished
-- Thus the contents are not as interesting
watch("viewMatrix()")
watch("modelMatrix()")
watch("projectionMatrix()")
camPos = vec3(0,0,-300)
camDir = vec3(0,0,1)
camUp = vec3(0,1,0)
watch("camUp")
watch("camDir")
watch("horizontal")
end
-- This function gets called once every frame
function draw()
-- Set the currentTest to the selected scene
currentTest = availableTests[SceneSelect]
-- First arg is FOV, second is aspect
perspective(FieldOfView, WIDTH/HEIGHT)
-- Position the camera up and back, look at origin
camera(camPos.x, camPos.y, camPos.z,camDir.x+camPos.x,camDir.y+camPos.y,camDir.z+camPos.z,camUp.x,camUp.y,camUp.z)
-- Write this into a variable so we can watch() it
-- at this point in time
the3DViewMatrix = viewMatrix()
-- This sets a dark background color
background(40, 40, 50)
-- Do your drawing here
currentTest:draw()
-- Restore orthographic projection
ortho()
-- Restore the view matrix to the identity
viewMatrix(matrix())
-- Draw a label at the top of the screen
fill(255)
font("MyriadPro-Bold")
fontSize(30)
text(currentTest:name(), WIDTH/2, HEIGHT - 30)
end
function touched(touch)
if touch.state == MOVING then
if CamType == 1 then --total free moving camera
--get the horizon for vertical rotation
q = Quaternion.Rotation(math.rad(90),camUp)
horizontal = camDir^q
--rotate camPos around vertical
q = Quaternion.Rotation(math.rad(touch.deltaX/3),camUp)
camDir = camDir^q
--rotate camPos and camUp around horizontal
q = Quaternion.Rotation(math.rad(touch.deltaY/3), horizontal)
camDir = camDir^q
camUp = camUp^q
else
--do rotation
q = Quaternion.Rotation(math.rad(90), vec3(0,1,0))
horizontal = vec3(camDir.x,0,camDir.z)^q
--rotate camPos around vertical
q = Quaternion.Rotation(math.rad(touch.deltaX/3),vec3(0,1,0))
camDir = camDir^q
--rotate camPos and camUp around horizontal
--also protect against rotating past vertical
oldcamDir = camDir
q = Quaternion.Rotation(math.rad(touch.deltaY/3), horizontal)
camDir = camDir^q
if (oldcamDir.x<0 and camDir.x>0) or (oldcamDir.x>0 and camDir.x<0) or (oldcamDir.z<0 and camDir.z>0) or (oldcamDir.z>0 and camDir.z<0) then
camDir = oldcamDir
end
--recompute Up from Dir as it's always vertical
q = Quaternion.Rotation(math.rad(-90), horizontal)
camUp = camDir^q
end
end
end
--# Test1
Test1 = class()
function Test1:name()
return "Codea Primitives in 3D"
end
function Test1:init()
-- you can accept and set parameters here
end
function Test1:draw()
-- Preserve existing transform and style
pushMatrix()
pushStyle()
-- This sets the line thickness
strokeWidth(5)
smooth()
rectMode(CENTER)
-- Make a floor
translate(0,-Size/2,0)
rotate(Angle,0,1,0)
rotate(90,1,0,0)
sprite("SpaceCute:Background", 0, 0, 300, 300)
-- Rotate and translate the square
resetMatrix()
rotate(Angle,0,1,0)
translate(0, 0, -100)
fill(44, 97, 161, 255)
rect(0, 0, Size, Size)
resetMatrix()
rotate(Angle,0,1,0)
fill(191, 26, 26, 255)
ellipse(0, 0, Size*0.8)
-- Restore transform and style
popStyle()
popMatrix()
end
--# Test2
Test2 = class()
function Test2:name()
return "3D Blocks"
end
function Test2:init()
-- all the unique vertices that make up a cube
local vertices = {
vec3(-0.5, -0.5, 0.5), -- Left bottom front
vec3( 0.5, -0.5, 0.5), -- Right bottom front
vec3( 0.5, 0.5, 0.5), -- Right top front
vec3(-0.5, 0.5, 0.5), -- Left top front
vec3(-0.5, -0.5, -0.5), -- Left bottom back
vec3( 0.5, -0.5, -0.5), -- Right bottom back
vec3( 0.5, 0.5, -0.5), -- Right top back
vec3(-0.5, 0.5, -0.5), -- Left top back
}
-- now construct a cube out of the vertices above
local cubeverts = {
-- Front
vertices[1], vertices[2], vertices[3],
vertices[1], vertices[3], vertices[4],
-- Right
vertices[2], vertices[6], vertices[7],
vertices[2], vertices[7], vertices[3],
-- Back
vertices[6], vertices[5], vertices[8],
vertices[6], vertices[8], vertices[7],
-- Left
vertices[5], vertices[1], vertices[4],
vertices[5], vertices[4], vertices[8],
-- Top
vertices[4], vertices[3], vertices[7],
vertices[4], vertices[7], vertices[8],
-- Bottom
vertices[5], vertices[6], vertices[2],
vertices[5], vertices[2], vertices[1],
}
-- all the unique texture positions needed
local texvertices = { vec2(0.03,0.24),
vec2(0.97,0.24),
vec2(0.03,0.69),
vec2(0.97,0.69) }
-- apply the texture coordinates to each triangle
local cubetexCoords = {
-- Front
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Right
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Back
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Left
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Top
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Bottom
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
}
-- now we make our 3 different block types
self.ms = mesh()
self.ms.vertices = cubeverts
self.ms.texture = "Planet Cute:Stone Block"
self.ms.texCoords = cubetexCoords
self.ms:setColors(255,255,255,255)
self.md = mesh()
self.md.vertices = cubeverts
self.md.texture = "Planet Cute:Dirt Block"
self.md.texCoords = cubetexCoords
self.md:setColors(255,255,255,255)
self.mg = mesh()
self.mg.vertices = cubeverts
self.mg.texture = "Planet Cute:Grass Block"
self.mg.texCoords = cubetexCoords
self.mg:setColors(255,255,255,255)
-- currently doesnt work properly without backfaces
self.mw = mesh()
self.mw.vertices = cubeverts
self.mw.texture = "Planet Cute:Water Block"
self.mw.texCoords = cubetexCoords
self.mw:setColors(255,255,255,100)
-- stick 'em in a table
self.blocks = { self.mg, self.md, self.ms }
-- our scene itself
-- numbers correspond to block positions in the blockTypes table
-- bottom middle top
self.scene = { { {3, 3, 0}, {2, 0, 0}, {0, 0, 0} },
{ {3, 3, 3}, {2, 2, 0}, {1, 0, 0} },
{ {3, 3, 3}, {2, 2, 2}, {1, 1, 0} } }
end
function Test2:draw()
pushMatrix()
pushStyle()
-- Make a floor
translate(0,-Size/2,0)
rotate(Angle,0,1,0)
rotate(90,1,0,0)
sprite("SpaceCute:Background", 0, 0, 300, 300)
-- render each block in turn
for zi,zv in ipairs(self.scene) do
for yi,yv in ipairs(zv) do
for xi, xv in ipairs(yv) do
-- apply each transform need - rotate, scale, translate to the correct place
resetMatrix()
rotate(Angle,0,1,0)
local s = Size*0.25
scale(s,s,s)
translate(xi-2, yi-2, zi-2) -- renders based on corner
-- so -2 fudges it near center
if xv > 0 then
self.blocks[xv]:draw()
end
end
end
end
popStyle()
popMatrix()
end
function Test2:touched(touch)
-- Codea does not automatically call this method
end
--# Quaternion
-- Quaternions
-- Author: Andrew Stacey
-- Website: http://www.math.ntnu.no/~stacey/HowDidIDoThat/iPad/Codea.html
-- Licence: CC0 http://wiki.creativecommons.org/CC0
--[[
This is a class for handling quaternion numbers. It was originally
designed as a way of encoding rotations of 3 dimensional space.
--]]
--import.libraries.Quaternion = function()
Quaternion = class()
--[[
A quaternion can either be specified by giving the four coordinates as
real numbers or by giving the scalar part and the vector part.
--]]
function Quaternion:init(...)
-- you can accept and set parameters here
if arg.n == 4 then
-- four numbers
self.a = arg[1]
self.b = arg[2]
self.c = arg[3]
self.d = arg[4]
elseif arg.n == 2 then
-- real number plus vector
self.a = arg[1]
self.b = arg[2].x
self.c = arg[2].y
self.d = arg[2].z
else
print("Incorrect number of arguments to Quaternion")
end
end
--[[
Test if we are zero.
--]]
function Quaternion:is_zero()
-- are we the zero vector
if self.a ~= 0 or self.b ~= 0 or self.c ~= 0 or self.d ~= 0 then
return false
end
return true
end
--[[
Test if we are real.
--]]
function Quaternion:is_real()
-- are we the zero vector
if self.b ~= 0 or self.c ~= 0 or self.d ~= 0 then
return false
end
return true
end
--[[
Test if the real part is zero.
--]]
function Quaternion:is_imaginary()
-- are we the zero vector
if self.a ~= 0 then
return false
end
return true
end
--[[
Test for equality.
--]]
function Quaternion:is_eq(q)
if self.a ~= q.a or self.b ~= q.b or self.c ~= q.c or self.d ~= q.d then
return false
end
return true
end
--[[
Defines the "==" shortcut.
--]]
function Quaternion:__eq(q)
return self:is_eq(q)
end
--[[
The inner product of two quaternions.
--]]
function Quaternion:dot(q)
return self.a * q.a + self.b * q.b + self.c * q.c + self.d * q.d
end
--[[
Makes "q .. p" return the inner product.
Probably a bad choice and likely to be removed in future versions.
function Quaternion:__concat(q)
return self:dot(q)
end
--]]
--[[
Length of a quaternion.
--]]
function Quaternion:len()
return math.sqrt(math.pow(self.a,2) + math.pow(self.b,2) + math.pow(self.c,2) + math.pow(self.d,2))
end
--[[
Often enough to know the length squared, which is quicker.
--]]
function Quaternion:lensq()
return math.pow(self.a,2) + math.pow(self.b,2) + math.pow(self.c,2) + math.pow(self.d,2)
end
--[[
Normalise a quaternion to have length 1, if possible.
--]]
function Quaternion:normalise()
local l
if self:is_zero() then
print("Unable to normalise a zero-length quaternion")
return false
end
l = 1/self:len()
return self:scale(l)
end
--[[
Scale the quaternion.
--]]
function Quaternion:scale(l)
return Quaternion(self.a * l,self.b * l,self.c * l, self.d * l)
end
--[[
Add two quaternions. Or add a real number to a quaternion.
--]]
function Quaternion:add(q)
if type(q) == "number" then
return Quaternion(self.a + q, self.b, self.c, self.d)
else
return Quaternion(self.a + q.a, self.b + q.b, self.c + q.c, self.d + q.d)
end
end
--[[
q + p
--]]
function Quaternion:__add(q)
return self:add(q)
end
--[[
Subtraction
--]]
function Quaternion:subtract(q)
return Quaternion(self.a - q.a, self.b - q.b, self.c - q.c, self.d - q.d)
end
--[[
q - p
--]]
function Quaternion:__sub(q)
return self:subtract(q)
end
--[[
Negation (-q)
--]]
function Quaternion:__unm()
return self:scale(-1)
end
--[[
Length (#q)
--]]
function Quaternion:__len()
return self:len()
end
--[[
Multiply the current quaternion on the right.
Corresponds to composition of rotations.
--]]
function Quaternion:multiplyRight(q)
local a,b,c,d
a = self.a * q.a - self.b * q.b - self.c * q.c - self.d * q.d
b = self.a * q.b + self.b * q.a + self.c * q.d - self.d * q.c
c = self.a * q.c - self.b * q.d + self.c * q.a + self.d * q.b
d = self.a * q.d + self.b * q.c - self.c * q.b + self.d * q.a
return Quaternion(a,b,c,d)
end
--[[
q * p
--]]
function Quaternion:__mul(q)
if type(q) == "number" then
return self:scale(q)
elseif type(q) == "table" then
if q:is_a(Quaternion) then
return self:multiplyRight(q)
end
end
end
--[[
Multiply the current quaternion on the left.
Corresponds to composition of rotations.
--]]
function Quaternion:multiplyLeft(q)
return q:multiplyRight(self)
end
--[[
Conjugation (corresponds to inverting a rotation).
--]]
function Quaternion:conjugate()
return Quaternion(self.a, - self.b, - self.c, - self.d)
end
function Quaternion:co()
return self:conjugate()
end
--[[
Reciprocal: 1/q
--]]
function Quaternion:reciprocal()
if self.is_zero() then
print("Cannot reciprocate a zero quaternion")
return false
end
local q = self:conjugate()
local l = self:lensq()
q = q:scale(1/l)
return q
end
--[[
Integral powers.
--]]
function Quaternion:power(n)
if n ~= math.floor(n) then
print("Only able to do integer powers")
return false
end
if n == 0 then
return Quaternion(1,0,0,0)
elseif n > 0 then
return self:multiplyRight(self:power(n-1))
elseif n < 0 then
return self:reciprocal():power(-n)
end
end
--[[
q^n
This is overloaded so that a non-number exponent returns the
conjugate. This means that one can write things like q^* or q^"" to
get the conjugate of a quaternion.
--]]
function Quaternion:__pow(n)
if type(n) == "number" then
return self:power(n)
else
return self:conjugate()
end
end
--[[
Division: q/p
--]]
function Quaternion:__div(q)
if type(q) == "number" then
return self:scale(1/q)
elseif type(q) == "table" then
if q:is_a(Quaternion) then
return self:multiplyRight(q:reciprocal())
end
end
end
--[[
Returns the real part.
--]]
function Quaternion:real()
return self.a
end
--[[
Returns the vector (imaginary) part as a Vec3 object.
--]]
function Quaternion:vector()
return vec3(self.b, self.c, self.d)
end
--[[
Represents a quaternion as a string.
--]]
function Quaternion:__tostring()
local s
local im ={{self.b,"i"},{self.c,"j"},{self.d,"k"}}
if self.a ~= 0 then
s = self.a
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 .. " + " .. v[1] .. v[2]
end
else
if v[1] == -1 then
s = s .. " - " .. v[2]
else
s = s .. " - " .. (-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 = v[1] .. v[2]
end
end
end
end
if s then
return s
else
return "0"
end
end
function Quaternion:tomatrix()
local a,b,c,d = self.a,self.d,-self.c,-self.b
local aa = 2*a*a
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
return matrix(
1 - bb - cc,
ab - cd,
ac + bd,
0,
ab + cd,
1 - aa - cc,
bc - ad,
0,
ac - bd,
bc + ad,
1 - aa - bb,
0,
0,0,0,1
)
end
--[[
(Not a class function)
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.
--]]
function Quaternion.Gravity()
local gxy, gy, gygxy, a, b, c, d
if Gravity.x == 0 and Gravity.y == 0 then
return Quaternion(1,0,0,0)
else
gy = - Gravity.y
gxy = math.sqrt(math.pow(Gravity.x,2) + math.pow(Gravity.y,2))
gygxy = gy/gxy
a = math.sqrt(1 + gxy - gygxy - gy)/2
b = math.sqrt(1 - gxy - gygxy + gy)/2
c = math.sqrt(1 - gxy + gygxy - gy)/2
d = math.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 Quaternion(a,b,c,d)
end
end
--[[
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 Quaternion.Rotation(a,...)
local q,c,s
q = Quaternion(0,...)
q = q:normalise()
c = math.cos(a/2)
s = math.sin(a/2)
q = q:scale(s)
q = q:add(c)
return q
end
--[[
The unit quaternion.
--]]
function Quaternion.unit()
return Quaternion(1,0,0,0)
end
--[[
Extensions to vec3 type.
--]]
do
local mt = getmetatable(vec3())
--[[
Promote to a quaternion with 0 real part.
--]]
mt["toQuaternion"] = function (self)
return Quaternion(0,self.x,self.y,self.z)
end
--[[
Apply a quaternion as a rotation.
--]]
mt["applyQuaternion"] = function (self,q)
local x = self:toQuaternion()
x = q:multiplyRight(x)
x = x:multiplyRight(q:conjugate())
return x:vector()
end
mt["__pow"] = function (self,q)
if type(q) == "table" then
if q:is_a(Quaternion) then
return self:applyQuaternion(q)
end
end
return false
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment