IsoSphere

Was inspired by JVMs tutorial with spheres and wrote some code for creating a isosphere with a light shader. Used this description to divide the sphere http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html

Have to figure out how to texture it properly now. :slight_smile:


--# Main
-- IsoSphere

-- Use this function to perform your initial setup
function setup()
    parameter.integer("Divisions", 0,5,3, createSphere)
    R = 0
end

function createSphere()
    is = IsoSphere(Divisions)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    camera(0,0,15, 0,0,0)
    perspective(45)
    rotate(R, 0,1,0)
    is:draw()            
end

function touched(touch)
    if touch.state == MOVING then
        R = R + touch.deltaX
    end
end

--# IsoSphere
-- Based on http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html

IsoSphere = class()

function IsoSphere:init(divisions) 
    local t = (1.0 + math.sqrt(5.0)) / 2.0;
    local vs = {
        vec3(-1,t,0),vec3(1,t,0),vec3(-1,-t,0),vec3(1,-t,0),
        vec3(0,-1,t),vec3(0,1,t),vec3(0,-1,-t),vec3(0,1,-t),
        vec3(t,0,-1),vec3(t,0,1),vec3(-t,0,-1),vec3(-t,0,1)       
    }
    local ts = {
        vs[1],vs[12],vs[6], -- 5 faces around vs[1]
        vs[1],vs[6],vs[2],
        vs[1],vs[2],vs[8],
        vs[1],vs[8],vs[11],
        vs[1],vs[11],vs[12],

        vs[2],vs[6],vs[10], -- 5 adjacent faces
        vs[6],vs[12],vs[5],
        vs[12],vs[11],vs[3],
        vs[11],vs[8],vs[7],
        vs[8],vs[2],vs[9],
        
        vs[4],vs[10],vs[5], -- 5 faces around vs[4]
        vs[4],vs[5],vs[3],
        vs[4],vs[3],vs[7],
        vs[4],vs[7],vs[9],
        vs[4],vs[9],vs[10],

        vs[5],vs[10],vs[6], -- 5 adjacent faces
        vs[3],vs[5],vs[12],
        vs[7],vs[3],vs[11],
        vs[9],vs[7],vs[8],
        vs[10],vs[9],vs[2]                                
    }
    
    ts = self:divide(divisions or 0, ts)
    
    self.m = mesh()    
    self.m.shader = lightShader
    self.m.vertices = ts
    self.m:setColors(255,255,255,255)
    local nor = self.m:buffer("normal")
    nor:resize(#ts)
    for i,v in ipairs(ts) do
        nor[i] = v:normalize()
    end
end

function IsoSphere:divide(divisions, ts)
    local mp = function (a, b) -- mid point
        return ((a+b)/2):normalize()*2
    end
    
    for i=1,divisions do
        local vs = {}
        for j=1,#ts,3 do
            local v1,v2,v3 = ts[j],ts[j+1],ts[j+2]
            local a,b,c = mp(v1,v2),mp(v2,v3),mp(v3,v1)
            local nvs = {v1,a,c, v2,b,a, v3,c,b, a,b,c}
            for k,v in ipairs(nvs) do
                table.insert(vs,v)
            end
        end
        ts = vs
    end
    return ts
end

function IsoSphere:draw()
    self.m.shader.light = vec3(-Gravity.x,-Gravity.y,0)
    self.m:draw()
end
--# LightShader
lightShader = shader()
 
lightShader.vertexProgram = [[
uniform mat4 modelViewProjection;
uniform vec3 light;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
attribute vec3 normal;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
 
void main()
{
    vColor = color;
    //vTexCoord = vec2(texCoord.x,1.-texCoord.y); 
    lowp vec4 nor = modelViewProjection * vec4(normal,0);
    lowp float vShade = (dot(nor.xyz,light)+0.2)/1.2;
    if (vShade >1.0) {vShade=1.0;}
    if (vShade <0.1) {vShade=0.1;}
    vColor.rgb = vColor.rgb * vShade;
    gl_Position = modelViewProjection * position;
}
]]
 
lightShader.fragmentProgram = [[
varying lowp vec4 vColor;
 
void main()
{
    gl_FragColor = vColor;
}
]]

I implemented isoSphere based on the same blog in http://twolivesleft.com/Codea/Talk/discussion/2373/shadow-mapping

The one extra thing I had to do, which Iā€™m not sure if yours does was ensure I consistently wound my triangles to get their orientation right as I use

if (!gl_FrontFacing) discard;

in my shader.

Nice! Our code it quite similar. :slight_smile:

I create normals, so I guess they can be used for discarding. My code for texturing got a bit messy though, might be a better way to find the corner cases, where the uv coordinates wrap.

Update: cleaned it up a bit, using one triangle vertex as base to calculate uv coordinates for the other two.

    local uvs = {}
    local calcUV = function(v, v2, v3)
        local a = vec2(1,0):angleBetween(vec2(-v.z,-v.x))
        local b = math.asin(-v.y)
        local uv1 = vec2(.5 + a/(math.pi*2), .5 - b/math.pi)
        local v1 = vec2(-v.z,-v.x)
        local a = v1:angleBetween(vec2(-v2.z,-v2.x))
        local uv2 = vec2(uv1.x + a/(math.pi*2),
                    uv1.y - (math.asin(-v2.y) - b)/math.pi)
        local a = v1:angleBetween(vec2(-v3.z,-v3.x))
        local uv3 = vec2(uv1.x + a/(math.pi*2),
                    uv1.y - (math.asin(-v3.y) - b)/math.pi)
        return uv1,uv2,uv3                    
    end
    for i=1,#ts,3 do
        local v1,v2 = ts[i]:normalize(),ts[i+1]:normalize()
        local v3 = ts[i+2]:normalize()
        local uv1,uv2,uv3 = calcUV(v1,v2,v3)
        nor[i],nor[i+1],nor[i+2] = v1,v2,v3
        table.insert(uvs, uv1)
        table.insert(uvs, uv2)
        table.insert(uvs, uv3)
    end
    self.m.texCoords = uvs