3D Viewer 5
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 | |
--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