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