Created
October 22, 2015 06:16
-
-
Save anonymous/8151c448b366642bc3a6 to your computer and use it in GitHub Desktop.
Gists Codea Upload
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 | |
displayMode(FULLSCREEN) | |
function setup() | |
--define direction of light source - this is the direction light is coming from, | |
--ie if you draw a line from here to 0,0,0, that is the direction of the light | |
light=vec3(1,.5,0) | |
-- candle | |
candle ={} | |
candle[#candle+1]=addCylinder{centre=vec3(0,0,0),height=10,radius=1,faceted=false, | |
ends=2,color=color(255, 255, 255, 255),light=light,ambience=.5} | |
candle[#candle+1]=addSphereSegment{centre=vec3(0,6,0),size=1.5,color=color(255, 173, 0, 149), | |
startLongitude=180,deltaLongitude=180} | |
candle[#candle+1]=addCylinder{centre=vec3(0,6+3,0),height=6,startRadius=1.5,endRadius=0, | |
faceted=false, | |
ends=2,color=color(255, 173, 0, 149),} | |
-- cake | |
shapes={} | |
shapes[#shapes+1]=addCylinder{centre=vec3(0,0,0),height=10,radius=30,faceted=false, | |
ends=2,color=color(233, 233, 233, 255),light=light,ambience=.5} | |
shapes[#shapes+1]=addCylinder{centre=vec3(0,7,0),height=4,radius=30,faceted=false, | |
ends=2,color=color(255, 0, 0, 255),light=light,ambience=.5} | |
shapes[#shapes+1]=addCylinder{centre=vec3(0,14,0),height=10,radius=30,faceted=false, | |
ends=2,color=color(233, 233, 233, 255),light=light,ambience=.5} | |
shapes[#shapes+1]=addCylinder{centre=vec3(0,19.5,0),height=1,radius=30,faceted=false, | |
ends=2,color=color(255, 0, 0, 255),light=light,ambience=.5} | |
for _,s in pairs(shapes) do | |
s.pos = vec3(0,0,0) | |
end | |
--set up some simple rotation to make them spin | |
rot=vec3(0,0,0) --current rotation (degrees) | |
deltaRot=vec3(0,1,0)*.2 --change in rotation each time we draw | |
end | |
function draw() | |
background(50) | |
perspective() --puts Codea in 3D mode | |
camera(0,50,120,0,0,0) --camera is 250 pixels back from 0, looking at (0,0,0) | |
rotate(rot.x,1,0,0) rotate(rot.y,0,1,0) rotate(rot.z,0,0,1) | |
for _,s in pairs(shapes) do | |
pushMatrix() | |
rotate(rot.x,1,0,0) rotate(rot.y,0,1,0) rotate(rot.z,0,0,1) | |
--use this next line with all your objects that use lighting | |
if s.shader then s.shader.invModel = modelMatrix():inverse():transpose() end | |
s:draw() | |
popMatrix() | |
end | |
translate(0,25,0) | |
for x=-1,1,2 do for z=-1,1,2 do | |
pushMatrix() | |
translate(x*10,0,z*10) | |
for _,s in pairs(candle) do | |
pushMatrix() | |
rotate(rot.x,1,0,0) rotate(rot.y,0,1,0) rotate(rot.z,0,0,1) | |
--use this next line with all your objects that use lighting | |
if s.shader then s.shader.invModel = modelMatrix():inverse():transpose() end | |
s:draw() | |
popMatrix() | |
end | |
popMatrix() | |
end end | |
rot=rot+deltaRot --change rotation | |
end | |
--# 3dLib | |
-- MeshShapes | |
--[[ | |
** Introduction | |
The setup and draw functions below create some shapes, to show how it's done. The best way to get started is to play with the code to see what happens when you change things. You should also look at the library code underneath, because there are extra features documented there for each shape. | |
Queries or bugs: to LoopSpace on the Codea forum | |
Instructions and hints follow (don't be put off by the fact you have to read something, it's not hard) | |
** Passing shape parameters ** | |
Each shape has a number of named parameters (inputs). You generally only need to use the ones you want. Each library function has a detailed description above it, telling you what the parameters are, and what they are for. | |
Note that in order to pass through named parameters, they need to be in a table, like so | |
functionName({a=1,b=2,c=3}) | |
and, if you are only passing through one table and nothing else, you can leave off the round brackets if you like. | |
functionName{a=1,b=2,c=3} | |
and that's what we've done below when creating shapes. | |
** Lighting ** | |
3D shapes usually need lighting or textures. To include light and shade, you set the direction of the light (see setup function below), and pass it in the "light" parameter, along with "ambience", which is the minimum brightness of the object (0-1). | |
The code has two lighting options. The default uses a shader (the strange code at the bottom of this page), and requires a special line of code in the draw function that does strange things to matrices. | |
There is also a basic option that doesn't require the shader or the line of code in draw. To set it, just include | |
basicLighting=true as a parameter.You will see examples of this below. Try both. | |
** Help! I don't know 3D. | |
3D doesn't have to be scary. The Codea forum has links to explanations and tutorials, and ask if you get stuck. You can also use the code below as a starting point. | |
--]] | |
--- SHAPE LIBRARY STARTS HERE ------ | |
local __doJewel, __doSuspension, __doPyramid, __doBlock, __addTriangle, __doSphere, | |
__threeFromTwo, __orthogonalTo, __doCylinder, __discreteNormal, __doCone, __doPoly, | |
__doFacetedClosedCone, __doFacetedOpenCone, __doSmoothClosedCone, | |
__doSmoothOpenCone, __doFacetedClosedCylinder, __doFacetedOpenCylinder, | |
__doSmoothClosedCylinder, __doSmoothOpenCylinder, __initmesh | |
--[[ | |
| Option | Default | Description | | |
|:-------------|:-------------------------|:------------| | |
| `mesh` | new mesh | The mesh to add the shape to. | | |
| `position` | end of mesh | The position in the mesh at which to start the shape. | | |
| `origin` | `vec3(0,0,0)` | The origin (or centre) of the shape. | | |
| `axis` | `vec3(0,1,0)` | The axis specifies the direction of the jewel. | | |
| `aspect` | 1 | The ratio of the height to the diameter of the gem. | | |
| `size` | the length of the axis | The size of the jewel; specifically the distance from the | |
centre to the apex of the jewel. | | |
| `colour`/`color` | white | The colour of the jewel. | | |
| `texOrigin` | `vec2(0,0)` | If using a sprite sheet, this is the lower left corner of the | |
rectangle associated with this gem. | | |
| `texSize` | `vec2(1,1)` | This is the width and height of the rectangle of the | |
texture associated to this gem. | |
--]] | |
function addJewel(t) | |
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting) | |
local p = t.position or (m.size + 1) | |
p = p + (1-p)%3 | |
local o = t.origin or vec3(0,0,0) | |
local c = t.colour or t.color or color(255, 255, 255, 255) | |
local as = t.aspect or 1 | |
local to = t.texOrigin or vec2(0,0) | |
local ts = t.texSize or vec2(1,1) | |
local a = {} | |
a[1] = t.axis or vec3(0,1,0) | |
if t.size then | |
a[1] = a[1]:normalize()*t.size | |
end | |
a[2] = __orthogonalTo(a[1]) | |
a[3] = a[1]:cross(a[2]) | |
local la = a[1]:len() | |
for i = 2,3 do | |
a[i] = as*la*a[i]:normalize() | |
end | |
local n = t.sides or 12 | |
if p > m.size - 12*n then | |
m:resize(p + 12*n-1) | |
end | |
local l,am | |
if rl then | |
l = vec3(0,0,0) | |
am = 1 | |
else | |
l = t.light or vec3(0,0,0) | |
if t.intensity then | |
l = l:normalize()*t.intensity | |
elseif l:lenSqr() > 1 then | |
l = l:normalize() | |
end | |
am = t.ambience or (1 - l:len()) | |
end | |
local np = __doJewel(m,p,n,o,a,c,to,ts,l,am) | |
if ret then | |
return m,p,np | |
else | |
return m | |
end | |
end | |
--[[ | |
A jewel is a special case of a "suspension". | |
m mesh to add shape to | |
p position of first vertex of shape | |
n number of sides | |
o centre of shape (vec3) | |
a axes (table of vec3s) | |
col colour | |
to offset of texture region (vec2) | |
ts size of texture region (vec2) | |
l light vector | |
--]] | |
function __doJewel(m,p,n,o,a,col,to,ts,l,am) | |
local th = math.pi/n | |
local cs = math.cos(th) | |
local sn = math.sin(th) | |
local h = (1 - cs)/(1 + cs) | |
local k,b,c,d,tb,tc,td,tex,pol | |
tex,pol={},{} | |
c = cs*a[2] + sn*a[3] | |
d = -sn*a[2] + cs*a[3] | |
tc = cs*vec2(ts.x*.25,0) + sn*vec2(0,ts.y*.5) | |
td = -sn*vec2(ts.x*.25,0) + cs*vec2(0,ts.y*.5) | |
for i = 1,2*n do | |
k = 2*(i%2) - 1 | |
table.insert(pol,o+h*k*a[1]+c) | |
table.insert(tex,tc) | |
c,d = cs*c + sn*d,-sn*c + cs*d | |
tc,td = cs*tc + sn*td,-sn*tc + cs*td | |
end | |
return __doSuspension(m,p,2*n,o,{o+a[1],o-a[1]},pol,tex,col,to,ts,true,true,l,am) | |
end | |
--[[ | |
A "suspension" is a double cone on a curve. | |
m mesh to add shape to | |
p position of first vertex of shape | |
n number of points | |
o centre of shape (vec3) | |
a apexes (table of 2 vec3s) | |
v vertices (table of vec3s in cyclic order) | |
t texture coordinates corresponding to vertices (relative to centre) | |
col colour | |
to offset of texture region (vec2) | |
ts size of texture region (vec2) | |
f faceted | |
cl closed curve or not | |
l light vector | |
--]] | |
function __doSuspension(m,p,n,o,a,v,t,col,to,ts,f,cl,l,am) | |
local tu | |
for i=1,2 do | |
tu = to+vec2(ts.x*(i*.5-.25),ts.y*.5) | |
p = __doCone(m,p,n,o,a[i],v,t,col,tu,ts,f,cl,l,am) | |
end | |
return p | |
end | |
--[[ | |
A "cone" is formed by taking a curve in space and joining each of its points to an apex. | |
If the original curve is made from line segments, the resulting cone has a natural triangulation | |
which can be used to construct it as a mesh. | |
m mesh | |
p position in mesh | |
n number of points | |
o "internal" point (to ensure that normals point outwards) | |
a apex of cone | |
v table of base points | |
t table of texture points | |
col colour | |
to texture offset | |
ts not used | |
f faceted or not | |
cl closed curve or not | |
l light vector | |
--]] | |
function __doCone(m,p,n,o,a,v,t,col,to,ts,f,cl,l,am) | |
if f then | |
if cl then | |
return __doFacetedClosedCone(m,p,n,o,a,v,t,col,to,ts,l,am) | |
else | |
return __doFacetedOpenCone(m,p,n,o,a,v,t,col,to,ts,l,am) | |
end | |
else | |
if cl then | |
return __doSmoothClosedCone(m,p,n,o,a,v,t,col,to,ts,l,am) | |
else | |
return __doSmoothOpenCone(m,p,n,o,a,v,t,col,to,ts,l,am) | |
end | |
end | |
end | |
function __doFacetedClosedCone(m,p,n,o,a,v,t,col,to,ts,l,am) | |
local j,nml,c | |
for k=1,n do | |
j = k%n + 1 | |
nml = (v[k] - a):cross(v[j] - a) | |
if nml:dot(a - o) < 0 then | |
nml = -nml | |
end | |
nml = nml:normalize() | |
c = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nml))) | |
__addTriangle(m,p,v[j],v[k],a,c,c,c,nml,nml,nml,to+t[j],to+t[k],to) | |
p = p + 3 | |
end | |
return p | |
end | |
function __doFacetedOpenCone(m,p,n,o,a,v,t,col,to,ts,l,am) | |
local j,nml,c | |
for k=1,n-1 do | |
j = k + 1 | |
nml = (v[k] - a):cross(v[j] - a) | |
if nml:dot(a - o) < 0 then | |
nml = -nml | |
end | |
nml = nml:normalize() | |
c = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nml))) | |
__addTriangle(m,p,v[j],v[k],a,c,c,c,nml,nml,nml,to+t[j],to+t[k],to) | |
p = p + 3 | |
end | |
return p | |
end | |
function __doSmoothClosedCone(m,p,n,o,a,v,t,col,to,ts,l,am) | |
local j,nmla,nmlb,nmlc,cc,ca,nb,cb | |
nmlb = vec3(0,0,0) | |
nmlc = __discreteNormal(v[1],o,v[n],a,v[2]) | |
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc))) | |
nb = vec3(0,0,0) | |
for k=1,n do | |
j = k%n + 1 | |
nb = nb + __discreteNormal(v[j],o,v[k],a,v[j%n+1]) | |
end | |
nb = nb:normalize() | |
cb = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nb))) | |
for k=1,n do | |
j = k%n + 1 | |
nmla = nmlc | |
ca = cc | |
nmlc = __discreteNormal(v[j],o,v[k],a,v[j%n+1]) | |
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc))) | |
__addTriangle(m,p,v[j],v[k],a,cc,ca,cb,nmlc,nmla,nmlb,to+t[j],to+t[k],to) | |
p = p + 3 | |
end | |
return p | |
end | |
function __doSmoothOpenCone(m,p,n,o,a,v,t,col,to,ts,l,am) | |
local j,nmla,nmlb,nmlc,cc,ca,ll,nb,cb | |
ll = l:len() | |
nmlb = vec3(0,0,0) | |
nmlc = __discreteNormal(v[1],o,a,v[2]) | |
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc))) | |
nb = vec3(0,0,0) | |
for k=1,n-2 do | |
j = k + 1 | |
nb = nb + __discreteNormal(v[j],o,v[k],a,v[j%n+1]) | |
end | |
nb = nb + __discreteNormal(v[n],o,v[n-1],a) | |
nb = nb:normalize() | |
cb = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nb))) | |
for k=1,n-2 do | |
j = k + 1 | |
nmla = nmlc | |
ca = cc | |
nmlc = __discreteNormal(v[j],o,v[k],a,v[j%n+1]) | |
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc))) | |
__addTriangle(m,p,v[j],v[k],a,cc,ca,cb,nmlc,nmla,nmlb,to+t[j],to+t[k],to) | |
p = p + 3 | |
end | |
nmla = nmlc | |
ca = cc | |
nmlc = __discreteNormal(v[n],o,v[n-1],a) | |
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc))) | |
__addTriangle(m,p,v[n],v[n-1],a,cc,ca,cb,nmlc,nmla,nmlb,to+t[n],to+t[n-1],to) | |
return p + 3 | |
end | |
--[[ | |
This forms a surface which has boundary a given curve by forming a cone with the | |
barycentre of the curve as its apex. | |
m mesh | |
p position in mesh | |
n number of points | |
o "internal" point (for normals) | |
v table of base points | |
t table of texture points | |
col colour | |
to texture offset | |
ts not used | |
f faceted or not | |
cl closed curve or not | |
l light vector | |
--]] | |
function __doPoly(m,p,n,o,v,t,col,to,ts,f,cl,l,am) | |
local a,b,r = vec3(0,0,0),vec2(0,0),0 | |
for k,u in ipairs(v) do | |
a = a + u | |
r = r + 1 | |
end | |
a = a / r | |
for k,u in ipairs(t) do | |
b = b + u | |
end | |
b = b / r | |
for k=1,r do | |
t[k] = t[k] - b | |
end | |
return __doCone(m,p,n,o,a,v,t,col,to+b,ts,f,cl,l,am) | |
end | |
--[[ | |
| Option | Default | Description | | |
|:-------|:--------|:------------| | |
| `mesh` | new mesh | The mesh to add the shape to. | | |
| `position` | end of mesh | The place in the mesh at which to add the shape. | | |
| `colour`/`color` | white | The colour of the shape. | | |
| `faceted` | true | Whether to make it faceted or smooth. | | |
| `ends` | `0` | Which ends to fill in (`0` for none, `1` for start, `2` for end, `3` for both) | | |
| `texOrigin` | `vec2(0,0)` | If using a sprite sheet, this is the lower left corner of the | |
rectangle associated with this shape. | | |
| `texSize` | `vec2(1,1)` | This is the width and height of the rectangle of the | |
texture associated to this shape. | | |
There are various ways to specify the dimensions of the cylinder. | |
If given together, the more specific overrides the more general. | |
`radius` and `height` (`number`s) can be combined with `axes` (table of three `vec3`s) to | |
specify the dimensions, where the first axis vector lies along the cylinder. The vector | |
`origin` then locates the cylinder in space. | |
`startCentre`/`startCenter` (a `vec3`), `startWidth` (`number` or `vec3`), `startHeight` (`number` | |
or `vec3`), `startRadius` (`number`) specify the dimensions at the start of the cylinder (if | |
numbers, they are taken with respect to certain axes). | |
Similarly named options control the other end. | |
If axes are needed, these can be supplied via the `axes` option. | |
If just the `axis` option is given (a single `vec3`), this is the direction along the cylinder. | |
Other directions (if needed) are found by taking orthogonal vectors to this axis. | |
--]] | |
function addCylinder(t) | |
t = t or {} | |
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting) | |
local p = t.position or (m.size + 1) | |
p = p + (1-p)%3 | |
local ip = p | |
local col = t.colour or t.color or color(255, 255, 255, 255) | |
local f = true | |
local ends | |
local solid = t.solid | |
if solid then | |
ends = t.ends or 3 | |
else | |
ends = t.ends or 0 | |
end | |
if t.faceted ~= nil then | |
f = t.faceted | |
end | |
local l,am | |
if rl then | |
l = vec3(0,0,0) | |
am = 1 | |
else | |
l = t.light or vec3(0,0,0) | |
if t.intensity then | |
l = l:normalize()*t.intensity | |
elseif l:lenSqr() > 1 then | |
l = l:normalize() | |
end | |
am = t.ambience or (1 - l:len()) | |
end | |
local r = t.radius or 1 | |
local h = t.height or 1 | |
local to = t.texOrigin or vec2(0,0) | |
local ts = t.texSize or vec2(1,1) | |
local sc,si,sj,ec,ei,ej,a,o | |
if t.axis or t.axes or t.origin or t.centre or t.center then | |
if t.axis then | |
a = t.axis | |
elseif t.axes then | |
a = t.axes[1] | |
else | |
a = vec3(0,1,0) | |
end | |
if t.height then | |
a = h*a:normalize() | |
end | |
if t.origin or t.centre or t.center then | |
local o = t.origin or t.centre or t.center | |
sc,ec = o - a/2,o + a/2 | |
end | |
end | |
sc = t.startCentre or t.startCenter or sc | |
ec = t.endCentre or t.endCenter or ec | |
sc,ec,a = __threeFromTwo(sc,ec,a,vec3(0,-h/2,0),vec3(0,h/2,0),vec3(0,h,0)) | |
si = t.startWidth or t.startRadius or t.radius or 1 | |
sj = t.startHeight or t.startRadius or t.radius or 1 | |
ei = t.endWidth or t.endRadius or t.radius or 1 | |
ej = t.endHeight or t.endRadius or t.radius or 1 | |
local c,d | |
if t.axes then | |
a,c,d = unpack(t.axes) | |
end | |
if type(si) == "number" then | |
if type(sj) == "number" then | |
if not c then | |
c = __orthogonalTo(a) | |
end | |
si = si*c:normalize() | |
if not d then | |
sj = sj*a:cross(c):normalize() | |
else | |
sj = sj*d:normalize() | |
end | |
else | |
si = si*sj:cross(a):normalize() | |
end | |
elseif type(sj) == "number" then | |
sj = sj*a:cross(si):normalize() | |
end | |
if type(ei) == "number" then | |
if type(ej) == "number" then | |
if not c then | |
c = __orthogonalTo(a) | |
end | |
ei = ei*c:normalize() | |
if not d then | |
ej = ej*a:cross(c):normalize() | |
else | |
ej = ej*d:normalize() | |
end | |
else | |
ei = ei*ej:cross(a):normalize() | |
end | |
elseif type(ej) == "number" then | |
ej = ej*a:cross(ei):normalize() | |
end | |
local n = t.size or 12 | |
local sa,ea,da = __threeFromTwo(t.startAngle,t.endAngle,t.deltaAngle,0,360,360) | |
local closed | |
if da == 360 then | |
closed = true | |
solid = false | |
else | |
closed = false | |
end | |
sa = math.rad(sa) | |
ea = math.rad(ea) | |
da = math.rad(da)/n | |
o = (sc + math.cos((sa+ea)/2)*si/2 + math.sin((sa+ea)/2)*sj/2 + ec + math.cos((sa+ea)/2) | |
*ei/2 + math.sin((sa+ea)/2)*ej/2)/2 | |
local ss = 1 + math.floor((ends+1)/2) | |
if solid then | |
ss = ss + 2 | |
end | |
ts.x = ts.x / ss | |
local cs,sn,ti,tj | |
ti,tj = vec2(ts.x/2,0),vec2(0,ts.y/2) | |
cs = math.cos(sa) | |
sn = math.sin(sa) | |
si,sj = cs*si + sn*sj, -sn*si + cs*sj | |
ei,ej = cs*ei + sn*ej, -sn*ei + cs*ej | |
ti,tj = cs*ti + sn*tj, -sn*ti + cs*tj | |
local u,v,tu,tv,tw,cnrs = {},{},{},{},{},{} | |
cnrs[1] = {sc,sc+si,ec+ei,ec} | |
cs = math.cos(da) | |
sn = math.sin(da) | |
for k=0,n do | |
table.insert(u,sc+si) | |
table.insert(v,ec+ei) | |
table.insert(tu,to + vec2(ts.x*k/n,0)) | |
table.insert(tv,to + vec2(ts.x*k/n,ts.y)) | |
table.insert(tw,ti) | |
si,sj = cs*si + sn*sj, -sn*si + cs*sj | |
ei,ej = cs*ei + sn*ej, -sn*ei + cs*ej | |
ti,tj = cs*ti + sn*tj, -sn*ti + cs*tj | |
end | |
cnrs[2] = {sc, sc+cs*si - sn*sj, ec+cs*ei - sn*ej, ec} | |
local size = 6*n + math.floor((ends+1)/2)*3*n | |
if closed then | |
size = size + 6 + math.floor((ends+1)/2)*3 | |
elseif solid then | |
size = size + 24 | |
end | |
if p - 1 + size > m.size then | |
m:resize(p-1+size) | |
end | |
n = n + 1 | |
p = __doCylinder(m,p,n,o,u,v,tu,tv,col,f,closed,l,am) | |
to = to + ts/2 | |
if solid and not closed then | |
local tex = {-ts/2,vec2(ts.x/2,-ts.y/2),ts/2,vec2(-ts.x/2,ts.y/2)} | |
for i=1,2 do | |
to.x = to.x + ts.x | |
p = __doPoly(m,p,4,o,cnrs[i],tex,col,to,ts,f,true,l,am) | |
end | |
end | |
if ends%2 == 1 then | |
to.x = to.x + ts.x | |
p = __doCone(m,p,n,o,sc,u,tw,col,to,ts,f,closed,l,am) | |
end | |
if ends >= 2 then | |
to.x = to.x + ts.x | |
p = __doCone(m,p,n,o,ec,v,tw,col,to,ts,f,closed,l,am) | |
end | |
if ret then | |
return m,ip, p | |
else | |
return m | |
end | |
end | |
--[[ | |
This adds a cylinder to the mesh. | |
m mesh to add shape to | |
p position of first vertex of shape | |
n number of points | |
o centre of shape (vec3) | |
a apexes (table of 2 vec3s) | |
v vertices (table of vec3s in cyclic order) | |
t texture coordinates corresponding to vertices (relative to centre) | |
col colour | |
to offset of texture region (vec2) | |
ts size of texture region (vec2) | |
f faceted | |
cl closed | |
l light vector | |
--]] | |
function __doCylinder(m,p,n,o,u,v,ut,vt,col,f,cl,l,am) | |
if f then | |
if cl then | |
return __doFacetedClosedCylinder(m,p,n,o,u,v,ut,vt,col,l,am) | |
else | |
return __doFacetedOpenCylinder(m,p,n,o,u,v,ut,vt,col,l,am) | |
end | |
else | |
if cl then | |
return __doSmoothClosedCylinder(m,p,n-1,o,u,v,ut,vt,col,l,am) | |
else | |
return __doSmoothOpenCylinder(m,p,n,o,u,v,ut,vt,col,l,am) | |
end | |
end | |
end | |
function __doFacetedClosedCylinder(m,p,n,o,u,v,ut,vt,col,l,am) | |
local i,j,nv,nu,cu,cv,ll | |
ll = l:len() | |
for k=1,n do | |
j = k%n + 1 | |
nu = (u[j] - u[k]):cross(v[k] - u[k]):normalize() | |
nv = (v[j] - v[k]):cross(u[k] - v[k]):normalize() | |
if nu:dot(u[k]-o) < 0 then | |
nu = -nu | |
end | |
if nv:dot(v[k]-o) < 0 then | |
nv = -nv | |
end | |
cv = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv))) | |
cu = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu))) | |
__addTriangle(m,p,v[j],v[k],u[j],cv,cv,cu,nv,nv,nu,vt[j],vt[k],ut[j]) | |
p = p + 3 | |
__addTriangle(m,p,v[k],u[j],u[k],cv,cu,cu,nv,nu,nu,vt[k],ut[j],ut[k]) | |
p = p + 3 | |
end | |
return p | |
end | |
function __doFacetedOpenCylinder(m,p,n,o,u,v,ut,vt,col,l,am) | |
local i,j,nv,nu,cu,cv,ll | |
ll = l:len() | |
for k=1,n-1 do | |
j = k + 1 | |
nu = (u[j] - u[k]):cross(v[k] - u[k]):normalize() | |
nv = (v[j] - v[k]):cross(u[k] - v[k]):normalize() | |
if nu:dot(u[k]-o) < 0 then | |
nu = -nu | |
end | |
if nv:dot(v[k]-o) < 0 then | |
nv = -nv | |
end | |
cv = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv))) | |
cu = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu))) | |
__addTriangle(m,p,v[j],v[k],u[j],cv,cv,cu,nv,nv,nu,vt[j],vt[k],ut[j]) | |
p = p + 3 | |
__addTriangle(m,p,v[k],u[j],u[k],cv,cu,cu,nv,nu,nu,vt[k],ut[j],ut[k]) | |
p = p + 3 | |
end | |
return p | |
end | |
function __doSmoothClosedCylinder(m,p,n,o,u,v,ut,vt,col,l,am) | |
local i,j,nv,nu,cu,cv | |
nv,nu,cv,cu = {},{},{},{} | |
nv[1] = __discreteNormal(v[1],o,v[n],u[1],v[2]) | |
nu[1] = __discreteNormal(u[1],o,u[n],v[1],u[2]) | |
cv[1] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[1]))) | |
cu[1] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[1]))) | |
for k=1,n do | |
j = k%n + 1 | |
i = j%n + 1 | |
nv[j] = __discreteNormal(v[j],o,v[k],u[j],v[i]) | |
nu[j] = __discreteNormal(u[j],o,u[k],v[j],u[i]) | |
cv[j] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[j]))) | |
cu[j] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[j]))) | |
__addTriangle(m,p,v[j],v[k],u[j],cv[j],cv[k],cu[j],nv[j],nv[k],nu[j],vt[j],vt[k],ut[j]) | |
p = p + 3 | |
__addTriangle(m,p,v[k],u[j],u[k],cv[k],cu[j],cu[k],nv[k],nu[j],nu[k],vt[k],ut[j],ut[k]) | |
p = p + 3 | |
end | |
return p | |
end | |
function __doSmoothOpenCylinder(m,p,n,o,u,v,ut,vt,col,l,am) | |
local i,j,nv,nu,cu,cv | |
nv,nu,cv,cu = {},{},{},{} | |
nv[1] = __discreteNormal(v[1],o,u[1],v[2]) | |
nu[1] = __discreteNormal(u[1],o,v[1],u[2]) | |
cv[1] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[1]))) | |
cu[1] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[1]))) | |
for k=1,n-2 do | |
j = k + 1 | |
i = j + 1 | |
nv[j] = __discreteNormal(v[j],o,v[k],u[j],v[i]) | |
nu[j] = __discreteNormal(u[j],o,u[k],v[j],u[i]) | |
cv[j] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[j]))) | |
cu[j] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[j]))) | |
__addTriangle(m,p,v[j],v[k],u[j],cv[j],cv[k],cu[j],nv[j],nv[k],nu[j],vt[j],vt[k],ut[j]) | |
p = p + 3 | |
__addTriangle(m,p,v[k],u[j],u[k],cv[k],cu[j],cu[k],nv[k],nu[j],nu[k],vt[k],ut[j],ut[k]) | |
p = p + 3 | |
end | |
nv[n] = __discreteNormal(v[n],o,v[n-1],u[n]) | |
nu[n] = __discreteNormal(u[n],o,u[n-1],v[n]) | |
cv[n] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[n]))) | |
cu[n] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[n]))) | |
__addTriangle(m,p,v[n],v[n-1],u[n],cv[n],cv[n-1],cu[n],nv[n],nv[n-1],nu[n],vt[n],vt[n-1],ut[n]) | |
p = p + 3 | |
__addTriangle(m,p,v[n-1],u[n],u[n-1],cv[n-1],cu[n],cu[n-1],nv[n-1],nu[n],nu[n-1],vt[n-1],ut | |
[n],ut[n-1]) | |
return p+3 | |
end | |
--[[ | |
This works out a normal vector for a vertex in a triangulated surface by taking an average of | |
the triangles in which it appears. | |
The normals are weighted by the reciprocal of the size of the corresponding triangle. | |
a vertex under consideration | |
o a point to determine which side the normals lie | |
... a cyclic list of vertices, successive pairs of which make up the triangles | |
--]] | |
function __discreteNormal(a,o,...) | |
local arg = {...} | |
arg.n = #arg | |
local n = arg.n | |
local na,nb | |
na = vec3(0,0,0) | |
for k=2,n do | |
nb = (arg[k] - a):cross(arg[k-1] - a) | |
na = na + nb/nb:lenSqr() | |
end | |
na = na:normalize() | |
if na:dot(a-o) < 0 then | |
na = -na | |
end | |
return na | |
end | |
--[[ | |
Adds a pyramid to a mesh. | |
| Option | Default | Description | | |
|:-------------|:-------------------------|:------------| | |
| `mesh` | new mesh | The mesh to add the shape to. | | |
| `position` | end of mesh | The position in the mesh at which to start the shape. | | |
| `origin` | `vec3(0,0,0)` | The origin (or centre) of the shape. | | |
| `axis` | `vec3(0,1,0)` | The axis specifies the direction of the jewel. | | |
| `aspect` | 1 | The ratio of the height to the diameter of the gem. | | |
| `size` | the length of the axis | The size of the jewel; specifically the distance from the | |
centre to the apex of the jewel. | | |
| `colour`/`color` | `color(255, 255, 255, 255)` | The colour of the jewel. | | |
| `texOrigin` | `vwc2(0,0)` | If using a sprite sheet, this is the lower left corner of the | |
rectangle associated with this gem. | | |
| `texSize` | `vec2(1,1)` | This is the width and height of the rectangle of the | |
texture associated to this gem. | |
--]] | |
function addPyramid(t) | |
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting) | |
local p = t.position or (m.size + 1) | |
p = p + (1-p)%3 | |
local o = t.origin or vec3(0,0,0) | |
local c = t.colour or t.color or color(255, 255, 255, 255) | |
local f = true | |
if t.faceted ~= nil then | |
f = t.faceted | |
end | |
local l,am | |
if rl then | |
l = vec3(0,0,0) | |
am = 1 | |
else | |
l = t.light or vec3(0,0,0) | |
if t.intensity then | |
l = l:normalize()*t.intensity | |
elseif l:lenSqr() > 1 then | |
l = l:normalize() | |
end | |
am = t.ambience or (1 - l:len()) | |
end | |
local as = t.aspect or 1 | |
local to = t.texOrigin or vec2(0,0) | |
local ts = t.texSize or vec2(1,1) | |
local a = t.axes or {} | |
a[1] = t.apex or a[1] or vec3(0,1,0) | |
if t.size then | |
a[1] = a[1]:normalize()*t.size | |
end | |
if not a[2] then | |
local ax,ay,az = math.abs(a[1].x),math.abs(a[1].y),math.abs(a[1].z) | |
if ax < ay and ax < az then | |
a[2] = vec3(0,a[1].z,-a[1].y) | |
elseif ay < az then | |
a[2] = vec3(a[1].z,0,-a[1].x) | |
else | |
a[2] = vec3(a[1].y,-a[1].x,0) | |
end | |
a[3] = a[1]:cross(a[2]) | |
end | |
local la = a[1]:len() | |
for i = 2,3 do | |
a[i] = as*la*a[i]:normalize() | |
end | |
local n = t.sides or 12 | |
if p > m.size - 6*n then | |
m:resize(p + 6*n-1) | |
end | |
local np = __doPyramid(m,p,n,o,a,c,to,ts,f,l,am) | |
if ret then | |
return m,p,np | |
else | |
return m | |
end | |
end | |
--[[ | |
A pyramid is a special case of a suspension. | |
m mesh | |
p position | |
n number of points | |
o origin | |
a apex | |
col colour | |
to texture offset | |
ts texture size | |
f faceted | |
l light vector | |
--]] | |
function __doPyramid(m,p,n,o,a,col,to,ts,f,l,am) | |
local th = 2*math.pi/n | |
local cs = math.cos(th) | |
local sn = math.sin(th) | |
local b,c,d,tb,tc,td,tex,pol | |
tex,pol={},{} | |
c = cs*a[2] + sn*a[3] | |
d = -sn*a[2] + cs*a[3] | |
tc = cs*vec2(ts.x*.25,0) + sn*vec2(0,ts.y*.5) | |
td = -sn*vec2(ts.x*.25,0) + cs*vec2(0,ts.y*.5) | |
for i = 1,n do | |
table.insert(pol,o+c) | |
table.insert(tex,tc) | |
c,d = cs*c + sn*d, -sn*c + cs*d | |
tc,td = cs*tc + sn*td, -sn*tc + cs*td | |
end | |
return __doSuspension(m,p,n,o+a[1]/2,{o+a[1],o},pol,tex,col,to,ts,f,true,l,am) | |
end | |
-- block faces are in binary order: 000, 001, 010, 011 etc | |
local BlockFaces = { | |
{1,2,3,4}, | |
{5,7,6,8}, | |
{1,5,2,6}, | |
{3,4,7,8}, | |
{2,6,4,8}, | |
{1,3,5,7} | |
} | |
local BlockTex = { | |
vec2(0,0),vec2(1/6,0),vec2(0,1),vec2(1/6,1) | |
} | |
--[[ | |
Adds a block to a mesh. | |
| Option | Default | Description | | |
|:-------|:--------|:------------| | |
| `mesh` | new mesh | Mesh to use to add shape to. | | |
| `position` | end of mesh | Position in mesh to add shape at. | | |
| `colour`/`color` | color(255, 255, 255, 255) | Colour or colours to use. Can be a table of | |
colours, one for each vertex of the block. | | |
| `faces` | all | Which faces to render | | |
| `texOrigin` | `vec2(0,0)` | Lower left corner of region on texture. | | |
| `texSize` | `vec2(1,1)` | Width and height of region on texture. | | |
| `singleImage` | `false` | Uses the same image for all sides. | | |
There are a few ways of specifying the dimensions of the "block". | |
`centre`/`center`, `width`, `height`, `depth`, `size`. This defines the "block" by specifying a | |
centre followed by the width, height, and depth of the cube (`size` sets all three). These can | |
be `vec3`s or numbers. If numbers, they correspond to the dimensions of the "block" in the | |
`x`, `y`, and `z` directions respectively. If `vec3`s, then are used to construct the vertices by | |
adding them to the centre so that the edges of the "block" end up parallel to the given | |
vectors. | |
`startCentre`/`startCenter`, `startWidth`, `startHeight`, `endCentre`/`endCenter`, `endWidth`, | |
`endHeight`. This defined the "block" by defining two opposite faces of the cube and then | |
filling in the region in between. The two faces are defined by their centres, widths, and | |
heights. The widths and heights can be numbers or `vec3`s exactly as above. | |
`block`. This is a table of eight vertices defining the block. The vertices are listed in binary | |
order, in that if you picture the vertices of the standard cube of side length `1` with one vertex | |
at the origin, the vertex with coordinates `(a,b,c)` is number a + 2b + 4c + 1 in the table (the | |
`+1` is because lua tables are 1-based). | |
--]] | |
function addBlock(t) | |
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting) | |
local p = t.position or (m.size + 1) | |
p = p + (1-p)%3 | |
local c = t.colour or t.color or color(255, 255, 255, 255) | |
if type(c) == "userdata" then | |
c = {c,c,c,c,c,c,c,c} | |
end | |
local f = t.faces or BlockFaces | |
local to = t.texOrigin or vec2(0,0) | |
local ts = t.texSize or vec2(1,1) | |
local dt = 1 | |
if t.singleImage then | |
dt = 0 | |
ts.x = ts.x * 6 | |
end | |
local l,am | |
if rl then | |
l = vec3(0,0,0) | |
am = 1 | |
else | |
l = t.light or vec3(0,0,0) | |
if t.intensity then | |
l = l:normalize()*t.intensity | |
elseif l:lenSqr() > 1 then | |
l = l:normalize() | |
end | |
am = t.ambience or (1 - l:len()) | |
end | |
local v | |
if t.block then | |
v = t.block | |
elseif t.center or t.centre then | |
local o = t.center or t.centre | |
local w,h,d = t.width or t.size or 1, t.height or t.size or 1, t.depth or t.size or 1 | |
w,h,d=w/2,h/2,d/2 | |
if type(w) == "number" then | |
w = vec3(w,0,0) | |
end | |
if type(h) == "number" then | |
h = vec3(0,h,0) | |
end | |
if type(d) == "number" then | |
d = vec3(0,0,d) | |
end | |
v = { | |
o - w - h - d, | |
o + w - h - d, | |
o - w + h - d, | |
o + w + h - d, | |
o - w - h + d, | |
o + w - h + d, | |
o - w + h + d, | |
o + w + h + d | |
} | |
elseif t.startCentre or t.startCenter then | |
local sc = t.startCentre or t.startCenter | |
local ec = t.endCentre or t.endCenter | |
local sw = t.startWidth | |
local sh = t.startHeight | |
local ew = t.endWidth | |
local eh = t.endHeight | |
if type(sw) == "number" then | |
sw = vec3(sw,0,0) | |
end | |
if type(sh) == "number" then | |
sh = vec3(0,sh,0) | |
end | |
if type(sc) == "number" then | |
sc = vec3(0,0,sc) | |
end | |
if type(ew) == "number" then | |
ew = vec3(ew,0,0) | |
end | |
if type(eh) == "number" then | |
eh = vec3(0,eh,0) | |
end | |
if type(ec) == "number" then | |
ec = vec3(0,0,ec) | |
end | |
v = { | |
sc - sw - sh, | |
sc + sw - sh, | |
sc - sw + sh, | |
sc + sw + sh, | |
ec - ew - eh, | |
ec + ew - eh, | |
ec - ew + eh, | |
ec + ew + eh | |
} | |
else | |
v = { | |
vec3(-1,-1,-1)/2, | |
vec3(1,-1,-1)/2, | |
vec3(-1,1,-1)/2, | |
vec3(1,1,-1)/2, | |
vec3(-1,-1,1)/2, | |
vec3(1,-1,1)/2, | |
vec3(-1,1,1)/2, | |
vec3(1,1,1)/2 | |
} | |
end | |
if p > m.size - 36 then | |
m:resize(p + 35) | |
end | |
local np = __doBlock(m,p,f,v,c,to,ts,dt,l,am) | |
if ret then | |
return m,p,np | |
else | |
return m | |
end | |
end | |
--[[ | |
m mesh | |
p index of first vertex to be used | |
f table of faces of block | |
v table of vertices of block | |
c table of colours of block (per vertex of block) | |
to offset for this shape's segment of the texture | |
ts size of this shape's segment of the texture | |
dt step size for the texture tiling | |
l light vector | |
--]] | |
function __doBlock(m,p,f,v,c,to,ts,dt,l,am) | |
local n,t,tv | |
t = 0 | |
l = l / 2 | |
for k,w in ipairs(f) do | |
n = (v[w[3]] - v[w[1]]):cross(v[w[2]] - v[w[1]]):normalize() | |
for i,u in ipairs({1,2,3,2,3,4}) do | |
m:vertex(p,v[w[u]]) | |
m:color(p,c[w[u]]:mix(color(0,0,0,c[w[u]].a),am+(1-am)*math.max(0,l:dot(n)))) | |
m:normal(p,n) | |
tv = BlockTex[u] + t*vec2(1/6,0) | |
tv.x = tv.x * ts.x | |
tv.y = tv.y * ts.y | |
m:texCoord(p, to + tv) | |
p = p + 1 | |
end | |
t = t + dt | |
end | |
return p | |
end | |
--[[ | |
Adds a sphere to a mesh. | |
| Options | Defaults | Description | | |
|:--------|:---------|:------------| | |
| `mesh` | New mesh | Mesh to add shape to. | | |
| `position` | End of mesh | Position at which to add shape. | | |
| `origin`/`centre`/`center` | `vec3(0,0,0)` | Centre of sphere. | | |
| `axes` | Standard axes | Axes of sphere. | | |
| `size` | `1` | Radius of sphere (relative to axes). | | |
| `colour`/`color` | `color(255, 255, 255, 255)` | Colour of sphere. | | |
| `faceted` | `false` | Whether to render the sphere faceted or smoothed (not yet | |
implemented). | | |
| `number` | `36` | Number of steps to use to render sphere (twice this for longitude. | | |
| `texOrigin` | `vec2(0,0)` | Origin of region in texture to use. | | |
| `texSize` | `vec2(0,0)` | Width and height of region in texture to use.| | |
--]] | |
function addSphere(t) | |
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting) | |
local p = t.position or (m.size + 1) | |
p = p + (1-p)%3 | |
local o = t.origin or t.centre or t.center or vec3(0,0,0) | |
local s = t.size or 1 | |
local c = t.colour or t.color or color(255, 255, 255, 255) | |
local n = t.number or 36 | |
local a = t.axes or {vec3(1,0,0),vec3(0,1,0),vec3(0,0,1)} | |
a[1],a[2],a[3] = s*a[1],s*a[2],s*a[3] | |
local to = t.texOrigin or vec2(0,0) | |
local ts = t.texSize or vec2(1,1) | |
local f = false | |
if t.faceted ~= nil then | |
f = t.faceted | |
end | |
local l,am | |
if rl then | |
l = vec3(0,0,0) | |
am = 1 | |
else | |
l = t.light or vec3(0,0,0) | |
if t.intensity then | |
l = l:normalize()*t.intensity | |
elseif l:lenSqr() > 1 then | |
l = l:normalize() | |
end | |
am = t.ambience or (1 - l:len()) | |
end | |
if p > m.size - 12*n*(n-1) then | |
m:resize(p+12*n*(n-1)-1) | |
end | |
local step = math.pi/n | |
local np = __doSphere(m,p,o,a,0,step,n,0,step,2*n,c,f,to,ts,l,am) | |
if ret then | |
return m,p,np | |
else | |
return m | |
end | |
end | |
--[[ | |
Adds a segment of a sphere to a mesh. | |
| Options | Defaults | Description | | |
|:--------|:---------|:------------| | |
| `mesh` | New mesh | Mesh to add shape to. | | |
| `position` | End of mesh | Position at which to add shape. | | |
| `origin`/`centre`/`center` | `vec3(0,0,0)` | Centre of sphere. | | |
| `axes` | Standard axes | Axes of sphere. | | |
| `size` | `1` | Radius of sphere (relative to axes). | | |
| `colour`/`color` | `color(255, 255, 255, 255)` | Colour of sphere. | | |
| `faceted` | `false` | Whether to render the sphere faceted or smoothed (not yet | |
implemented). | | |
| `number` | `36` | Number of steps to use to render sphere (twice this for longitude. | | |
| `solid` | `true` | Whether to make the sphere solid by filling in the internal sides. | | |
| `texOrigin` | `vec2(0,0)` | Origin of region in texture to use. | | |
| `texSize` | `vec2(0,0)` | Width and height of region in texture to use.| | |
Specifying the segment can be done in a variety of ways. | |
`startLatitude`, `endLatitude`, `deltaLatitude`, `startLongitude`, `endLongitude`, | |
`deltaLongitude` specify the latitude and longitude for the segment relative to given axes | |
(only two of the three pieces of information for each need to be given). | |
`incoming` and `outgoing` define directions that the ends of the segment will point towards. | |
--]] | |
function addSphereSegment(t) | |
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting) | |
local p = t.position or (m.size + 1) | |
p = p + (1-p)%3 | |
local ip = p | |
local o = t.origin or t.centre or t.center or vec3(0,0,0) | |
local s = t.size or 1 | |
local c = t.colour or t.color or color(255, 255, 255, 255) | |
local n = t.number or 36 | |
local solid = true | |
if t.solid ~= nil then | |
solid = t.solid | |
end | |
local a,st,dt,et,sp,dp,ep | |
if t.incoming then | |
a = {} | |
a[3] = t.incoming:cross(t.outgoing) | |
if a[3]:lenSqr() == 0 then | |
if t.axes then | |
a[3] = t.axes[3] - t.axes[3]:dot(t.incoming)*t.incoming/t.incoming:lenSqr() | |
end | |
if a[3]:lenSqr() == 0 then | |
a[3] = __orthogonalTo(t.incoming) | |
end | |
end | |
a[3] = a[3]:normalize() | |
a[2] = t.incoming:normalize() | |
a[1] = a[2]:cross(a[3]) | |
local w = a[3]:cross(t.outgoing):normalize() | |
dp = math.acos(a[1]:dot(w)) | |
sp = 0 | |
ep = dp | |
st = 0 | |
dt = math.pi | |
et = math.pi | |
else | |
a = t.axes or {vec3(1,0,0),vec3(0,1,0),vec3(0,0,1)} | |
a[1],a[2],a[3] = s*a[1],s*a[2],s*a[3] | |
sp,ep,dp = __threeFromTwo(t.startLongitude,t.endLongitude,t.deltaLongitude,0,360,360) | |
st,et,dt = __threeFromTwo(t.startLatitude,t.endLatitude,t.deltaLatitude,0,180,180) | |
sp = sp/180*math.pi | |
dp = dp/180*math.pi | |
ep = ep/180*math.pi | |
st = st/180*math.pi | |
dt = dt/180*math.pi | |
et = et/180*math.pi | |
end | |
local step = math.pi/n | |
local nt = math.ceil(dt/step) | |
local np = math.ceil(dp/step) | |
dt = dt / nt | |
dp = dp / np | |
local to = t.texOrigin or vec2(0,0) | |
local ts = t.texSize or vec2(1,1) | |
local f = false | |
if t.faceted ~= nil then | |
f = t.faceted | |
end | |
local l,am | |
if rl then | |
l = vec3(0,0,0) | |
am = 1 | |
else | |
l = t.light or vec3(0,0,0) | |
if t.intensity then | |
l = l:normalize()*t.intensity | |
elseif l:lenSqr() > 1 then | |
l = l:normalize() | |
end | |
am = t.ambience or (1 - l:len()) | |
end | |
local size = 6*nt*np | |
if st == 0 then | |
size = size - 3*np | |
end | |
if et >= math.pi then | |
size = size - 3*np | |
end | |
if solid then | |
size = size + 6*(nt+3) | |
local ss = 3 | |
if st ~= 0 then | |
size = size + 3*np | |
ss = ss + 1 | |
end | |
if et < math.pi then | |
size = size + 3*np | |
ss = ss + 1 | |
end | |
ts.x = ts.x/ss | |
end | |
if p > m.size - size then | |
m:resize(p-1+size) | |
end | |
p = __doSphere(m,p,o,a,st,dt,nt,sp,dp,np,c,f,to,ts,l,am) | |
if solid then | |
to.x = to.x + ts.x | |
local intl = o + math.sin(st+nt*dt/2)*math.cos(sp+np*dp/2)*a[1] + math.sin(st+nt*dt/2) | |
*math.sin(sp+dp*np/2)*a[2] + math.cos(st+nt*dt/2)*a[3] | |
local v,tex,at = {},{},1 | |
local tl,tu = math.cos(st), math.cos(et) - math.cos(st) | |
if st ~= 0 then | |
table.insert(v,o + math.cos(st)*a[3]) | |
table.insert(tex,vec2(ts.x,0)) | |
at = at + 1 | |
end | |
for k=0,nt do | |
table.insert(v,o+math.sin(st+k*dt)*math.cos(sp)*a[1] + math.sin(st+k*dt)*math.sin(sp) | |
*a[2] + math.cos(st+k*dt)*a[3]) | |
table.insert(tex,vec2(ts.x*(1-math.sin(st+k*dt)),ts.y*(math.cos(st+k*dt) - tl)/tu)) | |
end | |
if et < math.pi then | |
table.insert(v,o + math.cos(et)*a[3]) | |
table.insert(tex,ts) | |
at = at + 1 | |
end | |
p = __doPoly(m,p,nt+at,intl,v,tex,c,to,ts,f,true,l,am) | |
to.x = to.x + ts.x | |
v,tex,at = {},{},1 | |
if st ~= 0 then | |
table.insert(v,o + math.cos(st)*a[3]) | |
table.insert(tex,vec2(0,0)) | |
at = at + 1 | |
end | |
for k=0,nt do | |
table.insert(v,o+math.sin(st+k*dt)*math.cos(ep)*a[1] + math.sin(st+k*dt)*math.sin(ep) | |
*a[2] + math.cos(st+k*dt)*a[3]) | |
table.insert(tex,vec2(ts.x*math.sin(st+k*dt),ts.y*(math.cos(st+k*dt) - tl)/tu)) | |
end | |
if et < math.pi then | |
table.insert(v,o + math.cos(et)*a[3]) | |
table.insert(tex,vec2(0,ts.y)) | |
at = at + 1 | |
end | |
p = __doPoly(m,p,nt+at,intl,v,tex,c,to,ts,f,true,l,am) | |
to = to + ts/2 | |
if st ~= 0 then | |
to.x = to.x + ts.x | |
v,tex = {},{} | |
for k=0,np do | |
table.insert(v,o+math.sin(st)*math.cos(sp+k*dp)*a[1] + | |
math.sin(st)*math.sin(sp+k*dp)*a[2] + math.cos(st)*a[3]) | |
table.insert(tex,vec2(ts.x*math.sin(sp+k*dp)/2,ts.y*math.cos(sp+k*dp)/2)) | |
end | |
p = __doCone(m,p,np+1,intl,o + math.cos(st)*a[3],v,tex,c,to,ts,f,false,l,am) | |
end | |
if et < math.pi then | |
to.x = to.x + ts.x | |
v,tex = {},{} | |
for k=0,np do | |
table.insert(v,o+math.sin(et)*math.cos(sp+k*dp)*a[1] + | |
math.sin(et)*math.sin(sp+k*dp)*a[2] + math.cos(et)*a[3]) | |
table.insert(tex,vec2(ts.x*math.sin(sp+k*dp)/2,ts.y*math.cos(sp+k*dp)/2)) | |
end | |
p = __doCone(m,p,np+1,intl,o + math.cos(et)*a[3],v,tex,c,to,ts,f,false,l,am) | |
end | |
end | |
if ret then | |
return m,ip,p | |
else | |
return m | |
end | |
end | |
--[[ | |
Adds a sphere or segment of the surface of a sphere. | |
m mesh | |
p position of first vertex | |
o origin | |
a axes (table of vec3s) | |
st start angle for theta | |
dt delta angle for theta | |
nt number of steps for theta | |
sp start angle for phi | |
dp delta angle for phi | |
np number of steps for phi | |
c colour | |
f faceted or smooth | |
to offset for this shape's segment of the texture | |
ts size of this shape's segment of the texture | |
l light vector | |
--]] | |
function __doSphere(m,p,o,a,st,dt,nt,sp,dp,np,c,f,to,ts,ll,am) | |
local theta,ptheta,phi,pphi,ver,et,ep,tx,l,tex,stt,ett,ln,nml | |
et = nt*dt/ts.y | |
ep = np*dp/ts.x | |
if st == 0 then | |
stt = 2 | |
else | |
stt = 1 | |
end | |
if st + nt*dt >= math.pi then | |
ett = nt-1 | |
else | |
ett = nt | |
end | |
for i = stt,ett do | |
theta = st + i*dt | |
ptheta = st + (i-1)*dt | |
for j=1,np do | |
phi = sp + j*dp | |
pphi = sp + (j-1)*dp | |
ver = {} | |
tex = {} | |
for k,v in ipairs({ | |
{ptheta,pphi}, | |
{ptheta,phi}, | |
{theta,phi}, | |
{theta,phi}, | |
{ptheta,pphi}, | |
{theta,pphi} | |
}) do | |
table.insert(ver,math.sin(v[1])*math.cos(v[2])*a[1] + | |
math.sin(v[1])*math.sin(v[2])*a[2] + math.cos(v[1])*a[3]) | |
table.insert(tex,to + vec2((v[2]-sp)/ep,(v[1]-st)/et)) | |
end | |
for k = 1,6 do | |
m:vertex(p,o + ver[k]) | |
if f then | |
l = math.floor((k-1)/3) | |
nml = (-1)^l*(ver[3*l+3] - ver[3*l+1]):cross(ver[3*l+2] - ver[3*l+1]):normalize() | |
else | |
nml = ver[k]:normalize() | |
end | |
m:color(p,c:mix(color(0,0,0,255),am+(1-am)*math.max(0,ll:dot(nml)))) | |
m:normal(p,nml) | |
m:texCoord(p,tex[k]) | |
p = p + 1 | |
end | |
end | |
end | |
local ends = {} | |
if st == 0 then | |
table.insert(ends,0) | |
end | |
if st + nt*dt >= math.pi then | |
table.insert(ends,1) | |
end | |
et = nt*dt | |
ep = np*dp | |
for _,i in ipairs(ends) do | |
for j=1,np do | |
phi = sp + j*dp | |
pphi = sp + (j-1)*dp | |
ver = {} | |
tex = {} | |
for k,v in ipairs({ | |
{dt,pphi}, | |
{dt,phi}, | |
{0,phi} | |
}) do | |
table.insert(ver,math.sin(v[1])*math.cos(v[2])*a[1] + | |
math.sin(v[1])*math.sin(v[2])*a[2] + (-1)^i*math.cos(v[1])*a[3]) | |
tx = vec2((v[2]-sp)/ep,i + (1-2*i)*(v[1]-st)/et) | |
tx.x = tx.x * ts.x | |
tx.y = tx.y * ts.y | |
table.insert(tex,to + tx) | |
end | |
for k=1,3 do | |
m:vertex(p,o + ver[k]) | |
if f then | |
nml = (-1)^i*(ver[2]-ver[1]):cross(ver[3]-ver[1]):normalize() | |
else | |
nml = ver[k]:normalize() | |
end | |
m:normal(p,nml) | |
m:color(p,c:mix(color(0,0,0,255),am+(1-am)*math.max(0,ll:dot(nml)))) | |
m:texCoord(p,tex[k]) | |
p = p + 1 | |
end | |
end | |
end | |
return p | |
end | |
--[[ | |
These make the above available as methods on a mesh. | |
--]] | |
local mt = getmetatable(mesh()) | |
local __addShape = function(m,f,t) | |
t = t or {} | |
local nt = {} | |
for k,v in pairs(t) do | |
nt[k] = v | |
end | |
nt.mesh = m | |
return f(nt) | |
end | |
mt.addJewel = function(m,t) | |
return __addShape(m,addJewel,t) | |
end | |
mt.addPyramid = function(m,t) | |
return __addShape(m,addPyramid,t) | |
end | |
mt.addBlock = function(m,t) | |
return __addShape(m,addBlock,t) | |
end | |
mt.addCylinder = function(m,t) | |
return __addShape(m,addCylinder,t) | |
end | |
mt.addSphere = function(m,t) | |
return __addShape(m,addSphere,t) | |
end | |
mt.addSphereSegment = function(m,t) | |
return __addShape(m,addSphereSegment,t) | |
end | |
--[[ | |
Adds a triangle to a mesh, with specific vertices, colours, normals, and texture coordinates. | |
--]] | |
function __addTriangle(m,p,a,b,c,d,e,f,g,h,i,j,k,l) | |
m:vertex(p,a) | |
m:color(p,d) | |
m:normal(p,g) | |
m:texCoord(p,j) | |
p = p + 1 | |
m:vertex(p,b) | |
m:color(p,e) | |
m:normal(p,h) | |
m:texCoord(p,k) | |
p = p + 1 | |
m:vertex(p,c) | |
m:color(p,f) | |
m:normal(p,i) | |
m:texCoord(p,l) | |
end | |
--[[ | |
Returns three things, u,v,w, with the property that u + w = v. The input can be any number of | |
the three together with three defaults to be used if not enough information is given (so if any | |
two of the first three are given then that is enough information to determine the third). | |
--]] | |
function __threeFromTwo(a,b,c,d,e,f) | |
local u,v,w = a or d or 0, b or e or 1, c or f or 1 | |
if not a then | |
u = v - w | |
end | |
if not b then | |
v = u + w | |
end | |
if not c then | |
w = v - u | |
end | |
v = u + w | |
return u,v,w | |
end | |
--[[ | |
Returns a vector orthogonal to the given `vec3`. | |
--]] | |
function __orthogonalTo(v) | |
local a,b,c = math.abs(v.x), math.abs(v.y), math.abs(v.z) | |
if a < b and a < c then | |
return vec3(0,v.z,-v.y) | |
end | |
if b < c then | |
return vec3(-v.z,0,v.x) | |
end | |
return vec3(v.y,-v.x,0) | |
end | |
--[[ | |
Initialise a mesh | |
--]] | |
function __initmesh(m,l,a,i,t,b) | |
local r,rl | |
if m then | |
r = true | |
else | |
r,m = false,mesh() | |
if l and a and not b then | |
rl = true | |
m.shader = lighting() | |
if i then | |
l = l:normalize()*i | |
elseif l:lenSqr() > 1 then | |
l = l:normalize() | |
end | |
m.shader.light = l | |
m.shader.ambient = a | |
if t then | |
m.shader.useTexture = 1 | |
else | |
m.shader.useTexture = 0 | |
end | |
end | |
if t then | |
if type(t) == "string" then | |
m.texture = readImage(t) | |
else | |
m.texture = t | |
end | |
end | |
end | |
return m,r,rl | |
end | |
--[[ | |
A basic lighting shader with a texture. | |
--]] | |
function lighting() | |
return shader([[ | |
// | |
// A basic vertex shader | |
// | |
//This is the current model * view * projection matrix | |
// Codea sets it automatically | |
uniform mat4 modelViewProjection; | |
uniform mat4 invModel; | |
//This is the current mesh vertex position, color and tex coord | |
// Set automatically | |
attribute vec4 position; | |
attribute vec4 color; | |
attribute vec2 texCoord; | |
attribute vec3 normal; | |
//This is an output variable that will be passed to the fragment shader | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
varying highp vec3 vNormal; | |
void main() | |
{ | |
//Pass the mesh color to the fragment shader | |
vColor = color; | |
vTexCoord = texCoord; | |
highp vec4 n = invModel * vec4(normal,0.); | |
vNormal = n.xyz; | |
//Multiply the vertex position by our combined transform | |
gl_Position = modelViewProjection * position; | |
} | |
]],[[ | |
// | |
// A basic fragment shader | |
// | |
//Default precision qualifier | |
precision highp float; | |
//This represents the current texture on the mesh | |
uniform lowp sampler2D texture; | |
uniform highp vec3 light; | |
uniform lowp float ambient; | |
uniform lowp float useTexture; | |
//The interpolated vertex color for this fragment | |
varying lowp vec4 vColor; | |
//The interpolated texture coordinate for this fragment | |
varying highp vec2 vTexCoord; | |
varying highp vec3 vNormal; | |
void main() | |
{ | |
//Sample the texture at the interpolated coordinate | |
lowp vec4 col = vColor;//vec4(1.,0.,0.,1.); | |
if (useTexture == 1.) { | |
col *= texture2D( texture, vTexCoord ); | |
} | |
lowp float c = ambient + (1.-ambient) * max(0.,dot(light, normalize(vNormal))); | |
col.xyz *= c; | |
//Set the output color to the texture color | |
gl_FragColor = col; | |
} | |
]] | |
) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment