Created
February 13, 2013 09:44
-
-
Save sp4cemonkey/4943400 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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