Created
February 8, 2014 03:57
-
-
Save dermotbalson/8876478 to your computer and use it in GitHub Desktop.
Rotation demos v4
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
--# Notes | |
--[[ | |
ROTATION WITH QUATERNIONS AND FLYING | |
This project demonstrates the use of quaternions to rotate objects and fly them | |
The Main tab only contains code to help you choose from a number of demo projects. You will not need it in | |
your own projects. | |
Each numbered tab is a separate demo project, becoming more advanced from left to right. The first line of code is used by Main to manage your choice of tab, and can be deleted if you copy the code elsewhere. | |
The other tabs (to the right of the numbered tabs) contain the rotation library code. They are as follows: | |
Quaternion - is the engine, with all the quaternion functions. This is the only essential code you need, but because the use of most of the functions is not obvious, there are other "helper" classes like Rotation and Flight, which are used in the demos. | |
Rotation - has a simple class to help with rotations | |
Flight - has specific functionality for flying. If you use this, you don't need Rotations. | |
Plane - builds a simple box plane, also contains a box class that is used for some of the demos in this project | |
Model - imports and draws full 3D models in OBJ format | |
Sky - wraps an image on a sphere so you can look in all directions. As used in the flight projects, the sphere is centred on the aeroplane position but not rotated, so the plane is inside the sphere (not that you can tell) and can never fly outside it. | |
Joystick - provides controls for both an on screen joystick and iPad tilting | |
You need to download the sky image from here | |
https://www.dropbox.com/s/48eykfqdzxlfcjn/SkyDome1.jpg | |
and save to Documents:SkyDome (or save it somewhere else and change the name throughout the project) | |
--]] | |
--# Main | |
--Main | |
--This code manages which Code tab is run | |
--it remembers your last choice, and if you select a different one, it runs that instead | |
local tabs = {} | |
local fnames = {"setup","draw","touched","collide","orientationChanged","close","restart","keyboard","cleanup"} | |
local fns = {} | |
local tabDesc={} | |
function setup() | |
for k,v in ipairs(fnames) do --store addresses of key event functions | |
fns[v] = _G[v] | |
end | |
LastCode=readProjectData("RotationDemo") or 1 --load stored tab number | |
Choose_a_Demo=LastCode | |
RunCode() | |
end | |
function ShowList() | |
output.clear() | |
print("Select a demo (drag this section down out of the way, to see the selection slider below the other parameters)") | |
for i=1,#tabs do | |
print(i,tabDesc[i]) | |
end | |
end | |
--these two functions do all the tab switching magic (thanks to Andrew_Stacey) | |
function localise(n,d) | |
if d then tabDesc[n]=d end | |
local t= {} | |
setmetatable(t,{__index = _G}) | |
setfenv(2,t) | |
tabs[n] = t | |
end | |
--change tabs | |
function RunCode() | |
output.clear() | |
saveProjectData("RotationDemo",Choose_a_Demo) | |
cleanup() | |
local t = tabs[Choose_a_Demo] | |
for k,v in ipairs(fnames) do | |
if t[v] then _G[v] = t[v] else _G[v] = fns[v] end -- overwrite with the new code | |
end | |
setup() | |
LastCode=readProjectData("RotationDemo") or 1 --load stored tab number | |
parameter.integer("Choose_a_Demo",1,#tabs,LastCode,ShowList) --tab selector | |
parameter.action("Run selected demo", RunCode) | |
end | |
--default empty function to avoid errors for tabs that don't have it | |
function cleanup() | |
parameter.clear() | |
end | |
--# 1_Basic | |
--1. Basic rotation | |
if localise then localise(1,"Basic rotation") end | |
function setup() | |
--create a block to play with | |
--this uses the Block class in the Plane tab | |
b=Block(10,15,30,color(255),vec3(0,0,0),"Platformer Art:Block Brick",{0.1,0.1,0.9,0.9}) | |
r=Rotation() --initialise rotations | |
--these parameters let you play with rotations | |
--these angles will be added to the x,y,z rotation at each draw | |
parameter.number("X",0,1,.1) | |
parameter.number("Y",0,1,.1) | |
parameter.number("Z",0,1,.1) | |
parameter.action("Reset_Rotation",Reset) --restart from a level position | |
end | |
function Reset() | |
r=Rotation() | |
end | |
function draw() | |
background(220) | |
fill(0,0,0,150) | |
DrawText() | |
perspective() | |
camera(0,0,0,0,0,-100) | |
r:turn(X,Y,Z) --apply and store additional rotation | |
pushMatrix() | |
translate(0,0,-50) | |
r:rotate() --rotate (special version of the function, requires the Quaternion tab | |
b:draw() | |
popMatrix() | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("Play with the XYZ settings at left to rotate the block.",50,HEIGHT-50) | |
text("The XYZ values are in degrees, and are added to the existing rotation when redrawing",50,HEIGHT-75) | |
text("Drag the Output area down to see all the parameters",50,HEIGHT-100) | |
end | |
--# 2_Moving | |
--2. Moving | |
if localise then localise(2,"Moving and rotating") end | |
-- All changes are marked with ** 2 | |
function setup() | |
b=Block(10,15,30,color(255),vec3(0,0,0),"Platformer Art:Block Brick",{0.1,0.1,0.9,0.9}) | |
r=Rotation() --initialise rotations | |
pos=vec3(0,-50,-200) --initial position of block --** 2 | |
parameter.number("Speed",0,100,10) --speed of movement (pixels/sec) --** 2 | |
parameter.number("X",-1,1,.1) -- now you can move positive or negative ** 2 | |
parameter.number("Y",-1,1,0.05) | |
parameter.number("Z",-1,1,0) | |
parameter.action("Reset",Reset) | |
end | |
function Reset() | |
r=Rotation() | |
end | |
function draw() | |
background(220) | |
DrawText() | |
perspective() | |
camera(0,0,0,0,0,-100) | |
pushMatrix() | |
r:turn(X,Y,Z) --apply and store additional rotation | |
--update position of block. You can provide just the z distance as shown below --** 2 | |
--or if you prefer, a vec3 giving a position in x,y and z | |
pos=pos+r:Move(-Speed*DeltaTime) --use negative as z axis is reversed --** 2 | |
translate(pos.x,pos.y,pos.z) | |
r:rotate() --rotate (special version of the function, requires the Quaternion tab) | |
b:draw() | |
popMatrix() | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("Now the block moves in the direction it is facing.",50,HEIGHT-50) | |
text("You can change the speed as well as XYZ direction",50,HEIGHT-75) | |
text("Drag the Output area down to see all the parameters",50,HEIGHT-100) | |
end | |
--# 3_Following | |
--3. Moving | |
if localise then localise(3,"Following") end | |
-- All changes are marked with ** 3 | |
function setup() | |
b=Block(10,15,30,color(255),vec3(0,0,0),"Platformer Art:Block Brick",{0.1,0.1,0.9,0.9}) | |
r=Rotation() --initialise rotations | |
pos=vec3(0,-50,-200) --initial position of block | |
--we'll make another block follow the first one around, sitting on top of it and turning with it ** 3 | |
--it doesn't need its own Rotation because it will follow the first block | |
follow=Block(5,5,5,color(255),vec3(0,0,0),"Platformer Art:Block Special") -- ** 3 | |
followPos=vec3(0,10,0) --follower is 20 pixels above block ** 3 | |
parameter.number("Speed",0,100,10) --speed of movement (pixels/sec) | |
parameter.number("X",-1,1,.1) | |
parameter.number("Y",-1,1,0.05) | |
parameter.number("Z",-1,1,0) | |
parameter.action("Reset",Reset) | |
end | |
function Reset() | |
r=Rotation() | |
end | |
function draw() | |
background(220) | |
DrawText() | |
perspective() | |
camera(0,0,0,0,0,-100) | |
r:turn(X,Y,Z) --apply and store additional rotation | |
--update position of block. You can provide just the z distance as shown below | |
--or if you prefer, a vec3 giving a position in x,y and z | |
pos=pos+r:Move(-Speed*DeltaTime) --use negative as z axis is reversed | |
local f=pos + r:Move(followPos) --calculate position of follower ** 3 | |
pushMatrix() | |
translate(pos.x,pos.y,pos.z) | |
r:rotate() --rotate (special version of the function, requires the Quaternion tab) | |
b:draw() | |
popMatrix() | |
-- now rotate and position the following block ** 3 | |
pushMatrix() | |
translate(f.x,f.y,f.z) | |
r:rotate() --rotate the same way as the first block | |
follow:draw() | |
popMatrix() | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("A second block is positioned on top of the first one",50,HEIGHT-50) | |
text("This shows how to get different objects to move together",50,HEIGHT-75) | |
text("Drag the Output area down to see all the parameters",50,HEIGHT-100) | |
end | |
--# 4_CameraTracking | |
--4. Camera tracking | |
if localise then localise(4,"Camera tracking") end | |
-- All changes are marked with ** 4 | |
function setup() | |
b=Block(10,15,30,color(255),vec3(0,0,0),"Platformer Art:Block Brick",{0.1,0.1,0.9,0.9}) | |
r=Rotation() --initialise rotations | |
pos=vec3(0,-50,-200) --initial position of block | |
Speed=20 --fix speed for this demo ** 4 | |
parameter.boolean("CameraTrackingOn",false) | |
parameter.integer("CameraSetting",1,3,1) --** 4 | |
--three cam settings below, for each of them, x,y,z give camera position relative to the centre of the block | |
camSettings={vec3(0,0,100),vec3(0,0,100),vec3(0,10,0)} -- ** 4 | |
parameter.number("X",-1,1,.3) | |
parameter.number("Y",-1,1,0.05) | |
parameter.number("Z",-1,1,0.05) | |
parameter.action("Reset",Reset) | |
end | |
function Reset() | |
r=Rotation() | |
end | |
function draw() | |
background(220) | |
DrawText() | |
perspective() | |
r:turn(X,Y,Z) --apply and store additional rotation | |
--update position of block. | |
pos=pos+r:Move(-Speed*DeltaTime) | |
-- now rotate and position the camera ** 4 | |
--this needs to be done before we draw anything | |
local camPos,camLook,camUp | |
if CameraTrackingOn==false then | |
camPos=vec3(0,0,0) | |
camLook=vec3(0,0,-100) | |
camUp=vec3(0,1,0) | |
else | |
camPos=pos + r:Move(camSettings[CameraSetting]) --calculate position of camera ** 4 | |
--calculate where camera is looking | |
--if the camera is behind the plane, we can just look at the plane position | |
--but if the camera is on top of the plane, we won't see much if we look at it! | |
--so this code looks ahead. This means projecting forward in the current direction | |
camLook =pos+r:Move(-100) --look forward 100 pixels ** 4 | |
if CameraSetting>1 then --rotate camera for settings 2 & 3 | |
camUp=r:Move(vec3(0,1,0)) --rotate up vector | |
else --don't rotate for setting 1 | |
camUp=vec3(0,1,0) --default upright position | |
end | |
end | |
camera(camPos.x,camPos.y,camPos.z,camLook.x,camLook.y,camLook.z,camUp.x,camUp.y,camUp.z) | |
pushMatrix() | |
translate(pos.x,pos.y,pos.z) | |
r:rotate() --rotate (special version of the function, requires the Quaternion tab) | |
b:draw() | |
popMatrix() | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("Now the camera tracks the object (if you turn it on, alongside)",50,HEIGHT-50) | |
text("Setting 1 = camera follows behind, upright (block rotates left/right)",50,HEIGHT-70) | |
text("Setting 2 = camera follows behind, rotated with the block (block stays upright)",50,HEIGHT-90) | |
text("Setting 3 = camera is on top of the block (POV)",50,HEIGHT-110) | |
text("It's hard to see what's happening without a skyline - so that's the next demo",50,HEIGHT-130) | |
text("Drag the Output area down to see all the parameters",50,HEIGHT-150) | |
end | |
--# 5_Sky | |
--5. Sky sphere | |
if localise then localise(5,"Sky") end | |
-- All changes are marked with ** 5 | |
--This only requires two additional lines of code (and a suitable image) | |
--see the notes in the Sky tab for how this works | |
function setup() | |
b=Plane() -- use a simple plane -- ** 5 | |
r=Rotation() --initialise rotations | |
pos=vec3(0,-50,-200) --initial position of block | |
Speed=20 --fix speed for this demo | |
parameter.boolean("CameraTrackingOn",false) | |
parameter.integer("CameraSetting",1,3,1) | |
--three cam settings below, for each of them, x,y,z give camera position relative to the centre of the block | |
camSettings={vec3(0,0,100),vec3(0,40,200),vec3(0,3,-9)} | |
parameter.number("X",-1,1,0) | |
parameter.number("Y",-1,1,0.05) | |
parameter.number("Z",-1,1,0) | |
parameter.action("Reset",Reset) | |
--set up sky sphere | |
sky=SkyGlobe("Documents:SkyDome","Documents:SkyDome") -- ** 5 | |
end | |
function Reset() | |
r=Rotation() | |
end | |
function draw() | |
background(220) | |
DrawText() | |
perspective() | |
r:turn(X,Y,Z) --apply and store additional rotation | |
--update position of block. | |
pos=pos+r:Move(-Speed*DeltaTime) | |
-- now rotate and position the camera | |
--this needs to be done before we draw anything | |
local camPos,camLook,camUp | |
if CameraTrackingOn==false then | |
camPos=vec3(0,0,0) | |
camLook=vec3(0,0,-100) | |
camUp=vec3(0,1,0) | |
else | |
camPos=pos + r:Move(camSettings[CameraSetting]) --calculate position of camera | |
--calculate where camera is looking | |
--if the camera is behind the plane, we can just look at the plane position | |
--but if the camera is on top of the plane, we won't see much if we look at it! | |
--so this code looks ahead. This means projecting forward in the current direction | |
camLook =pos+r:Move(-100) --look forward 100 pixels | |
if CameraSetting>1 then --rotate camera for settings 2 & 3 | |
camUp=r:Move(vec3(0,1,0)) --rotate up vector | |
else --don't rotate for setting 1 | |
camUp=vec3(0,1,0) --default upright position | |
end | |
end | |
camera(camPos.x,camPos.y,camPos.z,camLook.x,camLook.y,camLook.z,camUp.x,camUp.y,camUp.z) | |
pushMatrix() | |
translate(camPos.x,camPos.y,camPos.z) | |
sky:draw() -- ** 5 | |
popMatrix() | |
pushMatrix() | |
translate(pos.x,pos.y,pos.z) | |
r:rotate() --rotate (special version of the function, requires the Quaternion tab) | |
b:draw() | |
popMatrix() | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("The sky makes it easier to see what is happening (If you don't see sky, re-start the program)",50,HEIGHT-50) | |
text("And now we are using a simple boxy plane object",50,HEIGHT-70) | |
text("You'll find it pretty hard to make the plane behave, though...",50,HEIGHT-90) | |
text("Drag the Output area down to see all the parameters",50,HEIGHT-110) | |
end | |
--# 6_Flight | |
--6. Introducing flight | |
if localise then localise(6,"Flying") end | |
--There are quite a few changes because the Flight functions are organised differently | |
-- All changes are marked with ** 6 | |
function setup() | |
b=Plane() | |
f=Flight() --initialise flight instance -- ** 6 | |
pos=vec3(0,-50,-200) --initial position of block | |
Speed=20 --fix speed for this demo | |
parameter.number("Pitch_Limit",30,90,90,AdjustSettings) | |
parameter.number("Yaw_Sensitivity",0.1,1,0.5,AdjustSettings) | |
parameter.number("Roll_Limit",30,120,90,AdjustSettings) | |
parameter.integer("CameraSetting",1,3,1) | |
--three cam settings below, for each of them, x,y,z give camera position relative to the centre of the plane | |
camSettings={vec3(0,0,50),vec3(-10,20,100),vec3(0,3,-9)} | |
parameter.action("Reset",Reset) | |
--set up sky sphere | |
sky=SkyGlobe("Documents:SkyDome","Documents:SkyDome") | |
--set up joystick (see Joystick tab for options) | |
j=JoyStick() -- ** 6 | |
end | |
function Reset() | |
r=Flight() | |
end | |
function AdjustSettings() --this is only here so you can play with settings interactively --** 6 | |
f.limit=vec3(Pitch_Limit,0,Roll_Limit)*math.pi/180 --these assume default settings apply! | |
f.sens=vec3(0,Yaw_Sensitivity,0) | |
end | |
function draw() | |
background(255) | |
DrawText() | |
perspective() | |
local v=j:update() --get joystick movement ** 6 | |
if j.touch then f:turn(v) end -- store additional rotation ** 6 | |
--update position of plane | |
pos=pos+f:position(-Speed*DeltaTime) -- ** 6 | |
-- now rotate and position the camera | |
--Flight class does it for us, send it the plane position and camera offset vector | |
f:setCamera(pos,camSettings[CameraSetting]) -- ** 6 | |
pushMatrix() | |
translate(pos.x,pos.y,pos.z) | |
sky:draw() --we centred the sky sphere on the camera before, do it on the plane for convenience ** 6 | |
f:rotate() | |
b:draw() | |
popMatrix() | |
j:draw() -- draw joystick ** 6 | |
end | |
--manage joystick -- pass touch values to joystick class ** 6 | |
function touched(t) | |
j:touched(t) | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("Now we'll use the Flight class",50,HEIGHT-50) | |
text("Use the on screen joystick to fly the plane",50,HEIGHT-70) | |
text("Try changing the limits and sensitivity settings",50,HEIGHT-90) | |
end | |
--# 7_3DModels | |
--7. Importing 3D models | |
if localise then localise(7,"3D models") end | |
--all changes marked ** 7 | |
--you can use different vehicles, see the list in the Model tab | |
--there can be quite a lot of work involved in using 3D models. You should get rid of any vertices you | |
--don't need, such as cockpit interiors. In this demo, this is done in SetupPlane. | |
--For the spitfire, I had to delete the static propellor and draw a spinning propellor myself. | |
--I left this out of this demo to save you downloading it, but if you want to include it | |
--it is here: https://www.dropbox.com/s/4ehrkhzeyi5fgb5/Prop2.png | |
--save to Dropbox:Prop2, then uncomment the line that loads the image, in setup | |
--note also that when we draw the plane, we can choose which of its meshes to draw | |
--so when we are in the cockpit, we only want the mesh named "Front", we don't need wings, tail etc | |
function setup() | |
SetupPlane() | |
--imgProp=readImage("Dropbox:Prop2") --extra image needed for propellor | |
f=Flight() --initialise flight instance | |
pos=vec3(0,-50,-200) --initial position of block | |
Speed=20 --fix speed for this demo | |
parameter.integer("CameraSetting",1,3,1) | |
--three cam settings below, for each of them, x,y,z give camera position relative to the centre of the plane | |
camSettings={vec3(0,0,25),vec3(0,8,25),vec3(0,0.6,1.5)} | |
parameter.action("Reset",Reset) | |
--set up sky sphere | |
sky=SkyGlobe("Documents:SkyDome","Documents:SkyDome") | |
--set up joystick (see Joystick tab for options) | |
j=JoyStick() | |
end | |
function SetupPlane() -- ** 7 all new | |
plane=OBJ("plane",1,"http://goo.gl/yw0i8m") | |
local m=plane.m --set of meshes | |
--remove the meshes we don't need | |
for i=#m,1,-1 do | |
if m[i].name=="Propellor" then table.remove(m,i) | |
elseif m[i].name=="Cockpit" then table.remove(m,i) | |
elseif m[i].name=="Pilot" then table.remove(m,i) | |
elseif m[i].name=="propeller_rotation" then table.remove(m,i) end | |
end | |
end | |
function Reset() | |
r=Flight() | |
end | |
function draw() | |
background(255) | |
DrawText() | |
perspective() | |
local v=j:update() --get joystick movement | |
if j.touch then f:turn(v) end -- store additional rotation | |
--update position of plane | |
pos=pos+f:position(-Speed*DeltaTime) | |
-- now rotate and position the camera | |
--Flight class does it for us, send it the plane position and camera offset vector | |
f:setCamera(pos,camSettings[CameraSetting]) | |
pushMatrix() | |
translate(pos.x,pos.y,pos.z) | |
sky:draw() --we centred the sky sphere on the camera before, do it on the plane for convenience | |
f:rotate() | |
--if view is inside cockpit, only draw the front of the plane | |
if CameraSetting==3 then plane:draw("Body_Front") else plane:draw("All") end -- ** 7 | |
plane:draw() | |
pushStyle() | |
--draw prop separately if we have loaded it --just for the spitfire | |
if imgProp then | |
translate(0,0,-2.12) | |
spriteMode(CENTER) | |
sprite(imgProp,0,0,3) | |
end | |
popStyle() | |
popMatrix() | |
j:draw() -- draw joystick | |
end | |
--manage joystick -- pass touch values to joystick class | |
function touched(t) | |
j:touched(t) | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("Now we'll use an imported 3D model",50,HEIGHT-50) | |
end | |
--# 8_Tilting | |
--8. Controlling the plane using tilting | |
if localise then localise(8,"Using tilt") end | |
--all changes marked ** 8 | |
--This demo uses iPad tilt to steer the plane | |
--it's extremely simple | |
--the most important change is setting all three directions to be incremental (see post at top of Fight for more) | |
--to give us finer control over how the pane reacts to tilting | |
function setup() | |
SetupPlane() | |
--imgProp=readImage("Dropbox:Prop2") --extra image needed for propellor | |
f=Flight({mode=vec3(1,1,1),sens=vec3(0.5,0.5,0.5)}) --initialise flight instance ** 8 | |
pos=vec3(0,-50,-200) --initial position of block | |
Speed=20 --fix speed for this demo | |
parameter.integer("CameraSetting",1,3,1) | |
--three cam settings below, for each of them, x,y,z give camera position relative to the centre of the plane | |
camSettings={vec3(0,0,25),vec3(0,8,25),vec3(0,0.6,1.5)} | |
parameter.action("Reset",Reset) | |
--set up sky sphere | |
sky=SkyGlobe("Documents:SkyDome","Documents:SkyDome") | |
end | |
function SetupPlane() | |
plane=OBJ("plane",1,"http://goo.gl/yw0i8m") | |
local m=plane.m --set of meshes | |
--remove the meshes we don't need | |
for i=#m,1,-1 do | |
if m[i].name=="Propellor" then table.remove(m,i) | |
elseif m[i].name=="Cockpit" then table.remove(m,i) | |
elseif m[i].name=="Pilot" then table.remove(m,i) | |
elseif m[i].name=="propeller_rotation" then table.remove(m,i) end | |
end | |
end | |
function Reset() | |
r=Flight() | |
end | |
function draw() | |
background(255) | |
DrawText() | |
perspective() | |
local v=vec2(Gravity.x,Gravity.y) --get tilt movement ** 8 | |
f:turn(v) -- store additional rotation | |
--update position of plane | |
pos=pos+f:position(-Speed*DeltaTime) | |
-- now rotate and position the camera | |
--Flight class does it for us, send it the plane position and camera offset vector | |
f:setCamera(pos,camSettings[CameraSetting]) | |
pushMatrix() | |
translate(pos.x,pos.y,pos.z) | |
sky:draw() --we centred the sky sphere on the camera before, do it on the plane for convenience | |
f:rotate() | |
--if view is inside cockpit, only draw the front of the plane | |
if CameraSetting==3 then plane:draw("Body_Front") else plane:draw("All") end -- ** 7 | |
plane:draw() | |
pushStyle() | |
--draw prop separately if we have loaded it --just for the spitfire | |
if imgProp then | |
translate(0,0,-2.12) | |
spriteMode(CENTER) | |
sprite(imgProp,0,0,3) | |
end | |
popStyle() | |
popMatrix() | |
end | |
--manage joystick -- pass touch values to joystick class | |
function touched(t) | |
j:touched(t) | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("Now we'll use use iPad tilt to fy",50,HEIGHT-50) | |
end | |
--# 9_Dogfight | |
--9. Dogfight | |
if localise then localise(9,"Dogfight") end | |
--all changes marked ** 9 | |
--we add another plane | |
--and a radar screen | |
function setup() | |
flight=Flight() --initialise flight instance | |
SetupPlane() | |
SetupEnemy() --** 9 | |
parameter.integer("CameraSetting",1,3,1) | |
--three cam settings below, for each of them, x,y,z give camera position relative to the centre of the plane | |
camSettings={vec3(0,0,25),vec3(0,8,25),vec3(0,0.6,1.5)} | |
parameter.action("Reset",Reset) | |
--set up sky sphere | |
sky=SkyGlobe("Documents:SkyDome","Documents:SkyDome") | |
--set up joystick (see Joystick tab for options) | |
j=JoyStick() | |
FPS=60 | |
end | |
function SetupPlane() | |
flight.pos=vec3(0,0,0) --initial position of plane | |
flight.speed=30 --fix speed for this demo | |
plane=OBJ("plane",1,"http://goo.gl/yw0i8m") | |
--remove the meshes we don't need | |
local m=plane.m --set of meshes | |
for i=#m,1,-1 do | |
if m[i].name=="Propellor" then table.remove(m,i) | |
elseif m[i].name=="Cockpit" then table.remove(m,i) | |
elseif m[i].name=="Pilot" then table.remove(m,i) | |
elseif m[i].name=="propeller_rotation" then table.remove(m,i) end | |
end | |
radar=Radar(flight) --set up radar screen centred on this plane ** 9 | |
end | |
function SetupEnemy() -- all new - note adjustments to default settings ** 9 | |
ePlane=plane --use the same plane | |
eFlight=Flight() --set up flight instance to control rotation | |
eFlight.mode=vec3(1,1,1) --make all adjustments incremental | |
eFlight.sens=vec3(1,1,1) --so any rotations we do are not adjusted (default is 0.25) | |
eFlight.level=false --plane doesn't try to level itself out | |
eFlight.pos=vec3(-100,10,-500) --start out in front of us | |
eFlight.speed=15 --pixels per sec | |
eFlight:turn(vec3(0,-45,0),1) --turn sideways | |
radar:AddTarget(1,eFlight) --add to radar table, pass id and ref to position | |
end | |
function Reset() | |
r=Flight() | |
end | |
function draw() | |
FPS=FPS*.9+.1/DeltaTime | |
background(255) | |
DrawText() | |
perspective() | |
local v=j:update() --get joystick movement | |
if j.touch then flight:turn(v) end -- store additional rotation | |
--update position of plane | |
flight.pos=flight.pos+flight:position(-flight.speed*DeltaTime) | |
-- now rotate and position the camera | |
--Flight class does it for us, send it the plane position and camera offset vector | |
flight:setCamera(flight.pos,camSettings[CameraSetting]) | |
--draw enemy plane first because it's usually behind our plane ** 9 | |
pushMatrix() | |
--update the enemy plane rotation here if you want to | |
--eFlight:turn(vec3(pitch,yaw,roll)) --in degrees ** 9 | |
eFlight.pos=eFlight.pos+eFlight:position(-eFlight.speed*DeltaTime) --** 9 | |
translate(eFlight.pos.x,eFlight.pos.y,eFlight.pos.z) --** 9 | |
eFlight:rotate() --** 9 | |
ePlane:draw("All") --** 9 | |
popMatrix() | |
pushMatrix() | |
translate(flight.pos.x,flight.pos.y,flight.pos.z) | |
sky:draw() --we centred the sky sphere on the camera before, do it on the plane for convenience | |
flight:rotate() | |
--if view is inside cockpit, only draw the front of the plane | |
plane:draw("All") | |
popMatrix() | |
j:draw() -- joystick | |
radar:draw() -- draw radar ** 9 | |
end | |
--manage joystick -- pass touch values to joystick class | |
function touched(t) | |
j:touched(t) | |
end | |
function DrawText() | |
textSize(12) | |
textMode(CORNER) | |
text("We add another plane - try to get close to it",50,HEIGHT-50) | |
text(FPS,50,HEIGHT-70) | |
end | |
--# Quaternion | |
-- 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") or is(n,"string") 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 | |
--# Rotation | |
--rotation | |
Rotation=class() | |
function Rotation:init() --create one for each object you want to rotate | |
self.q=vec4(1,0,0,0) --initialise quaternion | |
end | |
--adds to rotation (degrees), either pass through x,y,z or vec3(x,y,z) | |
function Rotation:turn(v,y,z) | |
if y then v=vec3(v,y,z) end | |
self.q=self.q*qTangent(v*math.pi/180) | |
end | |
--actually rotates the current scene | |
--can be used for other objects than the object you attached this to, if you want them to rotate the same way | |
function Rotation:rotate() | |
rotate(self.q) --special rotate function, requires Quaternion tab | |
end | |
--rotates the position of a movement vector | |
--in plain English, if you want to move 5 pixels forward, this will work out the actual x,y,z change | |
--taking into account any rotation. You then need to add it to the current position, to get the new position. | |
--the parameter v can be a vec3 or just the z value | |
function Rotation:Move(v) | |
if type(v)=="number" then v=vec3(0,0,v) end | |
return v^self.q --return rotated value of v | |
end | |
--# Flight | |
--Flight class | |
--See this post for an explanation of this class | |
--http://coolcodea.wordpress.com/2014/02/05/149-how-the-3d-flight-library-works/ | |
Flight=class() | |
--Note all the options you can set below, by passing through a table. | |
--Normally the defaults are fine, so you can omit the tabe | |
--However, for the explanation here, we'll assume you created a table T | |
--You can set the initial values of yaw, pitch and roll (although usually they will start out = 0) | |
--If you set yaw, it needs to be T.yaw = vec3(0,Y,0) where Y is the angle you want | |
--Similarly if you set pitch and/or roll, it is T.pitchroll = vec3(P,0,R) where P=pitch angel and R=roll angle | |
--mode tells the class whether to use incremental or fixed adjustments (see post above for details) | |
-- T.mode = vec3(P,Y,R) with values for P, Y and R of 0 for fixed, or 1 for incremental | |
--You can set the sensitivity of the controls for incremental adjustments, with T.sens=vec3(P,Y,R) and the | |
--values can be anything you want. The default is 1 | |
--You can set the rotation limits for fixed adjustments, with T.limit=vec3(P,Y,R), and the | |
--values are in degrees. The defaults are 90, 180, 45 | |
--When the plane returns to level flight automatically, you can set how long this takes with T.steady=S (seconds | |
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)*0.25 --sensitivity to incremental changes | |
self.limit=t.limit or vec3(90,180,90)*math.pi/180 --rotation limit | |
self.level=t.level or true --true if you want the plane to rotate back to level flight when controls released | |
self.steady=t.steady or 5 | |
self.pos=t.pos or vec3(0,0,0) --starting position | |
self.z=t.z or -1 | |
self.speed=t.speed or 0 | |
self:reset() | |
end | |
--resets plane to level | |
function Flight:reset() | |
self.y=vec4(1,0,0,0) | |
self.pr=vec4(1,0,0,0) | |
self.dv=vec3(0,0,0) | |
self.prevRot=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 | |
--]] | |
--rotates the plane | |
-- r = vec2(x,y) if we are passing x,y control values in range -1 to +1 or | |
-- r = vec3(P,Y,R) in degrees (fixed or incremental value) | |
--t is fraction to apply, default is DeltaTime | |
function Flight:turn(r,t) | |
if not r.z then r=vec3(-r.y,-r.x,-r.x) else r=r*math.pi/180 end | |
t=t or DeltaTime | |
--calculate change in fixed values | |
local delta = vec3(r.x - self.prevRot.x,r.y - self.prevRot.y,r.z - self.prevRot.z) | |
self.prevRot=r | |
--calculate both fixed and incremental results | |
--use mode to apply the correct value to pitch, yaw and roll | |
--incremental adjustments are also modified by the sensitivity vector and time | |
self.dv = self.mode * r * self.sens * t | |
self.pr = self.pr * qTangent((1 - self.mode) * delta * self.limit) | |
self.y = self.y * qTangent(self.dv) | |
self.turning = 1 | |
end | |
function Flight:position(s) --s is distance in pixels, either a z value or vec3 | |
if type(s)=="number" then s=vec3(0,0,s) end | |
return s^(self.y*self.pr) | |
end | |
function Flight:inversePosition(v) | |
local q=self.y*self.pr | |
return v^((self.y*self.pr):conjugate()) | |
end | |
function Flight:rotate() | |
if self.level~=1 then self:interpolate() end --update interpolation when rotating | |
rotate(self.y*self.pr) | |
end | |
function Flight:rotation() | |
return self.y*self.pr | |
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 | |
self.prevRot=self.prevRot*self.mode | |
elseif self.turning == 1 then | |
self.turning = 2 | |
elseif self.interpolating then | |
self.interpolating = self.slerpQ(ElapsedTime) | |
end | |
end | |
function Flight:interpolateSlerp() | |
local rt, stime, l, rl, dy, y | |
stime = ElapsedTime | |
dy = self.dv | |
rt = vec4(self.pr.x,0,self.pr.z,0):normalise() | |
l = self.pr:sdist(rt) * self.steady | |
rl = self.pr:make_slerp(rt) | |
return function(t) | |
t = t - stime | |
if t > l then | |
self.pr = vec4(1,0,0,0) | |
self.y = self.y * rt | |
return false | |
end | |
self.y = self.y * qTangent(edge(t,l,0) * dy) | |
self.pr = rl(smootherstep(t,0,l)) | |
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 | |
--camera looks in front of plane (and not at it) | |
local look = p + self:position(vec3(0,0,-500)) | |
camera(self.cam.at,look,up) | |
end | |
--# Controls | |
--Controls | |
--contains Joystick controls and Tilting controls | |
--1. Tilting the iPad | |
--This is as easy as just returning the Gravity values, which range from -1 to +1 for both x and y | |
--You may want to make all three directions incremental [self.mode in Flight tab = (1,1,1)] so you | |
--can control how much the plane reacts to tilting, and so you can do 360 degree rolls without | |
--dislocating your shoulders | |
function Tilt() | |
return vec2(Gravity.x,Gravity.y) | |
end | |
--2. Joystick | |
JoyStick = class() | |
--Note all the options you can set below. Pass them through in a named table | |
function JoyStick:init(t) | |
t = t or {} | |
self.radius = t.radius or 100 --size of joystick on screen | |
self.stick = t.stick or 30 --size of inner circle | |
self.centre = t.centre or self.radius * vec2(1,1) + vec2(5,5) | |
self.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() | |
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.touch = t.id | |
end | |
end | |
if t.id == self.touch then | |
if t.state~=ENDED then | |
local v = vec2(t.x,t.y) | |
if v:dist(self.centre)>self.radius-self.stick then | |
v = (v - self.centre):normalize()*(self.radius - self.stick) + self.centre | |
end --set x,y values for joy based on touch | |
self.target=v - self.centre | |
else --reset joystick to centre when touch ends | |
self.target=vec2(0,0) | |
self.touch = false | |
end | |
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.touch then | |
if self.moving ~= 0 then | |
self.moving = self.moving - 1 | |
end | |
else | |
self.moving = 2 | |
end | |
else | |
self.position = self.position + p:normalize() * DeltaTime * self.mspeed | |
self.moving = 2 | |
end | |
return self.position/(self.radius - self.stick) | |
end | |
function JoyStick:isMoving() | |
return self.moving | |
end | |
--3. Radar | |
--call this in setup | |
--it is positioned at bottom right of screen, change if you like | |
Radar=class() | |
function Radar:init(f) | |
self.flight=f --instance of Flight for this plane | |
self.range=1000 --number of pixels covered by scope radius | |
self.radius=105 | |
self.screenPos=vec2(WIDTH-self.radius-5,self.radius+5) --position | |
self.image=self:CreateRadarImage() | |
self.targets={} | |
end | |
function Radar:AddTarget(id,f) --p is vec reference to flight instance | |
self.targets[id]=f | |
end | |
function RemoveTarget(id) | |
table.remove(self.targets,id) | |
end | |
--create radar screen | |
function Radar:CreateRadarImage() | |
local img=image(self.radius*2,self.radius*2) | |
local r=self.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 Radar:draw() | |
ortho() | |
viewMatrix(matrix()) | |
pushStyle() | |
spriteMode(CENTER) | |
sprite(self.image,self.screenPos.x,self.screenPos.y) | |
fill(0,0,0,100) | |
local pa=self.flight:position(vec3(0,0,-1)) | |
local pv=vec2(pa.x,pa.z):normalize() | |
for i,o in pairs(self.targets) do | |
local d=vec2(self.flight.pos.x,self.flight.pos.z):dist(vec2(o.pos.x,o.pos.z)) --distance | |
if d<=self.range then --only display if on screen | |
d=d*self.radius/self.range --calculate distance scaled to radar screen | |
--direction from plane to target (without taking plane orientation into account | |
local ov=vec2(o.pos.x-self.flight.pos.x,o.pos.z-self.flight.pos.z):normalize() | |
--angle between plane orientation and direction from plane to target | |
local a=pv:angleBetween(ov) | |
ellipse(self.screenPos.x+math.sin(a)*d,self.screenPos.y+math.cos(a)*d,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 | |
--for more info on skyglobes see here | |
-- http://coolcodea.wordpress.com/2013/12/05/139-3d-sky/ | |
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=1000 --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:setColors(color(255)) | |
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 | |
--# Model | |
-- This class imports 3D models in the OBJ format. | |
--See this post for more info | |
--http://coolcodea.wordpress.com/2013/12/13/139-importing-full-3d-models-into-codea/ | |
-- You should be able to import most OBJ files without alteration, except that | |
--1. you need to include the accompanying .MTL file at the top of the OBJ file and insert a tag [obj] on a | |
--blank line between the two sections | |
--2. if you are planning to use image textures, you need to alter the map entries in the .MTL file so they | |
--show the name you want to use for the image, and its internet URL so it can be downloaded | |
--3. the best way to understand this is to look at the spitfire file below | |
--Object class | |
O={} | |
function O.LoadModel(p) | |
local mod={} | |
for i=1,#p do | |
mod[i]=OBJ(p[i][1],p[i][2],p[i][3]) | |
end | |
return mod | |
end | |
--OBJ library | |
OBJ=class() | |
OBJ.DataPrefix="cfg_" | |
OBJ.imgPrefix="Documents:z3D" | |
function OBJ:init(name,scale,url) | |
self.name=name | |
self.scale=scale or 1 | |
self.centre=vec3(0,0,0) | |
self.data=readGlobalData(OBJ.DataPrefix..name) | |
if self.data then self:ProcessData() | |
else http.request(url,function(d) self:DownloadData(d) end) end | |
end | |
function OBJ:DownloadData(data) | |
if data~=nil and string.find(data,"[obj]") then | |
saveGlobalData(OBJ.DataPrefix..self.name,data) | |
self.data=data | |
self:ProcessData() | |
else print("Error loading data for "..self.name) return end | |
end | |
function OBJ:ProcessData() | |
self.mtl={} | |
self.m={} | |
local p, v, tx, t, np, n={},{},{},{},{},{} | |
local s=self.data | |
local mname | |
local section="mtl" | |
for line in s:gmatch("[^\r\n]+") do | |
line=OBJ.trim(line) | |
if string.find(line,"%[obj%]")~=nil then section="obj" mname=nil end | |
--material definition section | |
if section=="mtl" then | |
if string.find(line,"newmtl") then | |
mname=OBJ.GetValue(line) | |
--print(mname) | |
self.mtl[mname]={} | |
else | |
local code=string.sub(line,1,2) | |
if code=="Ka" then --ambient | |
self.mtl[mname].Ka=OBJ.GetColor(line) | |
elseif code=="Kd" then --diffuse | |
self.mtl[mname].Kd=OBJ.GetColor(line) | |
elseif code=="Ks" then --specular | |
self.mtl[mname].Ks=OBJ.GetColor(line) | |
elseif code=="Ns" then --specular exponent | |
self.mtl[mname].Ns=OBJ.GetValue(line) | |
elseif code=="ill" then --illumination code | |
self.mtl[mname].illum=OBJ.GetValue(line) | |
elseif code=="ma" then --texture map name | |
local u=OBJ.split(OBJ.GetValue(line)," ") | |
if string.find(u[1],"%.") then | |
self.mtl[mname].map=string.sub(u[1],1,string.find(u[1],"%.")-1) | |
else | |
self.mtl[mname].map=u[1] | |
end | |
self.mtl[mname].path=u[2] | |
end | |
end | |
--data section | |
elseif section=="obj" then | |
--read in groups of data into separate meshes | |
local code=string.sub(line,1,2) | |
--look for material settings, a separate mesh is used for each | |
if string.find(line,"usemtl") then | |
if mname then | |
local m=mesh() | |
if self.scale~=1 then for i=1,#v do v[i]=v[i]*self.scale end end | |
m.vertices=v | |
if #t>0 then m.texCoords=t end | |
if #n>0 then m.normals=n end | |
if self.mtl[mname] then m.settings=self.mtl[mname] end | |
if m.settings and m.settings.map then m.texture=OBJ.imgPrefix..m.settings.map end | |
m.name=mname | |
self.m[#self.m+1]=m | |
end | |
mname=OBJ.GetValue(line) | |
v,t,n={},{},{} | |
--print(mname) | |
end | |
if code=="v " then --point position | |
p[#p+1]=OBJ.GetVec3(line) | |
elseif code=="vn" then --point normal | |
np[#np+1]=OBJ.GetVec3(line) | |
elseif code=="vt" then --texture co-ord | |
tx[#tx+1]=OBJ.GetVec2(line) | |
elseif code=="f " then --vertex | |
local pts,ptex,pnorm=OBJ.GetList(line) | |
if #pts==3 then | |
for i=1,3 do v[#v+1]=p[tonumber(pts[i])] end | |
if ptex then for i=1,3 do t[#t+1]=tx[tonumber(ptex[i])] end end | |
if pnorm then for i=1,3 do n[#n+1]=np[tonumber(pnorm[i])] end end | |
elseif #pts==4 then | |
for i=1,3 do v[#v+1]=p[tonumber(pts[i])] end | |
if ptex then for i=1,3 do t[#t+1]=tx[tonumber(ptex[i])] end end | |
if pnorm then for i=1,3 do n[#n+1]=np[tonumber(pnorm[i])] end end | |
v[#v+1]=p[tonumber(pts[3])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[3])] end | |
if pnorm then n[#n+1]=np[tonumber(pnorm[3])] end | |
v[#v+1]=p[tonumber(pts[4])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[4])] end | |
if pnorm then n[#n+1]=np[tonumber(pnorm[4])] end | |
v[#v+1]=p[tonumber(pts[1])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[1])] end | |
if pnorm then n[#n+1]=np[tonumber(pnorm[1])] end | |
elseif #pts>4 then | |
local cx,cy,cz=0,0,0 | |
local ttx,tty=0,0 | |
local nx,ny,nz=0,0,0 | |
for i=1,#pts do | |
local u=p[tonumber(pts[i])] cx,cy,cz=cx+u.x,cy+u.y,cz+u.z | |
if ptex then local u=tx[tonumber(ptex[i])] ttx,tty=ttx+u.x,tty+u.y end | |
end | |
local cp=vec3(cx/#pts,cy/#pts,cz/#pts) | |
if ptex then ct=vec2(ttx/#pts,tty/#pts) end | |
local j | |
for i=1,#pts do | |
if i<#pts then j=i+1 else j=1 end | |
v[#v+1]=p[tonumber(pts[i])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[i])] end | |
v[#v+1]=p[tonumber(pts[j])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[j])] end | |
v[#v+1]=cp | |
if ptex then t[#t+1]=ct end | |
end | |
end | |
end | |
end | |
end | |
local m=mesh() | |
if self.scale~=1 then for i=1,#v do v[i]=v[i]*self.scale end end | |
m.vertices=v | |
if #t>0 then m.texCoords=t end | |
if #n>0 then m.normals=n end | |
m.settings=self.mtl[mname] | |
if m.settings and m.settings.map then m.texture=OBJ.imgPrefix..m.settings.map end | |
m.name=mname | |
self.m[#self.m+1]=m | |
self:GetStats() | |
--download images if not stored locally | |
self.MissingImages={} | |
for i,O in pairs(self.mtl) do | |
if O.map then | |
local y=readImage(OBJ.imgPrefix..O.map) | |
if not y then self.MissingImages[#self.MissingImages+1]={O.map,O.path} end | |
end | |
end | |
if #self.MissingImages>0 then self:LoadImages() end | |
propAngle=0 --propellor | |
end | |
function OBJ:LoadImages() | |
--print("downloading"..self.MissingImages[1][1]) | |
http.request(self.MissingImages[1][2],function(d) self:StoreImage(d) end) | |
end | |
function OBJ:StoreImage(d) | |
--print("saving"..self.MissingImages[1][1]) | |
saveImage(OBJ.imgPrefix..self.MissingImages[1][1],d) | |
table.remove(self.MissingImages,1) | |
if #self.MissingImages~=0 then self:LoadImages() end | |
end | |
function OBJ:DeleteData() | |
saveGlobalData(OBJ.DataPrefix..self.name,nil) | |
for i,O in pairs(self.mtl) do | |
if O.map then | |
---print("deleting "..OBJ.imgPrefix..O.map) | |
local y=saveImage(OBJ.imgPrefix..O.map,nil) | |
end | |
end | |
end | |
function OBJ.GetColor(s) | |
local s1=string.find(s," ") | |
local s2=string.find(s," ",s1+1) | |
local s3=string.find(s," ",s2+1) | |
return color(string.sub(s,s1+1,s2-1)*255,string.sub(s,s2+1,s3-1)*255,string.sub(s,s3+1,string.len(s))*255) | |
end | |
function OBJ.GetVec3(s) | |
local s1=string.find(s," ") | |
local s2=string.find(s," ",s1+1) | |
local s3=string.find(s," ",s2+1) | |
return vec3(math.floor(string.sub(s,s1+1,s2-1)*100)/100, | |
math.floor(string.sub(s,s2+1,s3-1)*100)/100, | |
math.floor(string.sub(s,s3+1,string.len(s))*100)/100) | |
end | |
function OBJ.GetVec2(s) | |
local s1=string.find(s," ") | |
local s2=string.find(s," ",s1+1) | |
local s3=string.find(s," ",s2+1) | |
if s3 then | |
return vec3(math.floor(string.sub(s,s1+1,s2-1)*100)/100, | |
math.floor(string.sub(s,s2+1,s3-1)*100)/100) | |
else | |
return vec2(math.floor(string.sub(s,s1+1,s2-1)*100)/100, | |
math.floor(string.sub(s,s2+1,string.len(s))*100)/100) | |
end | |
end | |
function OBJ:draw(v) | |
for i=1,#self.m do | |
if v=="All" or (v and string.find(v,self.m[i].name)) then | |
self.m[i]:draw() | |
end | |
end | |
end | |
function OBJ.GetValue(s) | |
return string.sub(s,string.find(s," ")+1,string.len(s)) | |
end | |
function OBJ.trim(s) | |
while string.find(s," ") do s = string.gsub(s," "," ") end | |
return s:match'^()%s*$' and '' or s:match'^%s*(.*%S)' | |
end | |
function OBJ.split(s,sep) | |
sep=sep or "/" | |
local p={} | |
local pattern = string.format("([^%s]+)", sep) | |
string.gsub(s,pattern, function(c) p[#p+1] = c end) | |
return p | |
end | |
function OBJ.GetList(s) | |
local p,t,n={},{},{} | |
p=OBJ.split(s," ") | |
table.remove(p,1) | |
for i=1,#p do | |
local a=OBJ.split(p[i]) | |
if #a==1 then | |
p[i]=math.abs(a[1]) | |
elseif #a==2 then | |
p[i]=math.abs(a[1]) | |
t[i]=math.abs(a[2]) | |
elseif #a==3 then | |
p[i]=math.abs(a[1]) | |
t[i]=math.abs(a[2]) | |
n[i]=math.abs(a[3]) | |
end | |
end | |
return p,t,n | |
end | |
function OBJ:GetStats() | |
local x,y,z,n=0,0,0,0 | |
local minx,maxx,miny,maxy,minz,maxz=999,-999,999,-999,999,-999 | |
for i=1,#self.m do | |
local vv=self.m[i].vertices | |
local nn=0 | |
for j=1,#vv do | |
local v=vv[i] | |
if v~=nil then | |
x,y,z=x+v.x,y+v.y,z+v.z | |
nn=nn+1 | |
if v.x<minx then minx=v.x end | |
if v.x>maxx then maxx=v.x end | |
if v.y<miny then miny=v.y end | |
if v.y>maxy then maxy=v.y end | |
if v.z<minz then minz=v.z end | |
if v.z>maxz then maxz=v.z end | |
end | |
end | |
n=n+nn | |
end | |
self.centre=vec3(x/n,y/n,z/n) | |
self.minvert=vec3(minx,miny,minz) | |
self.maxvert=vec3(maxx,maxy,maxz) | |
self.size=vec3(maxx-minx,maxy-miny,maxz-minz) | |
end | |
function CalculateNormals(vertices) | |
--this assumes flat surfaces, and hard edges between triangles | |
local norm = {} | |
for i=1, #vertices,3 do --calculate normal for each set of 3 vertices | |
local n = ((vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i])):normalize() | |
norm[i] = n --then apply it to all 3 | |
norm[i+1] = n | |
norm[i+2] = n | |
end | |
return norm | |
end | |
function CalculateAverageNormals(vertices,f) | |
--average normals at each vertex | |
--first get a list of unique vertices, concatenate the x,y,z values as a key | |
local norm,unique= {},{} | |
for i=1, #vertices do | |
unique[vertices[i].x ..vertices[i].y..vertices[i].z]=vec3(0,0,0) | |
end | |
--calculate normals, add them up for each vertex and keep count | |
for i=1, #vertices,3 do --calculate normal for each set of 3 vertices | |
local n = (vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i]) | |
for j=0,2 do | |
local v=vertices[i+j].x ..vertices[i+j].y..vertices[i+j].z | |
unique[v]=unique[v]+n | |
end | |
end | |
--calculate average for each unique vertex | |
for i=1,#unique do | |
unique[i] = unique[i]:normalize() | |
end | |
--now apply averages to list of vertices | |
for i=1, #vertices,3 do --calculate average | |
local n = (vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i]) | |
for j=0,2 do | |
norm[i+j] = unique[vertices[i+j].x ..vertices[i+j].y..vertices[i+j].z] | |
end | |
end | |
return norm | |
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[1],r[2],r[3],r[4]} 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 | |
local cubetexCoords | |
if self.tex then | |
local texvertices = { vec2(self.texR[1],self.texR[2]), | |
vec2(self.texR[3],self.texR[2]), | |
vec2(self.texR[1],self.texR[4]), | |
vec2(self.texR[3],self.texR[4]) } | |
cubetexCoords = { | |
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], | |
} | |
end | |
--put it all together | |
local ms = mesh() | |
ms.vertices = cubeverts | |
if self.tex then | |
ms.texture = self.tex | |
ms.texCoords = cubetexCoords | |
ms:setColors(self.color) | |
else | |
ms.colors=cc | |
end | |
return ms | |
end | |
function Block:draw() | |
self.blk:draw() | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment