Skip to content

Instantly share code, notes, and snippets.

@tnlogy
Created January 6, 2014 02:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tnlogy/8277297 to your computer and use it in GitHub Desktop.
Save tnlogy/8277297 to your computer and use it in GitHub Desktop.
Example of implementing 3D touch. In progress.
--# Main
function setup()
displayMode(FULLSCREEN)
sky = Skybox()
forward, right, up = vec3(0,0,-1), vec3(1,0,0), vec3(0,1,0)
rays = {}
-- creating some random triangles to hit
tris = {}
local r = function (d) return math.random(-d,d) end
for i=1,300 do
local m = mesh()
local r1 = vec3(r(2000),r(2000),r(2000))
local r2 = r1 + vec3(r(100),r(100),r(100))
local r3 = r1 + vec3(r(100),r(100),r(100))
m.vertices = {r1,r2,r3}
m:setColors(color(229, 215, 33, 255))
tris[i] = m
end
end
function draw()
background(0, 0, 0, 255)
perspective(45, WIDTH/HEIGHT, .1, 50000)
-- rotate camera with iPad orientation
local m = matrix()
:rotate(RotationRate.z, forward.x,forward.y,forward.z)
:rotate(-RotationRate.x, right.x,right.y,right.z)
:rotate(-RotationRate.y, up.x,up.y,up.z)
forward, right, up = mult(m,forward),mult(m,right),mult(m,up)
camera(0,0,0, forward.x, forward.y, forward.z, up.x, up.y, up.z)
for i,o in ipairs(rays) do
pushMatrix()
local p = o.dir * o.t
translate(p.x,p.y,p.z)
o.m:draw()
popMatrix()
-- remove mesh when tween is finished
if o.t == 2000 then table.remove(rays, i) end
end
for i,o in ipairs(tris) do
o:draw()
end
rotate(-90,1,0,0)
sky:draw()
end
function touched(touch)
if touch.state == BEGAN then
local o = addRay(touch)
-- check if a triangle is hit
local d,j = false
for i,tri in ipairs(tris) do
local vs = tri.vertices
d = trintersect(vec3(0,0,0), o.dir, vs[1], vs[2], vs[3])
if d then j=i;break end
end
if not d then
tween(.8, o, {t=2000})
else -- if hit move to distance d then remove triangle
tween(.8, o, {t=d}, tween.easing.quadInOut, function ()
table.remove(tris,j)
o.t = 2000
end)
end
end
end
function addRay(touch)
-- touch position in interval -1,1
local tp = vec2(touch.x/WIDTH-.5,touch.y/HEIGHT-.5) * 2
-- distance from eye to screen for perspective(45)
local d = 1/math.tan(math.rad(45/2))
local p = vec3(0,0,0)
local dir = forward*d + up*tp.y + right*tp.x*WIDTH/HEIGHT
local o = {
t = 100,
dir = dir:normalize(),
m = Mesh("Space Art:Red Explosion"):right(right)
:up(up):quad(p, 10,10)
}
table.insert(rays, o)
return o
end
--# Mesh
Mesh = class()
function Mesh:init(texture)
self.vs, self.uvs = {}, {}
self.buffers = {}
self.texture = texture
self.ds = {
right = vec3(1,0,0),
up = vec3(0,1,0),
size = vec2(1,1),
center = true
}
self:reset()
end
function Mesh:reset()
self.ps = {}
for k,v in pairs(self.ds) do
self.ps[k] = v
end
return self
end
function Mesh:defaults()
for k,v in pairs(self.ps) do
self.ds[k] = v
end
return self
end
function Mesh:buffer(name, val, n)
if not self.buffers[name] then
self.buffers[name] = {}
end
for i=1,n do
table.insert(self.buffers[name], val)
end
return self
end
function Mesh:right(v) self.ps.right = v;return self end
function Mesh:up(v) self.ps.up = v;return self end
function Mesh:size(v) self.ps.size = v;return self end
function Mesh:rotate(a) self.ps.rotate = a;return self end
function mult(m, v) -- matrix multiplication
return vec3(
m[1]*v.x+m[2]*v.y+m[3]*v.z,
m[5]*v.x+m[6]*v.y+m[7]*v.z,
m[9]*v.x+m[10]*v.y+m[11]*v.z
)
end
function Mesh:quad(pos, w, h)
local ps = self.ps
w,h = w or ps.size.x, h or w or ps.size.y
local right = ps.right * w
local up = ps.up * h
if ps.rotate then
local n = right:cross(up):normalize()
local m = matrix():rotate(ps.rotate, n.x,n.y,n.z)
right, up = mult(m,right), mult(m,up)
end
if ps.center then
pos = pos - (right+up)*.5
end
table.insert(self.vs, pos)
table.insert(self.vs, pos+right)
table.insert(self.vs, pos+up)
table.insert(self.vs, pos+right)
table.insert(self.vs, pos+up+right)
table.insert(self.vs, pos+up)
table.insert(self.uvs, vec2(0,0))
table.insert(self.uvs, vec2(1,0))
table.insert(self.uvs, vec2(0,1))
table.insert(self.uvs, vec2(1,0))
table.insert(self.uvs, vec2(1,1))
table.insert(self.uvs, vec2(0,1))
self:reset()
return self
end
function Mesh:construct(s)
local m = mesh()
if s then m.shader = s end
m.vertices, m.texCoords = self.vs, self.uvs
if self.texture then
m.texture = asset(self.texture)
end
for k,v in pairs(self.buffers) do
(m:buffer(k)):set(v)
end
m:setColors(color(255, 255, 255, 255))
self.m = m
return m
end
function Mesh:draw()
if not self.m then self:construct() end
self.m:draw()
end
-- http://en.wikipedia.org/wiki/Möller–Trumbore_intersection_algorithm
function trintersect(origin, dir, v1, v2, v3)
local epsilon = .001
local e1, e2 = v2-v1, v3-v1
local pv = dir:cross(e2)
local det = e1:dot(pv)
if det > -epsilon and det < epsilon then return false end
local invDet = 1/det
local tv = origin - v1
local u = tv:dot(pv) * invDet
if (u < 0) or (u > 1) then return false end
local qv = tv:cross(e1)
local v = dir:dot(qv) * invDet
if v < 0 or (u+v) > 1 then return false end
local hitDistance = e2:dot(qv) * invDet
return (hitDistance >= 0) and hitDistance or false
end
--# Skybox
Skybox = class()
function Skybox:init()
local s = 20000
local hs = s/2
self.ms = {
Mesh("GSSkyny"):quad(vec3(0,0,-hs),s):construct(),
Mesh("GSSkypy"):up(vec3(0,-1,0)):
quad(vec3(0,0,hs),s):construct(),
Mesh("GSSkynz"):right(vec3(-1,0,0)):
up(vec3(0,0,1)):quad(vec3(0,-hs,0),s):construct(),
Mesh("GSSkypz"):up(vec3(0,0,1)):
quad(vec3(0,hs,0),s):construct(),
Mesh("GSSkypx"):right(vec3(0,-1,0)):up(vec3(0,0,1)):
quad(vec3(hs,0,0),s):construct(),
Mesh("GSSkynx"):right(vec3(0,1,0)):up(vec3(0,0,1)):
quad(vec3(-hs,0,0),s):construct()
}
end
function Skybox:draw()
for i,v in ipairs(self.ms) do
v:draw()
end
end
function Skybox:touched(touch)
end
function Skybox:hit(origin, ray)
local vs = self.ms[1].vertices
if trintersect(origin, ray, vs[1],vs[2],vs[3]) or
trintersect(origin, ray, vs[4],vs[5],vs[6]) then
print("yes")
end
end
--# Assets
-- utility function to download assets for the first run.
-- returns an empty image with the correct size while waiting
-- for http.request result.
local baseUrl = "http://goingsouth.meteor.com/"
local c = ContentScaleFactor
local imageMap = {
GSWing = {256,256,"wing.png"},
GSTail = {64,256,"tailfeather.png"},
GSTnlogy = {512,256,"tnlogy.png"},
GSLogo = {512,256,"goingsouth.png"},
GSClouds = {256,256, "clouds.png"},
-- Using skybox graphics by Jochum Skoglund
GSSkynx = {c*512,c*512,"skybox/nx.jpg"},
GSSkyny = {c*512,c*512,"skybox/ny.jpg"},
GSSkynz = {c*512,c*512,"skybox/nz.jpg"},
GSSkypx = {c*512,c*512,"skybox/px.jpg"},
GSSkypy = {c*512,c*512,"skybox/py.jpg"},
GSSkypz = {c*512,c*512,"skybox/pz.jpg"},
}
function asset(name)
if string.find(name, ":") then return name end
local img = readImage("Documents:" .. name)
if img == nil then
local w,h,url = unpack(imageMap[name] or {1,1})
img = image(w,h)
if url then
http.request(baseUrl .. url, function (data)
pushStyle()
setContext(img)
spriteMode(CORNER)
sprite(data,0,0,w,h)
setContext()
popStyle()
saveImage("Documents:" .. name, data)
end)
end
end
return img
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment