Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Last active December 30, 2015 21:59
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 dermotbalson/7891544 to your computer and use it in GitHub Desktop.
Save dermotbalson/7891544 to your computer and use it in GitHub Desktop.
3D Viewer 5
--# Main
--3D Model viewer
State={Ready=1,Loading=2,Error=3}
function setup()
parameter.integer("Choose",1,#Models,3)
parameter.action("Load",ShowModel)
parameter.integer("Zoom",1,300,50)
parameter.boolean("AutoRotate",true)
parameter.integer("X",-180,180,0)
parameter.integer("Y",-180,180,0)
parameter.integer("Z",-180,180,0)
parameter.action("DeleteStoredData",DeleteData)
parameter.integer("FPS",0,60,60)
--rotate shuttle in all three directions
rot=vec3(0,0,0)
tween( 15, rot, { x = 360 }, { easing = tween.easing.linear, loop = tween.loop.pingpong } )
tween( 27, rot, { y = 360 }, { easing = tween.easing.linear, loop = tween.loop.pingpong } )
tween( 35, rot, { z = 360 }, { easing = tween.easing.linear, loop = tween.loop.pingpong } )
--print model list
output.clear()
for i=1,#Models do print(i,Models[i].name) end
state=State.Ready
end
function ShowModel()
state=State.Loading
newModel=OBJ(Models[Choose].name,Models[Choose].url)
end
function DeleteData()
MM:DeleteData()
end
function ConfigureModel()
MM=newModel
local m=MM.m
for i=1,#m do --add lighting to each mesh
m[i]:setColors(color(255))
m[i].shader=shader(diffuseShader.vertexShader,diffuseShader.fragmentShader)
local ca,cd,cs
if m[i].settings then
ca = m[i].settings.Ka or color(255)
cd = m[i].settings.Kd or color(255)
cs = m[i].settings.Ks or color(255)
sp = m[i].settings.Ns or 32
if m[i].settings.map then m[i].texture=OBJ.imgPrefix..m[i].settings.map end
else ca,cd=color(255),color(255)
end
local a=0.2
m[i].shader.ambientColor=color(ca.r*a,ca.g*a,ca.b*a,255)
local d=1
m[i].shader.directColor=color(cd.r*d,cd.g*d,cd.b*d)
m[i].shader.directDirection=vec4(-2,1,4,0):normalize()
m[i].shader.specularColor=cs
m[i].shader.specularPower=sp
m[i].shader.shine=1
if m[i].hasNormals~=true then
m[i].normals=CalculateAverageNormals(m[i].vertices,0)
end
m[i].shader.reflect=1
if m[i].texture then m[i].shader.hasTexture=true else m[i].shader.hasTexture=false end
end
output.clear()
for i=1,#Models do print(i,Models[i].name) end
state=State.Ready
end
function draw()
background(116, 173, 182, 255)
FPS=FPS*0.9+0.1/DeltaTime
if state==State.Loading then
if newModel and newModel.ready then ConfigureModel() end
return
end
perspective()
camera(0,0,Zoom,0,0,-Zoom)
pushMatrix()
if AutoRotate then
rotate(rot.x,1,0,0)
rotate(rot.y,0,1,0)
rotate(rot.z,0,0,1)
else
rotate(X,1,0,0)
rotate(Y,0,1,0)
rotate(Z,0,0,1)
end
if MM then
local eye=vec3(0,0,Zoom)
MM:draw(eye)
end
popMatrix()
if touchPos then setContext() end
end
function GetColor(n)
local b=math.fmod(n,256)
local a=(n-b)/255
return color(a,b,0)
end
function CalculateNormals(vertices)
--this assumes flat surfaces, and hard edges between triangles
local norm = {}
for i=1, #vertices,3 do --calculate normal for each set of 3 vertices
local n = ((vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i])):normalize()
norm[i] = n --then apply it to all 3
norm[i+1] = n
norm[i+2] = n
end
return norm
end
function CalculateAverageNormals(vertices,f)
--average normals at each vertex
--first get a list of unique vertices, concatenate the x,y,z values as a key
local norm,unique= {},{}
for i=1, #vertices do
unique[vertices[i].x ..vertices[i].y..vertices[i].z]=vec3(0,0,0)
end
--calculate normals, add them up for each vertex and keep count
for i=1, #vertices,3 do --calculate normal for each set of 3 vertices
local n = (vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i])
for j=0,2 do
local v=vertices[i+j].x ..vertices[i+j].y..vertices[i+j].z
unique[v]=unique[v]+n
end
end
--calculate average for each unique vertex
for i=1,#unique do
unique[i] = unique[i]:normalize()
end
--now apply averages to list of vertices
for i=1, #vertices,3 do --calculate average
local n = (vertices[i+1] - vertices[i]):cross(vertices[i+2] - vertices[i])
for j=0,2 do
norm[i+j] = unique[vertices[i+j].x ..vertices[i+j].y..vertices[i+j].z]
end
end
return norm
end
--# Assets
--Assets
Models={
{name="Shuttle",
url="https://gist.github.com/dermotbalson/7884446/raw/a9766e3e63a7e83ffcb73a3ae68e5beba4d0be96/gistfile1.txt"},
{name="Me262",
url="https://gist.github.com/dermotbalson/7885523/raw/426d21ff34f738d2fc414a751c87db46792a6743/gistfile1.txt"},
{name="F22",url="http://bit.ly/1f333ha"},
{name="Merc",
url="https://gist.github.com/dermotbalson/7886036/raw/bbb6113f463922fbcf703a2e1c47d49c36e9090f/gistfile1.txt"},
{name="Spitfire",
url="https://gist.github.com/dermotbalson/7887492/raw/4ab63bb86028fd01bc535a670d90b0d98f148848/gistfile1.txt"}
}
--# OBJ
--OBJ library
OBJ=class()
OBJ.DataPrefix="cfg_"
OBJ.imgPrefix="Documents:z3D"
function OBJ:init(name,url,callback)
self.name=name
self.callback=callback
self.data=readGlobalData(OBJ.DataPrefix..name)
if self.data then self:ProcessData()
else http.request(url,function(d) self:DownloadData(d) end) end
end
function OBJ:DownloadData(data)
if data~=nil and string.find(data,"[obj]") then
saveGlobalData(OBJ.DataPrefix..self.name,data)
self.data=data
self:ProcessData()
else print("Error loading data for "..self.name) return end
end
function OBJ:ProcessData()
self.mtl={}
self.m={}
local p, v, tx, t, np, n={},{},{},{},{},{}
local s=self.data
local mname
local section="mtl"
for line in s:gmatch("[^\r\n]+") do
line=OBJ.trim(line)
if string.find(line,"%[obj%]")~=nil then section="obj" mname=nil end
--material definition section
if section=="mtl" then
if string.find(line,"newmtl") then
mname=OBJ.GetValue(line)
--print(mname)
self.mtl[mname]={}
else
local code=string.sub(line,1,2)
if code=="Ka" then --ambient
self.mtl[mname].Ka=OBJ.GetColor(line)
--print(mname,"Ka",OBJ.mtl[mname].Ka[1],OBJ.mtl[mname].Ka[2],OBJ.mtl[mname].Ka[3])
elseif code=="Kd" then --diffuse
self.mtl[mname].Kd=OBJ.GetColor(line)
--print(mname,"Kd",OBJ.mtl[mname].Kd[1],OBJ.mtl[mname].Kd[2],OBJ.mtl[mname].Kd[3])
elseif code=="Ks" then --specular
self.mtl[mname].Ks=OBJ.GetColor(line)
--print(mname,"Ks",OBJ.mtl[mname].Ks[1],OBJ.mtl[mname].Ks[2],OBJ.mtl[mname].Ks[3])
elseif code=="Ns" then --specular exponent
self.mtl[mname].Ns=OBJ.GetValue(line)
--print(mname,"Ns",OBJ.mtl[mname].Ns)
elseif code=="ill" then --illumination code
self.mtl[mname].illum=OBJ.GetValue(line)
--print(mname,"illum",OBJ.mtl[mname].illum)
elseif code=="ma" then --texture map name
local u=OBJ.split(OBJ.GetValue(line)," ")
if string.find(u[1],"%.") then
self.mtl[mname].map=string.sub(u[1],1,string.find(u[1],"%.")-1)
else
self.mtl[mname].map=u[1]
end
self.mtl[mname].path=u[2]
--print(mname,line,"\n",OBJ.mtl[mname].map,"\n",OBJ.mtl[mname].path)
end
end
--data section
elseif section=="obj" then
--read in groups of data into separate meshes
local code=string.sub(line,1,2)
--look for material settings, a separate mesh is used for each
if string.find(line,"usemtl") then
if mname then
local m=mesh()
m.vertices=v
if #t>0 then m.texCoords=t end
if #n>0 then m.normals=n end
if self.mtl[mname] then m.settings=self.mtl[mname] end
self.m[#self.m+1]=m
end
mname=OBJ.GetValue(line)
v,t,n={},{},{}
--print(mname)
end
if code=="v " then --point position
p[#p+1]=OBJ.GetVec3(line)
elseif code=="vn" then --point normal
np[#np+1]=OBJ.GetVec3(line)
elseif code=="vt" then --texture co-ord
tx[#tx+1]=OBJ.GetVec2(line)
elseif code=="f " then --vertex
local pts,ptex,pnorm=OBJ.GetList(line)
if #pts==3 then
for i=1,3 do v[#v+1]=p[tonumber(pts[i])] end
if ptex then for i=1,3 do t[#t+1]=tx[tonumber(ptex[i])] end end
if pnorm then for i=1,3 do n[#n+1]=np[tonumber(pnorm[i])] end end
elseif #pts==4 then
for i=1,3 do v[#v+1]=p[tonumber(pts[i])] end
if ptex then for i=1,3 do t[#t+1]=tx[tonumber(ptex[i])] end end
if pnorm then for i=1,3 do n[#n+1]=np[tonumber(pnorm[i])] end end
v[#v+1]=p[tonumber(pts[3])]
if ptex then t[#t+1]=tx[tonumber(ptex[3])] end
if pnorm then n[#n+1]=np[tonumber(pnorm[3])] end
v[#v+1]=p[tonumber(pts[4])]
if ptex then t[#t+1]=tx[tonumber(ptex[4])] end
if pnorm then n[#n+1]=np[tonumber(pnorm[4])] end
v[#v+1]=p[tonumber(pts[1])]
if ptex then t[#t+1]=tx[tonumber(ptex[1])] end
if pnorm then n[#n+1]=np[tonumber(pnorm[1])] end
elseif #pts>4 then
local cx,cy,cz=0,0,0
local ttx,tty=0,0
local nx,ny,nz=0,0,0
for i=1,#pts do
local u=p[tonumber(pts[i])] cx,cy,cz=cx+u.x,cy+u.y,cz+u.z
if ptex then local u=tx[tonumber(ptex[i])] ttx,tty=ttx+u.x,tty+u.y end
--if pnorm then local u=p[tonumber(pnorm[i])] nx,ny,nz=nx+u.x,ny+u.y,nz+u.z end
end
local cp=vec3(cx/#pts,cy/#pts,cz/#pts)
if ptex then ct=vec2(ttx/#pts,tty/#pts) end
--if pnorm then cn=vec3(nx/#pts,ny/#pts,nz/#pts):normalize() end
local j
for i=1,#pts do
if i<#pts then j=i+1 else j=1 end
v[#v+1]=p[tonumber(pts[i])]
if ptex then t[#t+1]=tx[tonumber(ptex[i])] end
--if pnorm then n[#n+1]=np[tonumber(pnorm[i])] end
v[#v+1]=p[tonumber(pts[j])]
if ptex then t[#t+1]=tx[tonumber(ptex[j])] end
--if pnorm then n[#n+1]=np[tonumber(pnorm[j])] end
v[#v+1]=cp
if ptex then t[#t+1]=ct end
--if pnorm then n[#n+1]=np[tonumber(cn)] end
end
end
end
end
end
local m=mesh()
m.vertices=v
if #t>0 then m.texCoords=t end
if #n>0 then m.normals=n end
m.settings=self.mtl[mname]
self.m[#self.m+1]=m
--download images if not stored locally
self.MissingImages={}
for i,O in pairs(self.mtl) do
if O.map then
local y=readImage(OBJ.imgPrefix..O.map)
if not y then self.MissingImages[#self.MissingImages+1]={O.map,O.path} end
end
end
if #self.MissingImages>0 then self:LoadImages()
else self.ready=true return end
end
function OBJ:LoadImages()
--print("downloading"..self.MissingImages[1][1])
http.request(self.MissingImages[1][2],function(d) self:StoreImage(d) end)
end
function OBJ:StoreImage(d)
--print("saving"..self.MissingImages[1][1])
saveImage(OBJ.imgPrefix..self.MissingImages[1][1],d)
table.remove(self.MissingImages,1)
if #self.MissingImages==0 then self.ready=true else self:LoadImages() end
end
function OBJ:DeleteData()
saveGlobalData(OBJ.DataPrefix..self.name,nil)
for i,O in pairs(self.mtl) do
if O.map then
---print("deleting "..OBJ.imgPrefix..O.map)
local y=saveImage(OBJ.imgPrefix..O.map,nil)
end
end
end
function OBJ.GetColor(s)
local s1=string.find(s," ")
local s2=string.find(s," ",s1+1)
local s3=string.find(s," ",s2+1)
return color(string.sub(s,s1+1,s2-1)*255,string.sub(s,s2+1,s3-1)*255,string.sub(s,s3+1,string.len(s))*255)
end
function OBJ.GetVec3(s)
local s1=string.find(s," ")
local s2=string.find(s," ",s1+1)
local s3=string.find(s," ",s2+1)
return vec3(math.floor(string.sub(s,s1+1,s2-1)*100)/100,
math.floor(string.sub(s,s2+1,s3-1)*100)/100,
math.floor(string.sub(s,s3+1,string.len(s))*100)/100)
end
function OBJ.GetVec2(s)
local s1=string.find(s," ")
local s2=string.find(s," ",s1+1)
local s3=string.find(s," ",s2+1)
if s3 then
return vec3(math.floor(string.sub(s,s1+1,s2-1)*100)/100,
math.floor(string.sub(s,s2+1,s3-1)*100)/100)
else
return vec2(math.floor(string.sub(s,s1+1,s2-1)*100)/100,
math.floor(string.sub(s,s2+1,string.len(s))*100)/100)
end
end
function OBJ:draw(e)
for i=1,#self.m do
self.m[i].shader.mModel=modelMatrix() --part of lighting
self.m[i].eyePosition=e
self.m[i]:draw()
end
end
function OBJ.GetValue(s)
return string.sub(s,string.find(s," ")+1,string.len(s))
end
function OBJ.trim(s)
while string.find(s," ") do s = string.gsub(s," "," ") end
return s:match'^()%s*$' and '' or s:match'^%s*(.*%S)'
end
function OBJ.split(s,sep)
sep=sep or "/"
local p={}
local pattern = string.format("([^%s]+)", sep)
string.gsub(s,pattern, function(c) p[#p+1] = c end)
return p
end
function OBJ.GetList(s)
local p,t,n={},{},{}
--for word in s:gmatch("%w+") do table.insert(p, word) end
p=OBJ.split(s," ")
table.remove(p,1)
for i=1,#p do
local a=OBJ.split(p[i])
if #a==1 then
p[i]=math.abs(a[1])
elseif #a==2 then
p[i]=math.abs(a[1])
t[i]=math.abs(a[2])
elseif #a==3 then
p[i]=math.abs(a[1])
t[i]=math.abs(a[2])
n[i]=math.abs(a[3])
end
end
return p,t,n
end
--# Shader
diffuseShader={
vertexShader=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform vec4 directColor;
uniform vec4 directDirection;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
attribute vec3 normal;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vDirectDiffuse;
void main()
{
vColor = color;
gl_Position = modelViewProjection * position;
vTexCoord = texCoord;
vNormal = mModel * vec4( normal, 0.0 );
vec4 norm = normalize(vNormal);
vPosition = mModel * position;
vDirectDiffuse = directColor * max( 0.0, dot( norm, directDirection ));
}
]],
fragmentShader=[[
precision highp float;
uniform vec4 ambientColor;
uniform lowp sampler2D texture;
uniform float reflect;
uniform bool hasTexture;
uniform vec4 directColor;
uniform float directStrength;
uniform vec4 directDirection;
uniform vec4 eyePosition;
uniform vec4 specularColor;
uniform float specularPower;
uniform float shine;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
varying lowp vec4 vPosition;
varying lowp vec4 vNormal;
varying vec4 vDirectDiffuse;
vec4 normalizedNormal = normalize(vNormal);
vec4 GetSpecularColor(vec4 lightPosition, vec4 lightColor, bool IsDirectional)
{
vec4 lightDirection;
if (IsDirectional) lightDirection = lightPosition;
else lightDirection = vec4( normalize( lightPosition - vPosition ));
vec4 cameraDirection = normalize( eyePosition - vPosition );
vec4 halfAngle = normalize( cameraDirection + lightDirection );
vec4 specularColor = min(lightColor + 0.5, 1.0);
float spec = pow( max( 0.0, dot( normalizedNormal, halfAngle)), specularPower );
return specularColor * spec * shine;
}
void main()
{
lowp vec4 ambient=vec4(0.,0.,0.,0.);
lowp vec4 diffuse=vec4(0.,0.,0.,0.);
lowp vec4 specular=vec4(0.,0.,0.,0.);
lowp vec4 pixel;
if (hasTexture) pixel = texture2D( texture, vTexCoord);
else pixel = vColor;
ambient = pixel * ambientColor;
diffuse = diffuse + pixel * vDirectDiffuse;
specular=specular + pixel * directStrength * GetSpecularColor(directDirection, specularColor, true);
vec4 totalColor = clamp( reflect * (ambient + diffuse + specular),0.,1.);
totalColor.a=1.;
gl_FragColor=totalColor;
}
]]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment