Skip to content

Instantly share code, notes, and snippets.

@JMV38
Created November 25, 2012 17:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JMV38/4144412 to your computer and use it in GitHub Desktop.
Save JMV38/4144412 to your computer and use it in GitHub Desktop.
Mesh loader
--# Convert
Convert = class()
-- converting a 3d object into a color image is done in steps:
-- 1/ convert each element of object in an image
-- 2/ assemble the image elements in one big image
-- the reading is the same in inverse order
function Convert:init()
end
function Convert:saveMesh(ms,name,debug)
local obj = {}
obj.texture = ms.texture
obj.vertices = ms.vertices
obj.texCoords = ms.texCoords
obj.colors = ms.colors
local img,size = self:pushObject00(obj,debug)
if debug then print("mesh size = "..math.floor(size*100).."% of max size") end
self:saveImage(name, img)
return size
end
function Convert:loadMesh(name,debug)
local img = self:readImage(name)
local obj = self:popObject00(img,debug)
local ms = mesh()
ms.texture = obj.texture
ms.vertices = obj.vertices
ms.texCoords = obj.texCoords
ms.colors = obj.colors
return ms
end
-- local functions to be faster
local insert = table.insert
local floor = math.floor
function Convert:saveImage(name,img)
local w,h = img.width, img.height
local x,y,r,g,b,a
local out = image(w,h*2)
for x=1,w do for y=1,h do
r,g,b,a = img:get(x,y)
out:set(x,y*2-1,r,g,0,255)
out:set(x,y*2 ,b,a,0,255)
end end
saveImage("Documents:"..name,out)
end
function Convert:readImage(name)
local img = readImage("Documents:"..name)
local w,h = img.width, img.height
local x,y,r,g,b,a,_
local out = image(w,h/2)
for x=1,w do for y=1,h/2 do
r,g,_,_ = img:get(x,y*2-1)
b,a,_,_ = img:get(x,y*2)
out:set(x,y,r,g,b,a)
end end
return out
end
function debugPrint(debug,txt,t)
if debug then
local str = ""
if type(t)=="table" then str ="{"..tostring(t[1])..",... }"
elseif t~=nil then str = tostring(t)
end
print(txt..str)
end
end
function Convert:popObject00(img,debug)
local n
local t,out = {},{}
-- read verification information
n = 1
t,n = self:getTable(n,img)
self:popToObject(t,out,debug)
t,n = self:getTable(n,img)
self:popToObject(t,out,debug)
if out.id~="JMV38-3d-object"
then error("this image is not a 3d object saved to an image")
elseif out.saveVersion ~= 0
then error("the object version ("..out.saveVersion..") is not compatible with this reader")
end
-- get other elements
t,n = self:getTable(n,img)
while t do
self:popToObject(t,out,debug)
t,n = self:getTable(n,img)
end
return out,n
end
function Convert:pushObject00(obj,debug)
local input = {}
input.id = "JMV38-3d-object"
input.saveVersion = 0
-- put everything in a table
local t ={}
self:pushToTable("id",input,t)
debugPrint(debug,"pushed : obj.".."id".." = ",input.id)
self:pushToTable("saveVersion",input,t)
debugPrint(debug,"pushed : obj.".."saveVersion".." = ",input.saveVersion)
local i,v
for i,v in pairs(obj) do
self:pushToTable(i,obj,t)
debugPrint(debug,"pushed : obj."..i.." = ",v)
end
-- 1 more pixel at 0 to indicate it is finished (and avoid image overflow)
insert(t, self:intToColor(0) )
-- convert to an image and return it
return self:tableToImage(t)
end
function Convert:tableToImage(t)
-- write table in an image
local n = #t
local h = floor(n/2048)+1
local sizefraction = (h+1)/2048
local txt
if sizefraction>1 then
txt = "Sorry, your mesh size once converted is: "..floor(sizefraction*100)
txt = txt.."% of maximum size, too big for this saving tool."
error(txt)
end
local w
if n>2048 then w=2048 else w=n end
local img = image(w,h)
local x,y,i
for i=1,n do
y = floor((i-1)/2048)
x = i-1 - y*2048
img:set(x+1,y+1,t[i])
-- if self:type(t[i])~="color" then
-- error(i.." : "..tostring(t[i]).." : "..self:type(t[i])) end
end
return img,sizefraction
end
function Convert:xy(i)
local x,y
y = floor((i-1)/2048)
x = i-1 - y*2048
return x+1,y+1
end
function Convert:getTable(n,img)
-- write some of the colors into a table
local w,h = img.width, img.height
local x,y,i
local t = {}
nmax = self:colorToInt( color(img:get(self:xy(n))) )
if nmax==0 then t=nil
else
for i=0,nmax do
t[i+1] = color(img:get( self:xy(i+n) ))
end
end
return t,(n+nmax+1)
end
-- **********************************************
function Convert:supported(typ)
local ans = false
if typ=="string" then ans = true
elseif typ=="number" then ans = true
elseif typ=="image" then ans = true
elseif typ=="{color}"then ans = true
elseif typ=="{vec3}" then ans = true
elseif typ=="{vec2}" then ans = true
elseif typ=="matrix" then ans = true
end
return ans
end
function Convert:pushToTable(tag,obj,out)
local x = obj[tag]
local typ = self:type(x)
if self:supported(typ) then
local n = #out
self:pushString(tag,out)
insert(out, self:stringToColor("=") )
self:pushString(typ,out)
insert(out, self:stringToColor("=") )
if typ=="string" then self:pushString(x,out)
elseif typ=="number" then self:pushNumber(x,out)
elseif typ=="image" then self:pushImage(x,out)
elseif typ=="{color}"then self:pushColors(x,out)
elseif typ=="{vec3}" then self:pushVec3(x,out)
elseif typ=="{vec2}" then self:pushVec2(x,out)
elseif typ=="matrix" then self:pushMatrix(x,out)
end
local n1 = #out - n
insert(out, n+1, self:intToColor(n1) )
else
print("the type of '"..tag.."' is not supported for saving: "..typ)
end
return out
end
function Convert:pushNumber(x,out)
insert(out,self:floatToColor(x))
end
function Convert:pushMatrix(m,out)
local i
for i=1,16 do
insert(out,self:floatToColor(m[i]))
end
end
function Convert:pushVec2(t,out)
local i,v
for i,v in ipairs(t) do
-- this is a texCoord, so it is transformed in (1-y)
insert(out,self:floatToColor(v.x))
insert(out,self:floatToColor(1.0-v.y))
end
end
function Convert:pushVec3(t,out)
local i,v
for i,v in ipairs(t) do
insert(out,self:floatToColor(v.x))
insert(out,self:floatToColor(v.y))
insert(out,self:floatToColor(v.z))
end
end
function Convert:pushColors(t,out)
local i,v,r,g,b,a
for i,v in ipairs(t) do
-- the color table is read from a mesh: it is normalized into [0.0;1.0]
r,g,b,a = v.r, v.g, v.b, v.a
r,g,b,a = floor(r*255),floor(g*255),floor(b*255),floor(a*255)
v = color(r,g,b,a)
insert(out,v)
end
end
function Convert:pushImage(img,out)
local w,h,x,y
w,h = img.width, img.height
insert(out,self:intToColor(w))
insert(out,self:intToColor(h))
for x=1,w do
for y=1,h do
insert(out,color(img:get(x,y)))
end
end
end
function Convert:pushString(txt,out)
local t1,t2
if txt:len()>4 then
t1 = txt:sub(1,4)
t2 = txt:sub(5)
else
t1 = txt
end
insert(out,self:stringToColor(t1))
if t2 then self:pushString(t2,out) end
end
-- **********************************************
function Convert:getString(t,n)
local p = n+1
while self:colorToString(t[p]) ~= "=" do p = p + 1 end
local res = {}
local i
-- for i=1,(p-n-1) do insert(res,t[n+i]) end
for i=1,(p-n-1) do insert(res, self:colorToString(t[n+i]) ) end
return table.concat(res,""),p
end
function Convert:popToObject(t,obj,debug)
local nmax = self:colorToInt( t[1] ) + 1
local n = 1
local tag,typ
tag,n = self:getString(t,n)
typ,n = self:getString(t,n)
local x,i
local out
if typ=="string" then out,n = self:popString(n,nmax,t)
elseif typ=="number" then out,n = self:popNumber(n,nmax,t)
elseif typ=="image" then out,n = self:popImage(n,nmax,t)
elseif typ=="{color}" then out,n = self:popColors(n,nmax,t)
elseif typ=="{vec3}" then out,n = self:popVec3(n,nmax,t)
elseif typ=="{vec2}" then out,n = self:popVec2(n,nmax,t)
elseif typ=="matrix" then out,n = self:popMatrix(n,nmax,t)
end
obj[tag]=out
debugPrint(debug,"pop : obj."..tag.." = ",out)
return tag,n
end
function Convert:popNumber(n,nmax,t)
local out
out = self:colorToFloat( t[1+n] )
return out,n+1+1
end
function Convert:popMatrix(n,nmax,t)
local i
local out = matrix()
for i=1,16 do
out[i] = self:colorToFloat( t[i+n] )
end
return out,n+16+1
end
function Convert:popVec2(n,nmax,t)
local i=1
local out = {}
while i<(nmax-n) do
local v = vec2()
v.x = self:colorToFloat( t[i+n] )
v.y = self:colorToFloat( t[i+n+1] )
out[(i+1)/2] = v
i = i + 2
end
return out,n+i
end
function Convert:popVec3(n,nmax,t)
local i=1
local out = {}
while i<(nmax-n) do
local v = vec3()
v.x = self:colorToFloat( t[i+n] )
v.y = self:colorToFloat( t[i+n+1] )
v.z = self:colorToFloat( t[i+n+2] )
out[(i+2)/3] = v
i = i + 3
end
return out,n+i
end
function Convert:popColors(n,nmax,t)
local i
local out = {}
for i=1,nmax-n do out[i] = t[i+n] end
return out,n+nmax+1
end
function Convert:popImage(n,nmax,t)
local w,h,x,y
w = self:colorToInt(t[n+1])
h = self:colorToInt(t[n+2])
local out = image(w,h)
local i = n+3
for x=1,w do
for y=1,h do
out:set(x,y,t[i])
i = i + 1
end
end
return out,n+i
end
function Convert:popString(n,nmax,t)
local i
local out = {}
for i=1,nmax-n do out[i] = self:colorToString(t[i+n]) end
out = table.concat(out,"")
return out,nmax+1
end
-- **********************************************
-- basic function to convert to and from colors
local abs = math.abs
local log10 = math.log10
local pow = math.pow
-- type(v):"nil", "number", "string", "boolean", "table", "function", "thread", "userdata".
function Convert:type(x)
local txt = type(x)
function typeCompare(x,ref)
-- nb: returns an error if x or ref have no metatable!
local i = getmetatable(x).__index
local j = getmetatable(ref).__index
return (i==j)
end
if txt == "userdata" then
if typeCompare(x,vec2()) then txt ="vec2"
elseif typeCompare(x,vec3()) then txt ="vec3"
elseif typeCompare(x,color()) then txt ="color"
elseif typeCompare(x,image(1,1)) then txt ="image"
elseif typeCompare(x,matrix()) then txt ="matrix"
end
end
if txt == "table" then
txt = "{"..self:type(x[1]).."}"
end
return txt
end
function Convert:intToColor(dat)
-- abs value coded on 4 octets uint (r,g,b,a)
local r,g,b,a = 0,0,0,0
local x
-- chech no sign
x = abs(dat)
if x~=dat then error("only uint supported : "..dat) end
-- convert in octet basis
r = x % 256
x = floor(x/256)
g = x % 256
x = floor(x/256)
b = x % 256
x = floor(x/256)
a = x
if a>255 then error("this integer is too big to save : "..dat) end
return color(r,g,b,a)
end
function Convert:colorToInt(c)
local r,g,b,a = c.r, c.g, c.b, c.a
local i = r +256*(g + 256*(b +256*a))
return i
end
function Convert:floatToColor(dat)
-- abs value coded on 3 octets (r,g,b)
-- a is coding the power of 10, from -64 to +63
-- the sign is coded by 1 bit (+128) in a
-- accuracy limit: about less than 10/(256*256*256) relative error
local r,g,b,a = 0,0,0,0
local x,sgn,p,c
-- remove the sign and save it
x = abs(dat)
if x==dat then sgn = 0 else sgn = 128 end
-- get the exponent
if x==0 then p=0 else
p = log10(x)
p = floor(p)
end
if (p<-64) or (63<p) then -- overflow
-- in case of overflow clip the data
if p<-64 then p=-64 else p=63 end
r,g,b,a =255,255,255,127
else
-- convert to int
x = x / pow(10,p)/10 -- number x is now : 0.abcdefg with a from 1 to 9
x = floor( x *255 * 256 * 256 )
-- convert to octet basis
c = self:intToColor(x)
r,g,b,a = c.r, c.g, c.b, c.a
if a>0 then error("bug: a should be 0") end
end
-- a saves sign and exponent
a = (p + 64) + sgn
return color(r,g,b,a)
end
function Convert:colorToFloat(c)
local r,g,b,a = c.r, c.g, c.b, c.a
local sgn
if a >127 then sgn =-1 ; a=a-128 else sgn =1 end
local p = a - 64
local x = ((r/256 + g)/256 +b)/255 * pow(10,p) * 10 * sgn
return x
end
function Convert:stringToColor(t)
local r,g,b,a = 0,0,0,0
local l = t:len()
if l>0 then r = t:byte(1) end
if l>1 then g = t:byte(2) end
if l>2 then b = t:byte(3) end
if l>3 then a = t:byte(4) end
if l>4 then error("the text: "..t.." is too long (4 chars max).") end
return color(r,g,b,a)
end
function Convert:colorToString(c)
local r,g,b,a = c.r, c.g, c.b, c.a
local txt = ""
if a>0 then txt = string.char(r,g,b,a)
elseif b>0 then txt = string.char(r,g,b)
elseif g>0 then txt = string.char(r,g)
elseif r>0 then txt = string.char(r)
end
return txt
end
--# Controllers
function resetTest(v)
if v=="reset" then
confirmBox:popup("Do you really want to reset all controllers?",
function() print(confirmBox.choice) end)
--allControllers:resetDemo()
end
end
--# ConfirmBox
ConfirmBox = class()
function ConfirmBox:init()
self.message = "confirm this decision?"
self.choice = nil
self.active = false
self.callback = nil
-- outer box
self.x0,self.y0,self.w0,self.h0 = WIDTH/2, HEIGHT/2, WIDTH/8, HEIGHT/8
-- text message
self.x1,self.y1,self.w1,self.h1 = WIDTH/2, HEIGHT*(1/2+1/20), WIDTH/9, HEIGHT/32
-- confirm
self.x2,self.y2,self.w2,self.h2 = WIDTH*(1/2-1/16), HEIGHT*(1/2-1/16), WIDTH/20, HEIGHT/32
-- cancel
self.x3,self.y3,self.w3,self.h3 = WIDTH*(1/2+1/16), HEIGHT*(1/2-1/16), WIDTH/20, HEIGHT/32
end
function ConfirmBox:popup(message,callback)
self.message = message
self.callback = callback
self.active = true
self.choice = nil
end
function ConfirmBox:draw()
if self.active then
pushStyle() pushMatrix()
font("AmericanTypewriter-Bold")
rectMode(RADIUS)
textMode(RADIUS)
strokeWidth(5)
fill(127, 127, 127, 255)
stroke(0, 0, 0, 255)
fontSize(20)
rect(self.x0,self.y0,self.w0,self.h0)
rect(self.x2,self.y2,self.w2,self.h2)
rect(self.x3,self.y3,self.w3,self.h3)
fill(0, 0, 0, 255)
textWrapWidth(self.w1*2)
text("confirm",self.x2,self.y2)
text("cancel",self.x3,self.y3)
text(self.message,self.x1,self.y1)
popStyle() popMatrix()
end
end
function ConfirmBox:touched(touch)
local choice = nil
if self.active then
if math.abs((touch.x- self.x2))<self.w2
and math.abs((touch.y- self.y2))<self.h2
then choice = true
elseif math.abs((touch.x- self.x3))<self.w3
and math.abs((touch.y- self.y3))<self.h3
then choice = false
end
self.choice = choice
if choice~=nil then
self.active = false
if self.callback then self.callback() end
end
end
end
--# InfoBloc
InfoBloc = class()
function InfoBloc:init(args)
-- layout
self.title = args.title or "info"
self.txt = args.txt or "the side bar is highlighted when info is present"
self.side = args.side or "bottom"
self.textPos = args.textPos or vec2(WIDTH/2,HEIGHT-100)
self.textWidth = args.textWidth or 700
self.popupEnabled = args.popupEnabled or true
self.t0 = ElapsedTime
self.timeout = 0.5 -- seconds before txt vanishes
-- settings by pos and w
local x = args.pos
local w = args.width or 0.09
-- or by x0 and x1
if args.x0 and args.x1 then
x = (args.x0 + args.x1)/2
w = args.x1 - args.x0
end
local w1,w2,h1,h2
local pos
local dx0,dy0,dx,dy
local htxt = 30
local w0,h0 = math.floor( w * WIDTH ), math.floor( w * HEIGHT )
if self.side == "top" then
w1,w2,h1,h2 = w0,w0,htxt,htxt
pos = vec2(x*WIDTH, HEIGHT-h1/2)
dx0,dy0,dx,dy = 0, -h1-5, 0, -h2-2
elseif self.side == "bottom" then
w1,w2,h1,h2 = w0,w0,htxt,htxt
pos = vec2(x*WIDTH, h1/2)
dx0,dy0,dx,dy = 0,h1+5, 0, h2+2
elseif self.side == "left" then
w1,w2,h1,h2 = htxt,w0,h0,htxt
pos = vec2(w1/2, x*HEIGHT)
dx0,dy0,dx,dy = w1/2+5+w2/2, h1/2-h2/2, 0, -h2-2
elseif self.side == "right" then
w1,w2,h1,h2 = htxt,w0,h0,htxt
pos = vec2(WIDTH-w1/2, x*HEIGHT)
dx0,dy0,dx,dy = -w1/2-5-w2/2, h1/2-h2/2, 0, -h2-2
end
self.img = self:calcImg(self.title,w1,h1,self.side)
-- position of images
local x,y = pos.x,pos.y
self.pos = {}
self.radius = {}
self.pos["title"] = pos
self.radius["title"] = vec2(w1/2,h1/2)
-- manage various drawing states
self.deployed = false
self.highlighted = true -- the title is highlighted
end
function InfoBloc:resetDemo()
-- do nothing
end
function InfoBloc:changeTxt(txt)
if txt then
self.txt = txt
self.highlighted = true -- the title is highlighted
else
self.txt = nil
self.highlighted = false -- the title is not highlighted
end
-- reset timing
if self.popupEnabled then
self.t0 = ElapsedTime
self.finishing = true
end
end
function InfoBloc:calcImg(txt,w,h,rot)
local w0,h0
pushStyle() pushMatrix()
if rot=="left" or rot=="right"
then w0,h0 = h,w
else w0,h0 = w,h
end
local img0 = image(w0,h0)
setContext(img0)
font("AmericanTypewriter-Bold")
rectMode(CENTER)
textMode(CENTER)
strokeWidth(1)
background(255, 255, 255, 184)
fill(0, 0, 0, 255)
stroke(0, 0, 0, 255)
fontSize(20)
text(txt,w0/2,h0/2)
setContext()
local img = image(w,h)
setContext(img)
background(255, 255, 255, 184)
spriteMode(CENTER)
translate(w/2,h/2)
if rot=="left" then rotate(-90) end
if rot=="right" then rotate(90) end
sprite(img0,0,0)
setContext()
popStyle() popMatrix()
return img
end
function InfoBloc:draw()
pushStyle()
spriteMode(CENTER)
-- menu title
if self.highlighted then
local n = 1
tint(255,255,255,128 + (math.cos(ElapsedTime*2*math.pi*n)+1)/2*128 )
else tint(255,255,255,128)
end
local pos = self.pos
-- menu button
sprite(self.img, pos["title"].x, pos["title"].y)
-- deployed or finishing
if self.deployed or self.finishing then
local isel = self.selected
local delta = (self.timeout-(ElapsedTime - self.t0))/self.timeout
if delta<0 then finishing = false end
local alpha = 255
if self.finishing and not self.deployed then alpha = 255*delta end
font("AmericanTypewriter-Bold")
textMode(CENTER)
textAlign(LEFT)
textWrapWidth(self.textWidth)
strokeWidth(1)
fill(194, 194, 194, alpha)
stroke(0, 0, 0, 255)
fontSize(20)
if self.txt then text(self.txt,self.textPos.x,self.textPos.y) end
end
popStyle()
end
function InfoBloc:titleTouched(touch)
local pos,radius= self.pos["title"],self.radius["title"]
local goodZone = false
if math.abs((touch.x-pos.x))<radius.x
and math.abs((touch.y-pos.y))<radius.y
then
goodZone = true
end
self.choiceDone = false
return goodZone
end
function InfoBloc:touched(t)
if self:titleTouched(t) and t.state == BEGAN then
self.deployed=true
self.highlighted = false -- stop flickering
end
if t.state == ENDED and self.deployed then
self.deployed=false
-- reset timing
self.t0 = ElapsedTime
self.finishing = true
end
end
--# Menu
Menu = class()
function Menu:init(args)
-- actions
self.callback = args.callback or doNothing
-- layout
self.title = args.title or "menu"
local list = args.list or {"choice1",true,"choice2",true,"choice3",true}
self.list,self.disabled = {},{}
local imax = #list/2
for i=1,imax do
self.list[i] = list[2*i-1]
self.disabled[i] = not list[2*i]
end
self.selected = args.selected or 1
self.side = args.side or "bottom"
-- settings by pos and w
local x = args.pos
local w = args.width or 0.09
-- or by x0 and x1
if args.x0 and args.x1 then
x = (args.x0 + args.x1)/2
w = args.x1 - args.x0
end
local w1,w2,h1,h2
local pos
local dx0,dy0,dx,dy
local htxt = 30
local w0,h0 = math.floor( w * WIDTH ), math.floor( w * HEIGHT )
if self.side == "top" then
w1,w2,h1,h2 = w0,w0,htxt,htxt
pos = vec2(x*WIDTH, HEIGHT-h1/2)
dx0,dy0,dx,dy = 0, -h1-5, 0, -h2-2
elseif self.side == "bottom" then
w1,w2,h1,h2 = w0,w0,htxt,htxt
pos = vec2(x*WIDTH, h1/2)
dx0,dy0,dx,dy = 0,h1+5, 0, h2+2
elseif self.side == "left" then
w1,w2,h1,h2 = htxt,w0,h0,htxt
pos = vec2(w1/2, x*HEIGHT)
dx0,dy0,dx,dy = w1/2+5+w2/2, h1/2-h2/2, 0, -h2-2
elseif self.side == "right" then
w1,w2,h1,h2 = htxt,w0,h0,htxt
pos = vec2(WIDTH-w1/2, x*HEIGHT)
dx0,dy0,dx,dy = -w1/2-5-w2/2, h1/2-h2/2, 0, -h2-2
end
-- create text images
self.img = {}
for i,txt in ipairs(self.list) do
self.img[i] = self:calcImg(txt,w2,h2)
end
self.img["title"] = self:calcImg(self.title,w1,h1,self.side)
-- position of images
local x,y = pos.x,pos.y
self.pos = {}
self.radius = {}
self.pos["title"] = pos
self.radius["title"] = vec2(w1/2,h1/2)
for i,img in ipairs(self.img) do
if i==1 then x,y = x+dx0,y+dy0
else x,y = x+dx,y+dy end
self.pos[i] = vec2(x,y)
self.radius[i] = vec2(w2/2,h2/2)
end
-- manage various drawing states
self.deployed = false
self.highlighted = nil -- the choice highlighted
end
function Menu:resetDemo()
-- do nothing
end
function Menu:calcImg(txt,w,h,rot)
local w0,h0
pushStyle() pushMatrix()
if rot=="left" or rot=="right"
then w0,h0 = h,w
else w0,h0 = w,h
end
local img0 = image(w0,h0)
setContext(img0)
font("AmericanTypewriter-Bold")
rectMode(CENTER)
textMode(CENTER)
strokeWidth(1)
-- background(255, 255, 255, 184)
background(255, 255, 255, 255)
fill(0, 0, 0, 255)
stroke(0, 0, 0, 255)
fontSize(20)
text(txt,w0/2,h0/2)
setContext()
local img = image(w,h)
setContext(img)
-- background(255, 255, 255, 184)
background(255, 255, 255, 255)
spriteMode(CENTER)
translate(w/2,h/2)
if rot=="left" then rotate(-90) end
if rot=="right" then rotate(90) end
sprite(img0,0,0)
setContext()
popStyle() popMatrix()
return img
end
function Menu:draw()
pushStyle()
spriteMode(CENTER)
-- menu title
if self.deployed then
tint(196, 196, 196, 255)
else tint(211, 211, 211, 127)
end
local pos = self.pos
-- menu button
sprite(self.img["title"], pos["title"].x, pos["title"].y)
-- deployed
if self.deployed then
local isel = self.selected
for i,img in ipairs(self.img) do
pushStyle()
if self.disabled[i] then tint(72, 72, 72, 255)
elseif i==isel then tint(255,255,255,255)
else tint(127, 127, 127, 255) end
sprite(img, pos[i].x, pos[i].y)
popStyle()
end
end
popStyle()
end
function Menu:titleTouched(touch)
local pos,radius= self.pos["title"],self.radius["title"]
local goodZone = false
if math.abs((touch.x-pos.x))<radius.x
and math.abs((touch.y-pos.y))<radius.y
then
goodZone = true
end
self.choiceDone = false
return goodZone
end
function Menu:selectTouched(touch)
local pos,radius
for i,v in ipairs(self.pos) do
pos,radius = self.pos[i],self.radius[i]
if math.abs((touch.x-pos.x))<radius.x
and math.abs((touch.y-pos.y))<radius.y
and not self.disabled[i]
then self.selected = i self.choiceDone=true
end
end
end
function Menu:touched(t)
if self:titleTouched(t) and t.state == BEGAN then
self.deployed=true
self.initialSelect = self.selected
end
if self.deployed then
self:selectTouched(t)
end
if t.state == ENDED and self.deployed then
self.deployed=false
if self.choiceDone then
self.callback(self.list[self.selected])
end
end
end
--# Menu2
Menu2 = class()
function Menu2:update()
end
function Menu2:init(args)
-- actions
self.callback = args.callback or doNothing
-- layout
self.title = args.title or "menu"
-- the
local list = args.list
self.list,self.enabled,self.selected,self.group = {},{},{},{}
self.groups = {}
local i,key
local imax = #list/3
for i=1,imax do
self.list[i] = list[(i-1)*3+1]
self.enabled[i] = list[(i-1)*3+2]
self.selected[i] = false
self.group[i] = list[(i-1)*3+3]
end
self.side = args.side or "bottom"
-- settings by pos and w
local x = args.pos
local w = args.width or 0.09
-- or by x0 and x1
if args.x0 and args.x1 then
x = (args.x0 + args.x1)/2
w = args.x1 - args.x0
end
local w1,w2,h1,h2
local pos
local dx0,dy0,dx,dy
local htxt = 30
local w0,h0 = math.floor( w * WIDTH ), math.floor( w * HEIGHT )
if self.side == "top" then
w1,w2,h1,h2 = w0,w0,htxt,htxt
pos = vec2(x*WIDTH, HEIGHT-h1/2)
dx0,dy0,dx,dy = 0, -h1-5, 0, -h2-2
elseif self.side == "bottom" then
w1,w2,h1,h2 = w0,w0,htxt,htxt
pos = vec2(x*WIDTH, h1/2)
dx0,dy0,dx,dy = 0,h1+5, 0, h2+2
elseif self.side == "left" then
w1,w2,h1,h2 = htxt,w0,h0,htxt
pos = vec2(w1/2, x*HEIGHT)
dx0,dy0,dx,dy = w1/2+5+w2/2, h1/2-h2/2, 0, -h2-2
elseif self.side == "right" then
w1,w2,h1,h2 = htxt,w0,h0,htxt
pos = vec2(WIDTH-w1/2, x*HEIGHT)
dx0,dy0,dx,dy = -w1/2-5-w2/2, h1/2-h2/2, 0, -h2-2
end
-- create text images
self.img = {}
for i,txt in ipairs(self.list) do
self.img[i] = self:calcImg(txt,w2,h2)
end
self.img["title"] = self:calcImg(self.title,w1,h1,self.side)
-- position of images
local x,y = pos.x,pos.y
self.pos = {}
self.radius = {}
self.pos["title"] = pos
self.radius["title"] = vec2(w1/2,h1/2)
for i,txt in ipairs(self.list) do
if i==1 then x,y = x+dx0,y+dy0
else x,y = x+dx,y+dy end
self.pos[i] = vec2(x,y)
self.radius[i] = vec2(w2/2,h2/2)
end
-- manage various drawing states
self.deployed = false
self.highlighted = nil -- the choice highlighted
end
function Menu2:select(key)
local i,v
for i,v in ipairs(self.list) do
if self.list[i]==key then self.selected[i]=true end
end
end
function Menu2:groupEnable(key)
local i,v
for i,v in ipairs(self.group) do
if self.group[i]==key then self.enabled[i]=true end
end
end
function Menu2:groupDisable(key)
local i,v
for i,v in ipairs(self.group) do
if self.group[i]==key then self.enabled[i]=false end
end
end
function Menu2:resetDemo()
-- do nothing
end
function Menu2:calcImg(txt,w,h,rot)
local w0,h0
pushStyle() pushMatrix()
if rot=="left" or rot=="right"
then w0,h0 = h,w
else w0,h0 = w,h
end
local img0 = image(w0,h0)
setContext(img0)
font("AmericanTypewriter-Bold")
rectMode(CENTER)
textMode(CENTER)
strokeWidth(1)
-- background(255, 255, 255, 184)
background(255, 255, 255, 255)
fill(0, 0, 0, 255)
stroke(0, 0, 0, 255)
fontSize(20)
text(txt,w0/2,h0/2)
setContext()
local img = image(w,h)
setContext(img)
-- background(255, 255, 255, 184)
background(255, 255, 255, 255)
spriteMode(CENTER)
translate(w/2,h/2)
if rot=="left" then rotate(-90) end
if rot=="right" then rotate(90) end
sprite(img0,0,0)
setContext()
popStyle() popMatrix()
return img
end
function Menu2:draw()
pushStyle()
spriteMode(CENTER)
-- menu title
if self.deployed then
tint(196, 196, 196, 255)
else tint(211, 211, 211, 127)
end
local pos = self.pos
-- menu button
sprite(self.img["title"], pos["title"].x, pos["title"].y)
-- deployed
if self.deployed then
local isel = self.choice
for i,txt in ipairs(self.list) do
pushStyle()
if self.enabled[i] == false then tint(72, 72, 72, 255)
elseif self.selected[i] == true or i==isel then tint(255,255,255,255)
elseif self.enabled[i] == true then tint(127, 127, 127, 255)
end
sprite( self.img[i], pos[i].x, pos[i].y)
popStyle()
end
end
popStyle()
end
function Menu2:titleTouched(touch)
local pos,radius= self.pos["title"],self.radius["title"]
local goodZone = false
if math.abs((touch.x-pos.x))<radius.x
and math.abs((touch.y-pos.y))<radius.y
then
goodZone = true
end
self.choiceDone = false
return goodZone
end
function Menu2:selectTouched(touch)
local pos,radius
for i,v in ipairs(self.pos) do
pos,radius = self.pos[i],self.radius[i]
if math.abs((touch.x-pos.x))<radius.x
and math.abs((touch.y-pos.y))<radius.y
then
self.choice = i
self.choiceDone=true
end
end
end
function Menu2:touched(t)
local group
if self:titleTouched(t) and t.state == BEGAN then
self.deployed=true
end
if self.deployed then
self:selectTouched(t)
end
if t.state == ENDED and self.deployed then
self.deployed=false
if self.choiceDone and self.enabled[self.choice] then
group = self.group[self.choice]
for i,_ in ipairs(self.group) do
if self.group[i] == group then self.selected[i] = false end
end
self.selected[self.choice] = true
self.callback( self.list[self.choice] )
end
self.choice = nil
end
end
--# Menu3
Menu3 = class()
function Menu3:update()
end
function Menu3:init(args)
-- actions
self.callback = args.callback or doNothing
-- layout
self.title = args.title or "menu"
self.hFactor = 0.1
-- the
local list = args.list
self.list,self.enabled,self.selected,self.group = {},{},{},{}
self.groups = {}
local i,key
local imax = #list/3
for i=1,imax do
self.list[i] = list[(i-1)*3+1]
self.enabled[i] = list[(i-1)*3+2]
self.selected[i] = false
self.group[i] = list[(i-1)*3+3]
end
self.side = args.side or "bottom"
-- settings by pos and w
local x = args.pos
local w = args.width or 0.09
-- or by x0 and x1
if args.x0 and args.x1 then
x = (args.x0 + args.x1)/2
w = args.x1 - args.x0
end
local w1,w2,h1,h2
local pos
local dx0,dy0,dx,dy
local htxt = 30
local w0,h0 = math.floor( w * WIDTH ), math.floor( w * HEIGHT )
if self.side == "top" then
w1,w2,h1,h2 = w0,w0,htxt,htxt
pos = vec2(x*WIDTH, HEIGHT-h1/2)
dx0,dy0,dx,dy = 0, -h1-5, 0, -h2-2
elseif self.side == "bottom" then
w1,w2,h1,h2 = w0,w0,htxt,htxt
pos = vec2(x*WIDTH, h1/2)
dx0,dy0,dx,dy = 0,h1+5, 0, h2+2
elseif self.side == "left" then
w1,w2,h1,h2 = htxt,w0,h0,htxt
pos = vec2(w1/2, x*HEIGHT)
dx0,dy0,dx,dy = w1/2+5+w2/2, h1/2-h2/2, 0, -h2-2
elseif self.side == "right" then
w1,w2,h1,h2 = htxt,w0,h0,htxt
pos = vec2(WIDTH-w1/2, x*HEIGHT)
dx0,dy0,dx,dy = -w1/2-5-w2/2, h1/2-h2/2, 0, -h2-2
end
-- create text images
self.img = {}
for i,txt in ipairs(self.list) do
self.img[i] = self:calcImg(txt,w2,h2)
end
self.img["title"] = self:calcImg(self.title,w1,h1,self.side)
-- position of images
local x,y = pos.x,pos.y
self.pos = {}
self.radius = {}
self.pos["title"] = pos
self.radius["title"] = vec2(w1/2,h1/2)
for i,txt in ipairs(self.list) do
if i==1 then x,y = x+dx0,y+dy0
else x,y = x+dx,y+dy end
self.pos[i] = vec2(x,y)
self.radius[i] = vec2(w2/2,h2/2)
end
-- manage various drawing states
self.deployed = false
self.highlighted = nil -- the choice highlighted
end
function Menu3:select(key)
local i,v
for i,v in ipairs(self.list) do
if self.list[i]==key then self.selected[i]=true end
end
end
function Menu3:groupEnable(key)
local i,v
for i,v in ipairs(self.group) do
if self.group[i]==key then self.enabled[i]=true end
end
end
function Menu3:groupDisable(key)
local i,v
for i,v in ipairs(self.group) do
if self.group[i]==key then self.enabled[i]=false end
end
end
function Menu3:resetDemo()
-- do nothing
end
function Menu3:calcImg(txt,w,h,rot)
local w0,h0
pushStyle() pushMatrix()
if rot=="left" or rot=="right"
then w0,h0 = h,w
else w0,h0 = w,h
end
local img0 = image(w0,h0)
setContext(img0)
font("AmericanTypewriter-Bold")
rectMode(CENTER)
textMode(CENTER)
strokeWidth(1)
-- background(255, 255, 255, 184)
background(255, 255, 255, 255)
fill(0, 0, 0, 255)
stroke(0, 0, 0, 255)
fontSize(20)
text(txt,w0/2,h0/2)
setContext()
local img = image(w,h)
setContext(img)
-- background(255, 255, 255, 184)
background(255, 255, 255, 255)
spriteMode(CENTER)
translate(w/2,h/2)
if rot=="left" then rotate(-90) end
if rot=="right" then rotate(90) end
sprite(img0,0,0)
setContext()
popStyle() popMatrix()
return img
end
function Menu3:draw()
pushStyle()
spriteMode(CENTER)
-- menu title
if self.deployed then
tint(196, 196, 196, 255)
else tint(211, 211, 211, 127)
end
local pos = self.pos
-- menu button
sprite(self.img["title"], pos["title"].x, pos["title"].y)
-- deployed
if self.deployed then
local isel = self.choice
local y0=pos["title"].y
for i,txt in ipairs(self.list) do
pushStyle()
if self.enabled[i] == false then tint(72, 72, 72, 255)
elseif self.selected[i] == true or i==isel then tint(255,255,255,255)
elseif self.enabled[i] == true then tint(127, 127, 127, 255)
end
sprite( self.img[i], pos[i].x, pos[i].y* self.hFactor+y0*(1-self.hFactor))
popStyle()
end
end
popStyle()
end
function Menu3:titleTouched(touch)
local pos,radius= self.pos["title"],self.radius["title"]
local goodZone = false
if math.abs((touch.x-pos.x))<radius.x
and math.abs((touch.y-pos.y))<radius.y
then
goodZone = true
end
self.choiceDone = false
return goodZone
end
function Menu3:selectTouched(touch)
local pos,radius
for i,v in ipairs(self.pos) do
pos,radius = self.pos[i],self.radius[i]
if math.abs((touch.x-pos.x))<radius.x
and math.abs((touch.y-pos.y))<radius.y
then
self.choice = i
self.choiceDone=true
end
end
end
function Menu3:touched(t)
local group
if self:titleTouched(t) and t.state == BEGAN then
self.deployed=true
tween( 1.0, self, { hFactor = 1 }, "outElastic")
end
if self.deployed then
self:selectTouched(t)
end
if t.state == ENDED and self.deployed then
self.deployed=false
self.hFactor = 0.1
if self.choiceDone and self.enabled[self.choice] then
group = self.group[self.choice]
for i,_ in ipairs(self.group) do
if self.group[i] == group then self.selected[i] = false end
end
self.selected[self.choice] = true
self.callback( self.list[self.choice] )
end
self.choice = nil
end
end
--# Controller
-- Base class for controllers
--
-- Controllers translate touch events into callbacks to functions
-- that do something in the app. (Model/View/Controller style).
--
-- Controllers can draw a representation of their current state on
-- the screen, but you can choose not to.
--
-- A controller can be installed as the global handler for touch
-- events by calling its activate() method
Controller = class()
function Controller:activate(input)
self.x0 = input.x0 or 0
self.x1 = input.x1 or 1
self.y0 = input.y0 or 0
self.y1 = input.y1 or 1
self.cx = (self.x0+self.x1)/2 *WIDTH
self.cy = (self.y0+self.y1)/2 *HEIGHT
self.wx = (self.x1- self.x0)/2 *WIDTH
self.wy = (self.y1- self.y0)/2 *HEIGHT
self.name = input.name
self.nameSide = input.nameSide
if self.nameSide then
self:textPanel()
end
self.timeout = input.timeout or 2
self.t0 = ElapsedTime
end
function Controller:resetDemo()
self.t0 = ElapsedTime
end
function Controller:textPanel()
local w,h = 20,20
local side = self.nameSide
if side=="top" or side=="bottom" then w = self.wx*2
elseif side=="left" or side=="right" then w = self.wy*2
else print("the text for nameSide is not recognized")
end
local img = image(w,h)
setContext(img)
pushStyle() pushMatrix()
strokeWidth(1)
rectMode(RADIUS)
background(127, 127, 127, 57)
fill(0, 0, 0, 255)
fontSize(20)
text(self.name,w/2,h/2)
popStyle() popMatrix()
setContext()
local imgActive = image(w,h)
setContext(imgActive)
pushStyle() pushMatrix()
strokeWidth(1)
rectMode(RADIUS)
background(255, 255, 255, 184)
fill(0, 0, 0, 255)
fontSize(20)
text(self.name,w/2,h/2)
popStyle() popMatrix()
setContext()
local side = self.nameSide
local x,y,r
if side=="top" then
x,y,r = self.cx , self.y1*HEIGHT - img.height/2 , 0
elseif side=="bottom" then
x,y,r = self.cx , self.y0*HEIGHT + img.height/2 , 0
elseif side=="left" then
x,y,r = self.x0*WIDTH + img.height/2 , self.cy , -90
elseif side=="right" then
x,y,r = self.x1*WIDTH - img.height/2 , self.cy , 90
end
self.tx, self.ty, self.tr = x,y,r
self.imgPassive = img
self.imgActive = imgActive
end
function Controller:demo(timeout)
self.timeout = timeout
if (ElapsedTime - self.t0)<timeout then
pushStyle() pushMatrix()
strokeWidth(1)
rectMode(RADIUS)
fill(255, 255, 255, 46)
rect(self.cx,self.cy,self.wx,self.wy)
fill(255, 255, 255, 255)
fontSize(20)
text("touch here to",self.cx,self.cy+20)
text(self.name,self.cx,self.cy - 20)
popStyle() popMatrix()
end
end
function Controller:checkTitle(touch)
-- show region if title touch
local img
img = self.imgActive
local tw,th = img.width/2, img.height/2
if self.tr ~=0 then tw,th = th,tw end
if math.abs((touch.x- self.tx))<tw
and math.abs((touch.y- self.ty))<th
then self.t0=ElapsedTime self:demo(1) end
end
function Controller:check(touch)
local goodZone = false
if math.abs((touch.x-self.cx))<self.wx
and math.abs((touch.y-self.cy))<self.wy
then
goodZone = true
-- show region if title touch
self:checkTitle(touch)
end
return goodZone
end
function Controller:drawName()
local img
if self.touchId
then img = self.imgActive
else img = self.imgPassive
end
local x,y,r = self.tx, self.ty, self.tr
pushMatrix()
translate(x,y)
rotate(r)
spriteMode(CENTER)
sprite(img ,0,0)
popMatrix()
end
-- Utility functions
function touchPos(t)
return vec2(t.x, t.y)
end
function clamp(x, min, max)
return math.max(min, math.min(max, x))
end
function clampAbs(x, maxAbs)
return clamp(x, -maxAbs, maxAbs)
end
function clampLen(vec, maxLen)
if vec == vec2(0,0) then
return vec
else
return vec:normalize() * math.min(vec:len(), maxLen)
end
end
-- projects v onto the direction represented by the given unit vector
function project(v, unit)
return v:dot(unit)
end
function sign(x)
if x == 0 then
return 0
elseif x < 0 then
return -1
elseif x > 0 then
return 1
else
return x -- x is NaN
end
end
function doNothing()
end
--# Contoller_TwoTouch
TwoTouch = class(Controller)
function TwoTouch:init(args)
self.touches = {}
self.touchStart = {}
self.releasedCallback1 = args.released1 or doNothing
self.steerCallback1 = args.moved1 or doNothing
self.pressedCallback1 = args.pressed1 or doNothing
self.releasedCallback2 = args.released2 or doNothing -- never fires..??
self.zoomCallback2 = args.moved2 or doNothing
self.pressedCallback2 = args.pressed2 or doNothing
self.activated = args.activate or true
-- pre-draw sprites
self.stick = self:createStick()
if self.activated then self:activate(args) end
end
function TwoTouch:touchCount()
local n = 0
for i,v in pairs(self.touches) do n = n + 1 end
return n
end
function TwoTouch:touched(touch)
local goodZone = self:check(touch)
local Nbefore,Nafter
Nbefore = self:touchCount()
if touch.state == BEGAN then
if goodZone and Nbefore<2 then
self.touches[touch.id] = touch -- new touch
self.touchStart[touch.id] = touch
else end -- that touch doesnt concern us
elseif self.touches[touch.id] then -- if this one of my touches
self.touches[touch.id] = touch -- update touch data
end
Nafter = self:touchCount() -- what is the new touch count?
-- if exactly one touch then move
if Nafter==1 and Nbefore<=1 then
--info:changeTxt("1 touch")
self:move(touch)
end
-- if exactly 2 touches then zoom
if Nafter==2 or Nbefore==2 then
-- info:changeTxt("2 touch")
self:zoom()
end
if touch.state == ENDED or touch.state == CANCELLED then
self:reset(touch)
end
end
function TwoTouch:draw()
if self.nameSide then self:drawName() end
if self.name then self:demo(self.timeout) end
for i,t in pairs(self.touches) do
if t then sprite(self.stick, t.x, t.y) end
end
end
function TwoTouch:reset(t)
self.touches[t.id] = nil
self.touchStart[t.id] = nil
end
function TwoTouch:move(t)
if t.state == BEGAN then
self.pressedCallback1(touchPos(t))
elseif t.state == MOVING then
self.steerCallback1(touchPos(t))
elseif t.state == ENDED or t.state == CANCELLED then
self.releasedCallback1()
end
end
function TwoTouch:zoom()
local n = 1
local t = {}
for i,touch in pairs(self.touches) do
t[n] = touch
n = n + 1
end
local v = touchPos(t[1]) - touchPos(t[2])
if t[1].state == BEGAN or t[2].state == BEGAN then
self.pressedCallback2( v )
elseif t[1].state == MOVING or t[2].state == MOVING then
self.zoomCallback2( v )
elseif
t[1].state == ENDED or t[2].state == ENDED or
t[1].state == CANCELLED or t[2].state == CANCELLED
then
self.releasedCallback2()
if not(t[1].state == ENDED or t[1].state == CANCELLED) then
self.pressedCallback1(touchPos(t[2]))
else
self.pressedCallback1(touchPos(t[1]))
end
end
end
function TwoTouch:createStick()
local base = image(56,56)
pushStyle() pushMatrix()
ellipseMode(RADIUS)
strokeWidth(1)
stroke(255, 255, 255, 255)
noFill()
setContext(base)
background(0, 0, 0, 0)
ellipse(base.width/2, base.height/2, 25, 25)
setContext()
popMatrix() popStyle()
return base
end
--# Controller_oneTouch
OneTouch = class(Controller)
function OneTouch:init(args)
self.releasedCallback = args.released or doNothing
self.steerCallback = args.moved or doNothing
self.pressedCallback = args.pressed or doNothing
self.activated = args.activate or true
-- pre-draw sprites
self.stick = self:createStick()
if self.activated then self:activate(args) end
end
function OneTouch:draw()
if self.nameSide then self:drawName() end
if self.name then self:demo(self.timeout) end
if self.touchId then
sprite(self.stick, self.pos.x, self.pos.y)
end
end
function OneTouch:reset()
self.touchId = nil
self.touchStart = nil
end
function OneTouch:touched(t)
local pos = touchPos(t)
local goodZone = self:check(t)
if t.state == BEGAN and self.touchId == nil and goodZone then
self.touchId = t.id
self.touchStart = pos
self.pos = pos
self.pressedCallback(pos)
elseif t.id == self.touchId then
if t.state == MOVING then
self.pos = pos
self.steerCallback(pos)
elseif t.state == ENDED or t.state == CANCELLED then
self:reset()
self.releasedCallback(pos)
end
end
end
function OneTouch:createStick()
local base = image(56,56)
pushStyle() pushMatrix()
ellipseMode(RADIUS)
strokeWidth(1)
stroke(255, 255, 255, 255)
noFill()
setContext(base)
background(0, 0, 0, 0)
ellipse(base.width/2, base.height/2, 25, 25)
setContext()
popMatrix() popStyle()
return base
end
--# Controller_VirtualStick
-- A virtual analogue joystick with a dead-zone at the center,
-- which activates wherever the user touches their finger
--
-- Arguments:
-- radius - radius of the stick (default = 100)
-- deadZoneRadius - radius of the stick's dead zone (default = 25)
-- moved(v) - Called when the stick is moved
-- v : vec2 - in the range vec2(-1,-1) and vec2(1,1)
-- pressed() - Called when the user starts using the stick (optional)
-- released() - Called when the user releases the stick (optional)
VirtualStick = class(Controller)
function VirtualStick:init(args)
self.radius = args.radius or 100
self.deadZoneRadius = args.deadZoneRadius or 25
self.releasedCallback = args.released or doNothing
self.steerCallback = args.moved or doNothing
self.pressedCallback = args.pressed or doNothing
self.activated = args.activate or true
-- pre-draw sprites
self.base = self:createBase()
self.stick = self:createStick()
if self.activated then self:activate(args) end
end
function VirtualStick:createBase()
local base = image(self.radius*2+6,self.radius*2+6)
pushStyle() pushMatrix()
ellipseMode(RADIUS)
strokeWidth(1)
stroke(255, 255, 255, 255)
noFill()
setContext(base)
background(0, 0, 0, 0)
ellipse(base.width/2, base.height/2, self.radius, self.radius)
ellipse(base.width/2, base.height/2, self.deadZoneRadius, self.deadZoneRadius)
setContext()
popMatrix() popStyle()
return base
end
function VirtualStick:createStick()
local base = image(56,56)
pushStyle() pushMatrix()
ellipseMode(RADIUS)
strokeWidth(1)
stroke(255, 255, 255, 255)
noFill()
setContext(base)
background(0, 0, 0, 0)
ellipse(base.width/2, base.height/2, 25, 25)
setContext()
popMatrix() popStyle()
return base
end
function VirtualStick:touched(t)
local pos = touchPos(t)
local goodZone = self:check(t)
if t.state == BEGAN and self.touchId == nil and goodZone then
self.touchId = t.id
self.touchStart = pos
self.stickOffset = vec2(0, 0)
self.pressedCallback()
elseif t.id == self.touchId then
if t.state == MOVING then
self.stickOffset = clampLen(pos - self.touchStart, self.radius)
self.steerCallback(self:vector())
elseif t.state == ENDED or t.state == CANCELLED then
self:reset()
self.releasedCallback()
end
end
end
function VirtualStick:vector()
local stickRange = self.radius - self.deadZoneRadius
local stickAmount = math.max(self.stickOffset:len() - self.deadZoneRadius, 0)
local stickDirection = self.stickOffset
if stickDirection:len()>0 then stickDirection = self.stickOffset:normalize() end
return stickDirection * (stickAmount/stickRange)
end
function VirtualStick:reset()
self.touchId = nil
self.touchStart = nil
self.stickOffset = nil
end
function VirtualStick:draw()
if self.nameSide then self:drawName() end
if self.name then self:demo(self.timeout) end
if self.touchId then
sprite(self.base,self.touchStart.x, self.touchStart.y)
sprite(self.stick,
self.touchStart.x+self.stickOffset.x,
self.touchStart.y+self.stickOffset.y)
end
end
--# Controller_VirtualSlider
-- A virtual analogue slider with a dead-zone at the center,
-- which activates wherever the user touches their finger
--
-- Arguments:
-- orientation - A unit vector that defines the orientation of the slider.
-- For example orientation=vec2(1,0) creates a horizontal slider,
-- orientation=vec2(0,1) creates a vertical slider. The slider
-- can be given an arbitrary orientation; it does not have to be
-- aligned with the x or y axis. For example, setting
-- orientation=vec2(1,1):normalize() creates a diagonal slider.
-- radius - Distance from the center to the end of the slider (default = 100)
-- deadZoneRadius - Distance from the center to the end of the dead zone (default = 25)
-- moved(x) - Called when the slider is moved
-- x : float - in the range -1 to 1
-- pressed() - Called when the user starts using the slider (optional)
-- released() - Called when the user releases the slider (optional)
VirtualSlider = class(Controller)
function VirtualSlider:init(args)
self.orientation = args.orientation or vec2(1,0)
self.radius = args.radius or 100
self.deadZoneRadius = args.deadZoneRadius or 25
self.releasedCallback = args.released or doNothing
self.movedCallback = args.moved or doNothing
self.pressedCallback = args.pressed or doNothing
self.activated = args.activate or true
self.base = self:createBase()
self.stick = self:createStick()
if self.activated then self:activate(args) end
end
function VirtualSlider:touched(t)
local pos = touchPos(t)
local goodZone = self:check(t)
if t.state == BEGAN and self.touchId == nil and goodZone then
self.touchId = t.id
self.touchStart = pos
self.sliderOffset = 0
self.pressedCallback()
elseif t.id == self.touchId then
if t.state == MOVING then
local v = pos - self.touchStart
self.sliderOffset = clampAbs(project(v, self.orientation), self.radius)
self.movedCallback(self:value())
elseif t.state == ENDED or t.state == CANCELLED then
self:reset()
self.releasedCallback()
end
end
end
function VirtualSlider:reset()
self.touchId = nil
self.touchStart = nil
self.sliderOffset = nil
end
function VirtualSlider:value()
local range = self.radius - self.deadZoneRadius
local amount = sign(self.sliderOffset) * math.max(math.abs(self.sliderOffset) - self.deadZoneRadius, 0)
return amount/range
end
function VirtualSlider:createBase()
local img = image(self.radius*2+6,self.radius*2+6)
setContext(img)
pushStyle()
ellipseMode(RADIUS)
strokeWidth(3)
stroke(255, 255, 255, 255)
lineCapMode(SQUARE)
noFill()
background(0, 0, 0, 0)
local function polarLine(orientation, fromRadius, toRadius)
local from = orientation * fromRadius + vec2(1,1)*(self.radius + 3)
local to = orientation * toRadius + vec2(1,1)*(self.radius + 3)
line(from.x, from.y, to.x, to.y)
end
polarLine(self.orientation, self.deadZoneRadius, self.radius)
polarLine(self.orientation, -self.deadZoneRadius, -self.radius)
popStyle()
setContext()
return img
end
function VirtualSlider:createStick()
local img = image(56,56)
setContext(img)
pushStyle() pushMatrix()
ellipseMode(RADIUS)
strokeWidth(3)
stroke(255, 255, 255, 255)
lineCapMode(SQUARE)
noFill()
background(0, 0, 0, 0)
strokeWidth(1)
ellipse(28, 28, 25, 25)
popMatrix() popStyle()
setContext()
return img
end
function VirtualSlider:draw()
if self.nameSide then self:drawName() end
if self.name then self:demo(self.timeout) end
if self.touchId then
pushMatrix() pushStyle()
spriteMode(CENTER)
sprite(self.base,self.touchStart.x, self.touchStart.y)
local sliderPos = self.orientation * self.sliderOffset + self.touchStart
strokeWidth(1)
sprite(self.stick, sliderPos.x, sliderPos.y)
popStyle() popMatrix()
end
end
--# Controller_All
All = class(Controller)
-- Forwards each touch event to all the controllers in the table
-- passed to the constructor
function All:init(controllers)
self.controllers = controllers
end
function All:touched(t)
for _, c in pairs(self.controllers) do
c:touched(t)
end
end
function All:resetDemo()
for _, c in pairs(self.controllers) do
c:resetDemo()
end
end
function All:draw()
for _, c in pairs(self.controllers) do
c:draw()
end
end
--# FPS
FPS = class()
function FPS:init()
self.val = 60
end
function FPS:draw()
-- update FPS value with some smoothing
self.val = self.val*0.99+ 1/(DeltaTime)*0.01
-- write the FPS on the screen
fill(208, 208, 208, 255)
fontSize(30)
font("AmericanTypewriter-Bold")
rectMode(CENTER)
text(math.floor(self.val).." fps",50,HEIGHT-25)
end
--# Main
-- project "sphere" JMV38
displayMode(FULLSCREEN)
function setup()
-- a global object to manage infos
info = InfoBloc({side="right", x0=0.9 , x1=1 ,
textPos=vec2(WIDTH/2, HEIGHT-125), textWidth =800} )
-- object creation
axis = Axis(25)
-- this is to see the codea controls
codeaPanelSetup()
-- controls and menus
menus = Menus()
controls = ObjectControl2()
-- start with an embeded object
cube = Cube(100,0,0,0)
obj = cube.ms
-- load saved object
tool = Convert()
--obj = tool:loadMesh("test")
local txt = "Start by loading some objects i've put on the web for you\n"
txt = txt.."Then save save them locally on your ipad.\n"
txt = txt.."Then create, save and load your own meshes."
info:changeTxt(txt)
--sprite()
end
function codeaPanelSetup()
-- global variable
codeaPanel = image(265,32)
setContext(codeaPanel)
background(127, 127, 127, 42)
setContext()
end
function codeaPanelDraw()
spriteMode(CORNER) -- a white background below controls
sprite(codeaPanel, 5,5)
end
function draw()
pushMatrix()
perspective(45)
camera(0,0,400, 0,0,0, 0,1,0)
background(14, 14, 14, 255) -- clean background
controls:move()
obj:draw()
axis:draw()
popMatrix()
ortho() -- Restore orthographic projection
viewMatrix(matrix()) -- Restore the view matrix to the identity
controls:draw()
menus.all:draw()
codeaPanelDraw()
end
function touched(touch)
controls:touched(touch)
menus.all:touched(touch)
end
--# Cube
Cube = class()
function Cube:init(size,x0,y0,z0)
-- position of the object
self.m = modelMatrix()
self.x0,self.y0,self.z0 = x0,y0,z0
self.m[13],self.m[14],self.m[15] = self.x0,self.y0,self.z0
-- object design
self.size = size
local R = color(148, 24, 25, 255)
local V = color(35, 140, 28, 255)
local B = color(15, 109, 204, 255)
local r = size/2
self.ms = mesh()
self.ms.vertices={
vec3(-r,-r,r),vec3(-r,r,r),vec3(r,-r,r),
vec3(r,r,r),vec3(-r,r,r),vec3(r,-r,r),
vec3(-r,-r,-r),vec3(-r,r,-r),vec3(r,-r,-r),
vec3(r,r,-r),vec3(-r,r,-r),vec3(r,-r,-r),
vec3(-r,-r,r),vec3(-r,r,r),vec3(-r,r,-r),
vec3(-r,-r,r),vec3(-r,-r,-r),vec3(-r,r,-r),
vec3(r,-r,r),vec3(r,r,r),vec3(r,r,-r),
vec3(r,-r,r),vec3(r,-r,-r),vec3(r,r,-r),
vec3(r,r,r),vec3(-r,r,r),vec3(r,r,-r),
vec3(-r,r,-r),vec3(-r,r,r),vec3(r,r,-r),
vec3(r,-r,r),vec3(-r,-r,r),vec3(r,-r,-r),
vec3(-r,-r,-r),vec3(-r,-r,r),vec3(r,-r,-r),
}
self.ms.colors = {
R,R,R,R,R,R,R,R,R,R,R,R,
V,V,V,V,V,V,V,V,V,V,V,V,
B,B,B,B,B,B,B,B,B,B,B,B,
}
end
function Cube:draw()
pushMatrix()
local m = self.m * modelMatrix()
modelMatrix(m)
self.ms:draw()
popMatrix()
end
--# Axis
Axis = class()
function Axis:init(size)
-- position of the object
self.m = modelMatrix()
self.x0,self.y0,self.z0 = -190,-100,0
-- object design
self.size = size
-- triangles of the x,y,z axis
local X,Y,Z,O = vec3(1,0,0) * size ,
vec3(0,1,0) * size , vec3(0,0,1) * size , vec3(0,0,0)
self.X, self.Y, self.Z, self.O = X,Y,Z,O
local vertices = {
O,X,Y,
O,X,Z,
O,Y,Z
}
local R = color(148, 24, 25, 255)
local V = color(35, 140, 28, 255)
local B = color(15, 109, 204, 255)
local colors = {
R,R,R,
V,V,V,
B,B,B
}
self.ms = mesh()
self.ms.vertices = vertices
self.ms.colors = colors
-- labels
pushStyle() pushMatrix()
local w = math.floor(size/5)
self.w = w
if w<10 then w=10 end
if w>30 then w=30 end
local c=w/2
local s = w-2
self.xlabel = image(w,w)
setContext(self.xlabel)
background(127, 127, 127, 255)
fill(208, 208, 208, 255)
fontSize(s)
font("AmericanTypewriter-Bold")
textMode(CENTER)
text("X",c,c)
setContext()
self.ylabel = image(w,w)
setContext(self.ylabel)
background(127, 127, 127, 255)
fill(208, 208, 208, 255)
fontSize(s)
font("AmericanTypewriter-Bold")
textMode(CENTER)
text("Y",c,c)
setContext()
self.zlabel = image(w,w)
setContext(self.zlabel)
background(127, 127, 127, 255)
fill(208, 208, 208, 255)
fontSize(s)
font("AmericanTypewriter-Bold")
textMode(CENTER)
text("Z",c,c)
setContext()
self.olabel = image(w,w)
setContext(self.olabel)
background(127, 127, 127, 255)
fill(208, 208, 208, 255)
fontSize(s)
font("AmericanTypewriter-Bold")
textMode(CENTER)
text("O",c,c)
setContext()
popMatrix() popStyle()
end
function Axis:draw()
local c = self.w/2
local m
m = modelMatrix()
pushMatrix()
resetMatrix()
--translate(-150,0,20)
m[13],m[14],m[15] = self.x0,self.y0,self.z0
modelMatrix(m)
pushStyle()
spriteMode(CENTER)
pushMatrix()
translate(self.X.x + c, self.X.y, self.X.z -0.05)
sprite(self.xlabel,0,0)
popMatrix() pushMatrix()
translate(self.Y.x, self.Y.y + c, self.Y.z -0.05)
sprite(self.ylabel)
popMatrix() pushMatrix()
translate(self.Z.x, self.Z.y, self.Z.z +0.05)
sprite(self.zlabel)
popMatrix() pushMatrix()
translate(self.O.x - c, self.O.y - c, self.O.z -0.05)
sprite(self.olabel)
popMatrix()
self.ms:draw()
popStyle()
popMatrix()
end
--# Menus
Menus = class()
function Menus:init()
-- save
local menu1 = Menu({side="right", x0=0.70, x1=0.79, title="save",
callback = self.selectSave,
list={ "Slots:",false,"slot1",true,"slot2",true, "slot3",true,"slot4", true,
"slot5", true, "slot6", true,
}, selected=0 })
-- load from disk
local menu2 = Menu({side="right", x0=0.60, x1=0.69, title="disk",
callback = self.diskLoad,
list={ "Slots:",false,"slot1",true,"slot2",true, "slot3",true,"slot4", true,
"slot5", true,"slot6",true}, selected=0 })
-- load from web
local menu3 = Menu({side="right", x0=0.50, x1=0.59, title="web",
callback = self.webLoad,
list={ "links:",false,"meca",true, "cube1",true, "cube2",true,
"cylin",true, "sphere",true, "earth",true,
}, selected=0 })
self.all=All({info,menu1,menu2,menu3})
end
function Menus.selectSave(v)
local txt = nil
local size = tool:saveMesh(obj,v)
txt = "the mesh has been saved in your CODEA 'Documents:' folder,\n"
txt = txt.."under the name '"..v.."', and its size is "
txt = txt..math.floor(size*100).."% of max size."
info:changeTxt(txt)
end
function Menus.diskLoad(v)
local txt = nil
local name = "Documents:"..v
local img = readImage(name)
if img==nil then
txt = "the image '"..v.."' from your CODEA 'Documents:' folder does not exist\n"
txt = txt.."so the current object has NOT been changed. "
else
obj = tool:loadMesh(v)
txt = "the image "..v.." from your CODEA document folder,\n"
txt = txt.."has been read and loaded in the current mesh: obj "
end
info:changeTxt(txt)
end
function Menus.webLoad(name)
-- all the urls here
local url = nil
if name == "meca"
then url = "http://www.jmv38.net23.net/mesh/mechanics.png"
elseif name == "cube1"
then url = "http://www.jmv38.net23.net/mesh/cube1.png"
elseif name == "cube2"
then url = "http://www.jmv38.net23.net/mesh/cube2.png"
elseif name == "cylin"
then url = "http://www.jmv38.net23.net/mesh/cylinder.png"
elseif name == "sphere"
then url = "http://www.jmv38.net23.net/mesh/sphere.png"
elseif name == "earth"
then url = "http://www.jmv38.net23.net/mesh/earth.png"
end
if url then
http.request(url,
function(img, status, head)
saveImage("Documents:webImage",img)
obj = tool:loadMesh("webImage")
end )
end
end
function Menus:draw()
self.all:draw()
end
function Menus:touched(touch)
self.all:touched(touch)
end
--# WebImageLoader
WebImageLoader = class(Job2)
-- CAUTION! the Job2 tab MUST be at the left of this tab!
function WebImageLoader:start(args)
self.txtToIdentifyModifier = "web loading"
local name = args.name or args -- the 2 syntax can be accepted
self.name = name
self.t0 = ElapsedTime -- start count time
self.tmax = 20
local continue = true
if name=="none" then -- this name is for no image
continue = false -- stop immediately
else
local img = hardDiskManager:readImg(name) -- is image on disk?
if img then -- if it is here,
continue = false -- stop immediately
else
continue = self:loadWebImage(name) -- start loading
end
end
return continue
end
function WebImageLoader:myFunction()
local frac = (ElapsedTime - self.t0)/self.tmax
local continue = true
-- the image is already there
if hardDiskManager:readImg(self.name) or frac>1 then
continue = false
end
return continue,frac
end
function WebImageLoader:print(...)
-- activate/desactivate printing
-- print(...)
end
function WebImageLoader:loadWebImage(name)
local continue
local url = self:urlWebImage(name)
-- load and store in table
if url then
continue = true
http.request(url,
function(img, status, head)
if hardDiskManager.localSaveEnabled then
hardDiskManager:saveImg(name,img)
end
end )
else
print("image "..name.." not in my list of urls")
continue = false
end
return continue
end
function WebImageLoader:urlWebImage(name)
-- all the urls here
local url = nil
if name == "meca"
then url = "http://www.jmv38.net23.net/mesh/mechanics.png"
elseif name == "cube1"
then url = "http://www.jmv38.net23.net/mesh/cube1.png"
elseif name == "cube2"
then url = "http://www.jmv38.net23.net/mesh/cube1.png"
end
return url
end
--# ObjectControl2
ObjectControl2 = class()
-- the axis are the view axis
function ObjectControl2:init()
-- la derniere valeur changee
self.lastChange = 0
self.info = nil
self.m = matrix()
-- angles
self.ax = 0
self.ay = 0
self.az = 0
local temp = 0
local angle = 0
local delta = 0
local m , m0
local v0=0
-- keep angle
local function clipAngle(a)
if a > 180 then a = a - 360 end
if a < -180 then a = a + 360 end
return a
end
-- rotate an translate
self.controller1 = TwoTouch({
pressed1 = function(v) self.lastVrot = nil self:selectTarget() end,
moved1 = function(v) self:rotChange(v) end,
released1 = function() self:rotReset() self.lastVrot=nil end,
pressed2 = function(v) self.m0 = self.m ; self.transV0 = v end,
-- pressed2 = function(v) self.m0 = self.target ; self.transV0 = v end,
moved2 = function(v) self:transChange(v) end,
x0=0,x1=0.9,y0=0.1,y1=0.9, timeout=2,
name="rotate and zoom (1 or 2 fingers)", nameSide = "left"})
-- reset
self.controller3 = VirtualSlider({
pressed = function(v) m = self.m self.m = matrix() end,
moved = function(v) if v==0 then self.m=matrix() else self.m=m end end,
orientation = vec2(0,1),deadZoneRadius=10,
x0=0.9,x1=1,y0=0.80,y1=0.89, timeout=2,
name="reset", nameSide = "right"})
self.controls = All({self.controller1,self.controller2,self.controller3})
end
function ObjectControl2:selectTarget()
local target = "control"
if source then
if source.visible
then target = "source"
else target = "control"
end
else target = "control"
end
--print(target)
self.target = target
end
function ObjectControl2:rotReset()
self.rotTime = nil self.rotAxis = nil
end
function ObjectControl2:transReset()
self.speed = nil self.speedAxis = nil
end
function ObjectControl2:rotChange(v)
local angle = 0
local factor = 90
local m0
if self.target == "control" then
m0 = self.m:inverse()
else
m0 = (source.m*self.m):inverse()
end
local X,Y,Z = vec3(m0[1],m0[2],m0[3]),vec3(m0[5],m0[6],m0[7]),vec3(m0[9],m0[10],m0[11])
local v1
if self.lastVrot then
v1 = v - self.lastVrot
local v0 = vec3(v1.x,v1.y,0):rotate(90,0,0,1)
local R = X*v0.x + Y*v0.y + Z*v0.z
local a = v1:len()/4
if R:len()~=0 then
if self.target == "control" then
self.m = self.m:rotate(a,R.x,R.y,R.z)
else
source.m = source.m:rotate(a,R.x,R.y,R.z)
end
end
end
self.lastVrot = v
end
function ObjectControl2:transChange(v)
local shift = 0
local factor = 1
local m0 = self.m0:inverse()
local x,y,z = m0[9],m0[10],m0[11] -- Z view axix, in object coords reference
-- translation along Z axis
shift = (v:len()-self.transV0:len()) * factor
self.m = self.m0:translate(shift*x,shift*y,shift*z)
-- self.target = self.m0:translate(shift*x,shift*y,shift*z)
-- rotation around Z axis
local angle = - v:angleBetween(self.transV0)*180/math.pi
self.m = self.m:rotate(angle,x,y,z)
-- self.target = self.target:rotate(angle,x,y,z)
end
function ObjectControl2:move()
modelMatrix(self.m)
end
function ObjectControl2:draw()
self.controls:draw()
end
function ObjectControl2:resetDemo()
self.controls:resetDemo()
end
function ObjectControl2:touched(touch)
self.controls:touched(touch)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment