Created
July 17, 2015 06:47
-
-
Save dermotbalson/b42313c23f8e95df2312 to your computer and use it in GitHub Desktop.
Flying quaternions for blog
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 | |
function setup() | |
--plane setup | |
local p=MakePlane() --make mesh | |
plane=QC(p) --create quaternion, pass it the mesh | |
--joysticks | |
joyPR=JoyStick() --pitch/roll | |
joyY=JoyStick({centre=vec2(310,110)}) --yaw | |
--plane postion and sky | |
pos=vec3(0,0,0) | |
sky=Sky(5000,readImage("Dropbox:SkyGlobe")) | |
speed=2 | |
--camera settings | |
--1 = directly behind, 2 = behind and above, 3 = POV cockpit view | |
parameter.integer("Camera",1,3,1) | |
camSettings={{vec3(0,0,30),false},{vec3(2,5,25),false},{vec3(0,1.5,-6.75),true}} --three camera positions | |
end | |
function draw() | |
background(120) | |
perspective() | |
local jPR=joyPR:update() --get joystick movement | |
local jY=joyY:update() | |
--if joystick used, rotate plane, else level it | |
if joyPR:isTouched() or joyY:isTouched() then | |
--convert joystick values to x,y,z rotations (how you do this is up to you) | |
plane:AdjustOrientation(jPR.y,-jY.x,-jPR.x/3) | |
else | |
plane:RollLevel() | |
end | |
local p=plane:Move(vec3(0,0,-speed)) --velocity vector | |
pos=pos+p*DeltaTime --calculate new position | |
plane:SetCamera(pos,camSettings[Camera][1],camSettings[Camera][2]) | |
sky:draw(pos) | |
plane:draw(pos) | |
joyPR:draw() | |
joyY:draw() | |
end | |
function touched(t) | |
joyPR:touched(t) --get joystick touches | |
joyY:touched(t) | |
end | |
--# Utility | |
--Utility | |
--Sky utility | |
--this class creates and draws a sphere that surrounds the camera | |
--make the radius big enough to include the whole scene | |
--if the image is covering the entire sphere (top to bottom) its width should be twice its height | |
--if the image is covering the top half of the sphere, its width should be 4x its height | |
--in the case of this demo, the image is just for the top half, but it happens to look like sea | |
-- if you paste it again on the bottom half. That's why if you provide an image whose width is 4x | |
-- its height, the code pastes it twice. You can delete that code if you don't want it | |
Sky=class() | |
function Sky:init(r,tex) --r=radius, tex=texture image | |
--this code pastes the image twice - see note above | |
if tex.width==tex.height*4 then | |
local img=image(tex.width,tex.width/2) | |
setContext(img) | |
sprite(tex,img.width/2,img.height*3/4) | |
sprite(tex,img.width/2,img.height*1/4,tex.width,tex.height) | |
setContext() | |
self.sky=CreateSphere(r,img) | |
else self.sky=CreateSphere(r,tex) end --just use this if you only want to paste the image once | |
end | |
--draws the image, centred on vec3 p | |
function Sky:draw(p) pushMatrix() translate(p:unpack()) self.sky:draw() popMatrix() end | |
--these three functions create a sphere mesh | |
function CreateSphere(r,tex,col,nx,ny) | |
local vertices,tc = Sphere_OptimMesh(nx or 40,ny or 20) | |
vertices = Sphere_WarpVertices(vertices) | |
for i=1,#vertices do vertices[i]=vertices[i]*r end | |
local ms = mesh() | |
ms.vertices=vertices | |
if tex then ms.texture,ms.texCoords=tex,tc end | |
ms:setColors(col or color(255)) | |
return ms | |
end | |
function Sphere_OptimMesh(nx,ny) | |
local v,t={},{} | |
local k,s,x,y,x1,x2,i1,i2,sx,sy=0,1,0,0,{},{},0,0,nx/ny,1/ny | |
local c = vec3(1,0.5,0) | |
local m1,m2 | |
for y=0,ny-1 do | |
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 | |
x1,x2 = {},{} | |
for i1 = 1,nx1 do x1[i1] = (i1-1)/(nx1-1)*sx end x1[nx1+1] = x1[nx1] | |
for i2 = 1,nx2 do x2[i2] = (i2-1)/(nx2-1)*sx end x2[nx2+1] = x2[nx2] | |
local i1,i2,n,nMax,continue=1,1,0,0,true | |
nMax = nx*2+1 | |
while continue do | |
m1,m2=(x1[i1]+x1[i1+1])/2,(x2[i2]+x2[i2+1])/2 | |
if m1<=m2 then | |
v[k+1],v[k+2],v[k+3]=vec3(x1[i1],sy*y,1)-c,vec3(x1[i1+1],sy*y,1)-c,vec3(x2[i2],sy*(y+1),1)-c | |
t[k+1],t[k+2],t[k+3]=vec2(-x1[i1]/2,sy*y) ,vec2(-x1[i1+1]/2,sy*y),vec2(-x2[i2]/2,sy*(y+1)) | |
if i1<nx1 then i1 = i1 +1 end | |
else | |
v[k+1],v[k+2],v[k+3]=vec3(x1[i1],sy*y,1)-c,vec3(x2[i2],sy*(y+1),1)-c,vec3(x2[i2+1],sy*(y+1),1)-c | |
t[k+1],t[k+2],t[k+3]=vec2(-x1[i1]/2,sy*y),vec2(-x2[i2]/2,sy*(y+1)),vec2(-x2[i2+1]/2,sy*(y+1)) | |
if i2<nx2 then i2 = i2 +1 end | |
end | |
if i1==nx1 and i2==nx2 then continue=false end | |
k,n=k+3,n+1 | |
if n>nMax then continue=false end | |
end | |
end | |
return v,t | |
end | |
function Sphere_WarpVertices(verts) | |
local m = matrix(0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0) | |
local vx,vy,vz,vm | |
for i,v in ipairs(verts) do | |
vx,vy = v[1], v[2] | |
vm = m:rotate(180*vy,1,0,0):rotate(180*vx,0,1,0) | |
vx,vy,vz = vm[1],vm[5],vm[9] | |
verts[i] = vec3(vx,vy,vz) | |
end | |
return verts | |
end | |
-- *** Code to create blovk plane ***---- | |
--first the block | |
function CreateBlock(w,h,d,col,pos,tex) --width,height,depth,colour,position,texture | |
local x,X,y,Y,z,Z=pos.x-w/2,pos.x+w/2,pos.y-h/2,pos.y+h/2,pos.z-d/2,pos.z+d/2 | |
local v={vec3(x,y,Z),vec3(X,y,Z),vec3(X,Y,Z),vec3(x,Y,Z),vec3(x,y,z),vec3(X,y,z),vec3(X,Y,z),vec3(x,Y,z)} | |
local vert={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 texCoords | |
if tex then | |
local t={vec2(0,0),vec2(1,0),vec2(0,1),vec2(1,1)} | |
texCoords={t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3], | |
t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3],t[1],t[2],t[4],t[1],t[4],t[3]} | |
end | |
local n={vec3(0,0,1),vec3(1,0,0),vec3(0,0,-1),vec3(-1,0,0),vec3(1,0,0),vec3(-1,0,0)} | |
local norm={} | |
for i=1,6 do for j=1,6 do norm[#norm+1]=n[i] end end | |
local ms = mesh() | |
ms.vertices = vert | |
ms.normals=norm | |
if tex then ms.texture,ms.texCoords = tex,texCoords end | |
ms:setColors(col or color(255)) | |
return ms | |
end | |
--and then the plane | |
function MakePlane() | |
local img=readImage("Platformer Art:Block Brick"):copy(4,4,62,62) | |
local b=CreateBlock(1,1,1,color(255),vec3(0,0,0),img) | |
local bv,bt=b:buffer("position"),b:buffer("texCoord") | |
local m=mesh() | |
local v,t={},{} | |
local w,h,d=1.5,1.5,15 --body | |
for i=1,b.size do v[#v+1]=vec3(bv[i].x*w,bv[i].y*h,bv[i].z*d) end for i=1,b.size do t[#t+1]=bt[i] end | |
local w,h,d=1.5,0.5,2 --cockpit | |
for i=1,b.size do v[#v+1]=vec3(bv[i].x*w,bv[i].y*h+1.25,bv[i].z*d-4) end for i=1,b.size do t[#t+1]=bt[i] end | |
local w,h,d=16,0.25,3 --wings | |
for i=1,b.size do v[#v+1]=vec3(bv[i].x*w,bv[i].y*h+0.5,bv[i].z*d-3) end for i=1,b.size do t[#t+1]=bt[i] end | |
local w,h,d=8,0.25,1.5 --tail horizontal | |
for i=1,b.size do v[#v+1]=vec3(bv[i].x*w,bv[i].y*h+0.5,bv[i].z*d+7) end for i=1,b.size do t[#t+1]=bt[i] end | |
local w,h,d=0.25,3,2 --tail vertical | |
for i=1,b.size do v[#v+1]=vec3(bv[i].x*w,bv[i].y*h+2,bv[i].z*d+7) end for i=1,b.size do t[#t+1]=bt[i] end | |
m.texture=img | |
m.vertices=v | |
m.texCoords=t | |
m:setColors(color(255)) | |
return m | |
end | |
--# Controls | |
--Controls | |
--contains Joystick controls and Tilting controls | |
--1. Tilting the iPad (not used in this demo) | |
--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 | |
--the damp variable sets a "dead" area in the middle of the joystick where touches don't have an effect | |
--the default for this is 0.1. This means that sideways or vertical movements don't have any effect until | |
--they get beyond 10% of the radius away from the centre. The reason is that our fingers are not accurate, | |
--and if you are just trying to go up, it is difficult to do that without your finger going a little left or right | |
--and making the plane tip as well. If the middle of the joystick is "dead", your finger doesn't need to be so | |
--accurate. | |
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.damp=t.damp or vec2(0.1,0.1) --see note above | |
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 | |
local v=self.position/(self.radius - self.stick) | |
return self:Dampen(v) | |
end | |
function JoyStick:Dampen(v) | |
if not self.damp then return v end | |
if v.x>0 then v.x=math.max(0,(v.x-self.damp.x)/(1-self.damp.x)) | |
else v.x=math.min(0,(v.x+self.damp.x)/(1-self.damp.x)) end | |
if v.y>0 then v.y=math.max(0,(v.y-self.damp.y)/(1-self.damp.y)) | |
else v.y=math.min(0,(v.y+self.damp.y)/(1-self.damp.y)) end | |
return v | |
end | |
function JoyStick:isMoving() | |
return self.moving | |
end | |
function JoyStick:isTouched() | |
return self.touch | |
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 | |
--# Quaternion | |
--Quaternion class | |
QC=class() | |
local sin,cos,pi,rad,deg=math.sin,math.cos,math.pi,math.rad,math.deg | |
local asin,acos,atan2=math.asin,math.acos,math.atan2 | |
local sqrt,abs,min,max=math.sqrt,math.abs,math.min,math.max | |
QC.euler={["xyz"]={1,2,3,1},["xzy"]={1,3,2,-1},["yxz"]={2,1,3,-1}, | |
["yzx"]={2,3,1,1},["zxy"]={3,1,2,1},["zyx"]={3,2,1,-1}} | |
QC.t={["xyz"]={-1,1,-1,1},["xzy"]={1,-1,-1,1},["yxz"]={1,1,-1,-1}, | |
["yzx"]={-1,1,1,-1},["zxy"]={-1,-1,1,1},["zyx"]={1,-1,1,-1}} | |
QC.seq,QC.seq2="yxz","yzx" | |
function QC:init(m,level) | |
self.q=vec4(1,0,0,0) | |
self.m=m | |
self:Reset() | |
self.level=level or vec3(0,1,0) | |
self.f=0.99 | |
self.limit=vec3(85,9999,175) | |
end | |
function QC:Reset() | |
self.q=vec4(1,0,0,0) | |
self.prevAngles=vec3(0,0,0) | |
self.factor=vec3(1,1,1) | |
end | |
function QC:RotateDegrees(x,y,z,seq) | |
local q1=QC.EulerToQuat(x, y, z, true, seq) | |
self.q=QC.Multiply(self.q,q1) | |
end | |
function QC:RotateRadians(x,y,z,seq) | |
local q1=QC.EulerToQuat(x, y, z, false, seq) | |
self.q=QC.Multiply(self.q,q1) | |
end | |
function QC:draw(v,m) | |
v=v or vec3(0,0,0) | |
m=m or self.m | |
local rm=self:QToMatrix(v) | |
pushMatrix() | |
modelMatrix(rm) | |
m:draw() | |
popMatrix() | |
end | |
function QC:Move(d) | |
if type(d)=="number" then return self:RotateVector(vec3(0,0,-d)) | |
else return self:RotateVector(d) end | |
end | |
function QC:SetRotation(v) | |
local m=self:QToMatrix() | |
m[13],m[14],m[15]=v:unpack() | |
modelMatrix(m) | |
end | |
function QC:SetCamera(pos,camPos,rotateZ) | |
local c=pos+self:RotateVector(camPos) | |
local t=c+self:RotateVector(vec3(0,0,-1)) --camera looks at this point | |
local Up | |
if rotateZ then Up=self:RotateVector(vec3(0,1,0)) else Up=vec3(0,1,0) end | |
--print(camPos) | |
camera(c.x,c.y,c.z,t.x,t.y,t.z,Up.x,Up.y,Up.z) | |
end | |
function QC:RollLevel() | |
if not self.levelling then | |
local v=self:RotateVector(vec3(0,0,-1)) | |
local a=-math.deg(math.atan2(v.x,-v.z)) | |
self.qt=QC.EulerToQuat(0, a, 0, true) | |
self.levelling=true | |
end | |
self.q=self.Lerp(self.q,self.qt,0.02) | |
end | |
function QC:AdjustOrientation(x,y,z) | |
self:RotateDegrees(x,y,z) | |
self.levelling=nil | |
end | |
--helper functions | |
function QC.EulerToQuat(x,y,z,degrees,seq) | |
if degrees then x,y,z=rad(x),rad(y),rad(z) end | |
seq=seq or QC.seq | |
-- compute all trigonometric values used to compute the quaternion | |
local cx,sx = cos(x/2), sin(x/2) | |
local cy,sy = cos(y/2), sin(y/2) | |
local cz,sz = cos(z/2), sin(z/2) | |
local w1, w2 = cx * cy * cz, sx * sy * sz | |
local x1, x2 = sx * cy * cz, cx * sy * sz | |
local y1, y2 = cx * sy * cz, sx * cy * sz | |
local z1, z2 = cx * cy * sz, sx * sy * cz | |
local t=QC.t[seq] | |
return vec4(w1+t[1]*w2,x1+t[2]*x2,y1+t[3]*y2,z1+t[4]*z2) | |
end | |
function QC.Multiply(q1,q2,dontNormalise) | |
local w1,x1,y1,z1=q1.x,q1.y,q1.z,q1.w | |
local w2,x2,y2,z2=q2.x,q2.y,q2.z,q2.w | |
local w = w1*w2 - x1*x2 - y1*y2 - z1*z2 | |
local x = w1*x2 + x1*w2 + y1*z2 - z1*y2 | |
local y = w1*y2 - x1*z2 + y1*w2 + z1*x2 | |
local z = w1*z2 + x1*y2 - y1*x2 + z1*w2 | |
local v=vec4(w,x,y,z) | |
if dontNormalise then return v else return v:normalize() end | |
end | |
function QC:QToMatrix(v) | |
-- calculate coefficients used for building the matrix | |
v=v or vec3(0,0,0) | |
local w,x,y,z=self.q.x,self.q.y,self.q.z,self.q.w | |
local x2,y2,z2 = x + x,y + y,z + z | |
local xx,xy,xz = x * x2,x * y2,x * z2 | |
local yy,yz,zz = y * y2,y * z2,z * z2 | |
local wx,wy,wz = w * x2,w * y2,w * z2 | |
return matrix(1-(yy+zz),xy+wz,xz-wy,0, | |
xy-wz,1-(xx+zz),yz+wx,0, | |
xz+wy,yz-wx,1-(xx+yy),0, | |
v.x,v.y,v.z,1) | |
end | |
function QC:RotateVector(v) | |
local w,x,y,z=self.q.x,self.q.y,self.q.z,self.q.w | |
local x1 = y*v.z - z*v.y | |
local y1 = z*v.x - x*v.z | |
local z1 = x*v.y - y*v.x | |
local x2 = w*x1 + y*z1 - z*y1 | |
local y2 = w*y1 + z*x1 - x*z1 | |
local z2 = w*z1 + x*y1 - y*x1 | |
return vec3(v.x+2*x2,v.y+2*y2,v.z+2*z2) | |
end | |
function QC.LookAtMatrix(source,target,up) | |
up=up or vec3(0,1,0) | |
local Z=(source-target):normalize() | |
local X=(up:cross(Z)):normalize() | |
local Y=(Z:cross(X)):normalize() | |
return matrix(X.x,X.y,X.z,0, Y.x,Y.y,Y.z,0, Z.x,Z.y,Z.z,0, source.x,source.y,source.z,1) | |
end | |
function QC:RotateTowards(source,target,limit,type,up) | |
type=type or 1 | |
local m=QC.LookAtMatrix(source,target,up) | |
if not limit then self.q=QC.MatrixToQ(m) return end | |
local q2=QC.MatrixToQ(m) | |
local a=QC.AngleBetween(self.q,q2) | |
if abs(a)<0.001 then return end | |
local u=min(1,rad(limit)/a) | |
if type==1 then self.q=QC.Lerp(self.q,q2,u) else self.q=QC.Slerp(self.q,q2,u) end | |
end | |
function QC.MatrixToQ(m) | |
local w,x,y,z | |
local t=1+m[1]+m[6]+m[11] | |
if t>0.00000001 then | |
local s=sqrt(t)*2 | |
x=(m[7]-m[10])/s | |
y=(m[9]-m[3])/s | |
z=(m[2]-m[5])/s | |
w=0.25*s | |
else | |
if m[1] > m[6] and m[1] > m[11] then -- Column 1 | |
local s = sqrt( 1.0 + m[1] - m[6] - m[11] ) * 2 | |
x = 0.25 * s | |
y = (m[2] + m[5] ) / s | |
z = (m[9] + m[3] ) / s | |
w = (m[7] - m[10] ) / s | |
elseif m[6] > m[11] then -- Column 2 | |
local s = sqrt( 1.0 + m[6] - m[1] - m[11] ) * 2 | |
x = (m[2] + m[5] ) / s | |
y = 0.25 * s | |
z = (m[7] + m[10] ) / s | |
w = (m[9] - m[3] ) / s | |
else --Column 3 | |
local s = sqrt( 1.0 + m[11] - m[1] - m[6] ) * 2 | |
x = (m[9] + m[3] ) / s | |
y = (m[7] + m[10] ) / s | |
z = 0.25 * s | |
w = (m[2] - m[5] ) / s | |
end | |
end | |
return vec4(w,x,y,z) | |
end | |
function QC.AngleBetween(q1,q2) | |
q1,q2=q1:normalize(),q2:normalize() | |
local a=2*acos(q1:dot(q2)) | |
if not a then a=0 end | |
local pi=pi | |
if abs(a)>pi then | |
if a>0 then a=2*pi-a else a=-a-2*pi end | |
end | |
return a | |
end | |
function QC.Lerp(q1,q2,t) | |
if q1.x*q2.x+q1.y*q2.y+q1.z*q2.z+q1.w*q2.w<0 then return (q1*(1-t)-q2*t):normalize() | |
else return (q1*(1-t)+q2*t):normalize() | |
end | |
end | |
function QC.Slerp(q1,q2,t) | |
local w1,x1,y1,z1,w2,x2,y2,z2=q1.x,q1.y,q1.z,q1.w,q2.x,q2.y,q2.z,q2.w | |
local a=acos(q1:dot(q2)) | |
local sa=sin(a) | |
if sa==0 then return q1 | |
else return ((q1*sin((1-t)*a)+q2*sin(t*a))/sa):normalize() end | |
end | |
function QC.MatrixByVector(m,v) | |
local x=m[1]*v.x+m[5]*v.y+m[9]*v.z | |
local y=m[2]*v.x+m[6]*v.y+m[10]*v.z | |
local z=m[3]*v.x+m[7]*v.y+m[11]*v.z | |
if v.w then | |
x,y,z=x+m[13],y+m[14],z+m[15] | |
return vec4(x,y,z,1) | |
else return vec3(x,y,z) end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment