3D Failure


--# Icosphere
Icosphere = class()

function Icosphere:init()
    -- Initial stuff

    self.faces = {}
    self.vertices = {}
    self.mesh = mesh()
    self.mesh.texture = "Planet Cute:Grass Block"
    self.iterationLevel = level
    self:initVertices()
    self:initFaces()
    
    -- Subdivide
    for i = 1, self.iterationLevel do
        self:subdivideFaces()
    end
    
    -- Fix mesh verts
    local ti = table.insert
    local cl = color
    local r = math.random
    local ve = {}
    local c = {}
    local tx = {}
    local main = cl(r(0,255),r(0,255),r(0,255))
    for i,v in ipairs(self.faces) do
        -- Change color slightly from original
        clr = cl(main.r+r(-50,50),main.g+r(-50,50),main.b+r(-50,50))
        
        -- Vertices
        ti(ve,v[1])
        ti(ve,v[2])
        ti(ve,v[3])
        
        -- Colors
        ti(c,clr)
        ti(c,clr)
        ti(c,clr)
        
        -- Texture (disabled for now)
        --ti(tx,vec2(0,0))
        --ti(tx,vec2(1,0))
        --ti(tx,vec2(0.5,0.5))
    end
    self.mesh.vertices = ve
    self.mesh:setColors(color(60,150,40))
    self.mesh.colors = c
    -- self.mesh.texCoords = tx -- Uncomment this line to show an image on each of the faces
end

function Icosphere:initVertices()
    local t = (1 + math.sqrt(5)) / 2
    local p = vec3
    local function v(...)
        table.insert(self.vertices,p(...):normalize())
    end
    
    v(-1, t, 0)
    v( 1, t, 0)
    v(-1,-t, 0)
    v( 1,-t, 0)
    
    v( 0,-1, t)
    v( 0, 1, t)
    v( 0,-1,-t)
    v( 0, 1,-t)
    
    v( t, 0,-1)
    v( t, 0, 1)
    v(-t, 0,-1)
    v(-t, 0, 1)
end

function Icosphere:subdivideFaces()
    local faces2 = {}
    
    local function gm(v,v2)
        return ((v+v2)/2):normalize()
    end
    
    local ti = table.insert
    
    for i,tri in ipairs(self.faces) do
        local a = gm(tri[1],tri[2])
        local b = gm(tri[2],tri[3])
        local c = gm(tri[3],tri[1])
        ti(faces2,{tri[1],a,c})
        ti(faces2,{tri[2],b,a})
        ti(faces2,{tri[3],c,b})
        ti(faces2,{a,b,c})
    end
    self.faces = faces2
end

function Icosphere:initFaces()
    local function f(...)
        table.insert(self.faces,self:triangleIndices(...))
    end
    
    f(1,12,6)
    f(1,6,2)
    f(1,2,8)
    f(1,8,11)
    f(1,11,12)
    
    f(2,6,10)
    f(6,12,5)
    f(12,11,3)
    f(11,8,7)
    f(8,2,9)
    
    f(4,10,5)
    f(4,5,3)
    f(4,3,7)
    f(4,7,9)
    f(4,9,10)
    
    f(5,10,6)
    f(3,5,12)
    f(7,3,11)
    f(9,7,8)
    f(10,9,2)
end

function Icosphere:triangleIndices(i1,i2,i3)
    return {
        self.vertices[i1],
        self.vertices[i2],
        self.vertices[i3]
    }
end

function Icosphere:draw()
    pushMatrix()
    scale(Zoom)
    self.mesh:draw()
    popMatrix()
end

--# Main
-- 3D Sphere

-- Use this function to perform your initial setup
function setup()
    print("Hello Spheres!")
    level = 0
    i = Icosphere()
    parameter.integer("level",0,10,1,function() 
        local pstr = string.format("Creating %d faces...",20*4^level)
        if level > 4 then
            pstr = pstr.." (may take a while)"
        end
        print(pstr)
        i:init() -- init again with new level
        collectgarbage()
    end)
    parameter.integer("Zoom",1,40)
    slide = vec2(0,0)
    camX, camY, camZ = -50,-50,-50
    rotMatrix = matrix()
end

-- This function gets called once every frame
function draw()
    -- This sets a light background color 
    background(226, 226, 235, 255)

    -- Do your drawing here
    perspective(45)
    camera(camX,camY,camZ,0,0,0,1,0,0)
    pushMatrix()
    applyMatrix(rotMatrix)
    i:draw()
    popMatrix()
    if slide:len()>0 then
        rotMatrix = rotMatrix:rotate(slide:len(),slide.x,slide.y,0)
    end
    slide = slide * 0.9
end

function touched(touch)
    slide.y = touch.deltaY
    slide.x = touch.deltaX
end

-- Useless functions (did not work for rotation)

function rotateZ(x,y,z,r)
    local l = math.sqrt(x^2+y^2)
    local nx = x*math.cos(r) - y*math.sin(r)
    local ny = y*math.cos(r) + x*math.sin(r)
    
    return nx,ny,z
end

function rotateY(x,y,z,r)
    local l = math.sqrt(x^2+y^2)
    local nz = z*math.cos(r) - x*math.sin(r)
    local nx = x*math.cos(r) + z*math.sin(r)
    
    return nx,y,nz
end

function rotateX(x,y,z,r)
    local l = math.sqrt(x^2+y^2)
    local ny = y*math.cos(r) - z*math.sin(r)
    local nz = z*math.cos(r) + y*math.sin(r)
    
    return x,ny,nz
end

Unfortunately, rotation in 3D is not as easy as it seems. Neither is lighting. I tried shaders, but everything turns out black or invisible (because my understanding of shaders is limited). I wanted to make a globe from which points could be selected, for level selection and other purposes.
Final questions:

  • How can I make touch rotation easier (for the user)?
  • Is there a simple shader to render my sphere mesh as a wireframe?
  • Are there any good 3d lighting tutorials out there (possibly for Codea)?

Thanks in advance!

@em2 Maybe when Codea Craft is released, 3D will be a lot easier for what you want to do. As for rotation, see the reference for rotate(angle,x,y,z) that allows you to rotate the object some degrees in the x or y or z axis or a combination of them. Nice example, I crash at about level 8 on my iPad Air.

EDIT: On my iPad Pro, it crashes at level 10.

@em2 Here’s an example that I probable posted in the past that shows the use of rotate for a sphere.

function setup()
    parameter.number("view",1,10,3)
    parameter.integer("level",6,200,12,setup1)
    parameter.number("AngleX",0,360,90)
    parameter.number("AngleY",0,360,140) 
    parameter.number("AngleZ",0,360,90)
end

function setup1()    
    tab={}
    M,N=level,level
    for n=0,N do
        tab[n]={}
        for m=0,M do
            x=10 * math.sin(math.pi * m/M) * math.cos(2*math.pi * n/N)
            y=10 * math.sin(math.pi * m/M) * math.sin(2*math.pi * n/N)
            z=10 * math.cos(math.pi * m/M)
            tab[n][m]=vec3(x,y,z)
        end
    end
    sph={}
    for n=0,N-1 do
        for m=0,M-1 do
            table.insert(sph,tab[n][m])
            table.insert(sph,tab[n][m+1])
            table.insert(sph,tab[n+1][m+1])  
            table.insert(sph,tab[n][m])
            table.insert(sph,tab[n+1][m])
            table.insert(sph,tab[n+1][m+1])
        end
    end
    cols={}
    for z=1,#sph,6 do
        col=vec4(math.random(),math.random(),math.random(),1)
        for q=1,6 do
            table.insert(cols,col)
        end
    end
    sphere=mesh()
    sphere.vertices=sph
    sphere.colors=cols      
end

function draw()  
    background(40, 40, 50)
    perspective(view,WIDTH/HEIGHT)
    camera(2000,0,0,0,0,0,0,1,0)
    rotate(AngleX,1,0,0)
    rotate(AngleY,0,1,0)
    rotate(AngleZ,0,0,1)
    sphere:draw()
end

@em2 it’s true that Codea Craft is about to revolutionise 3D in Codea.

I’m not sure whether Craft includes a wireframe shader. My trench run game from a couple of years’ back has a wireframe mode. The installer here should still work: https://codea.io/talk/discussion/comment/65766/#Comment_65766

If you can’t wait for Craft (and there’s no harm in learning how 3D works under the hood) @Ignatz put together some great ebooks on shaders and lighting in Codea, find them at coolcodea.wordpress.com

Craft seems like it’ll be awesome, but I don’t think I’ll have access to it’s features as I only have Codea Scratchpad. For some reason whenever I try buying an app on the Appstore, I have to answer security questions that I don’t even remember setting up :\ so I could never buy Codea. Maybe I can call Apple or something. I’m not really sure. I’m at the point where I’d do almost anything for the full version of Codea