Craft sphere texture

@dave1707 All my writings on Codea are listed on this section of my website. Haven’t written anything for a while, but am happy to take requests …

@LoopSpace Thanks for the link. I have it bookmarked and will look thru it later.

Here’s my latest texture code. I don’t like it because it’s sloppy and has hard coded values. This works for both flat (triangles) and rounded (smooth) icospheres. It handles the bad triangles that were showing up in the previous rounded icospheres. The higher the level, the better the texture looks. It will do flat and rounded level 5 icospheres in less than a half second on my iPad Air, doing both the outside and inside texture. I’m starting to understand the problems with texturing the icospheres and hopefully in future versions, I’ll be able to cut the size of the code and remove the hard coded values. I changed the texture map from my other versions just to give a better view of the texturing. Also, the lower level icospheres don’t texture as smooth at the poles as I want and maybe I’ll get those fixed too.

Code deleted, see updated code below.

@dave1707 - found a funny!! Set the size of the sphere to 800 and run, You are inside the sphere and there is a smaller untextured sphere inside coloured grey. The inside sphere responds to the two finger shrink/enlarge in the opposite way to the big sphere that you are inside. Intriguing, is the inner sphere the sphere you added for internal viewing? it looks like the second sphere may not be necessary - just the inversion of the normals as described by @LoopSpace.

@Bri_G That has something to do with the limit of the camera. If you add
v.camera.farPlane=3000 after v=scene.camera:… and before createSphere() then you don’t get it. Without the farPlane, then you’re looking into dead space.

@dave1707 - thinking about it, it makes sense, if you build a reverse sphere inside another, as part of that sphere, and you increase the size of the outer sphere; then the inner sphere will move in the opposite direction ie towards the centre and hence shrink.

@Bri_G I’m not building a reverse sphere, all I’m doing is allowing you to see the texture of the original sphere from the inside. Depending on the direction that you define the 3 points of the triangle defines which direction you can view the texture from. So the indices table has the values winding in one direction and I add the table to itself reading the table in the reverse order. So the indices table has the points winding in both directions which allows you to see the texture from both sides.

@dave1707 - your right but isn’t that just hiding the other sphere, took it down to a setting of 1440 for the farPlane and still there. At 1500 it’s gone. Just feel it’s more unnecessary work for the graphic processors than needed, and curious to know what it is.

@Bri_G There isn’t another sphere, just the one I create. What you’re seeing is a hole in the viewing area because of the way the camera works. As you get closer to the hole, it gets bigger and looks like you’re looking at a black sphere. @Simeon or @John will have to explain it because they’re the ones who wrote Codea. All I know is the farPlane fixes it because it allows the camera to see farther away.

@Bri_G Here’s my latest texture code. I think I have all the kinks worked out for all levels both triangular and smooth. It’s a lot simpler than what I had before. With my other code I didn’t understand everything that was happening, but as I wrote more and more versions, I started to get an understanding of what was going on. Even though the code is smaller, it’s a little slower. Here are approximate times in seconds on my iPad Air for all levels and shapes.

EDIT: I think this version takes care of the poles for all levels except 0 which doesn’t have a pole but a flat surface.

EDIT: I made changes to the code below to increase the speed. On my iPad Air, the level 5 smooth icosphere that took .662 now takes .58 seconds. Not much of an increase, but an increase. On my iPad Pro, it takes .25 seconds.

Level     Triangular  Smooth
0           .088       .090
1           .088       .088
2           .096       .094
3           .122       .123
4           .210       .230
5           .591       .662 
--displayMode(FULLSCREEN)

-- created by dave1707

function setup()  
    -- create test texture
    myImg=image(1024,512)
    setContext(myImg)
    background(170, 198, 223, 255)
    fill(255,0,0)
    ellipse(250,256,200,100)
    fill(0,255,0)
    ellipse(900,256,100,200)
    fill(255,255,0)
    rect(512,320,256,128)
    stroke(255,0,0)
    strokeWidth(8)
    line(0,0,1024,512)
    line(0,512,1024,0)
    strokeWidth(4)
    stroke(255,255,0)
    line(4,0,4,512)
    line(1020,0,1020,512)
    line(0,508,1024,508)
    line(0,4,1024,4)
    fill(0, 19, 255, 255)
    fontSize(25)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde",512,490)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde",512,22)
    fontSize(40)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.8)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.65)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.5)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.35)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.2)
    stroke(0)
    strokeWidth(3)
    for z=0,1024,64 do
        line(z,0,z,512)
    end
    for z=0,512,32 do
        line(0,z,1024,z)
    end
    setContext() 
    -- end test texture  

    level=5     --  0 thru 5
    flat=false  -- true (triangles) or false (rounded)
    insideView=true -- show inside view of sphere

    assert(craft, "Please include Craft as a dependency")
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
    scene = craft.scene()
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 350, 0, 600)
    createSphere()
end

function draw()
    update(DeltaTime)
    scene:draw()
    sprite(myImg,WIDTH*.85,100,300)
end

function update(dt)
    scene:update(dt)
    sph.rotation = quat.eulerAngles(-90,0,0)
end

function createSphere()
    sph=scene:entity()
    sph.position=vec3(0,0,0)
    sph.model = craft.model.icosphere(100,level,flat)
    sph.material = craft.material("Materials:Standard")    
    sph.material.map=myImg 
       
    local seam=0
    local ax,bx,cx,ay,by,cy,c,lat,lon  
    local posTab=sph.model.positions
    local pTab,cTab,nTab,llTab,uvTab,colTab,norTab,iTab={},{},{},{},{},{},{},{}
    local deg=math.deg
    local asin=math.asin
    local atan2=math.atan2
      
    -- create tables for rounded icospheres        
    if not flat then
        iTab=sph.model.indices
        pTab=sph.model.positions
        cTab=sph.model.colors
        nTab=sph.model.normals  
        for a,b in pairs(iTab) do
            posTab[a]=pTab[b]
            colTab[a]=cTab[b]
            norTab[a]=nTab[b]
            iTab[a]=a
        end
    end 
       
    -- convert sphere positions to latitude and longitude
    for a,b in pairs(posTab) do
        c=b:normalize()
        lat=deg(asin(c.z))+90
        lon=deg(atan2(c.y,c.x))
        llTab[a]=vec2(lon,lat)     -- save lon, lat in table
        if lon//1==-149 then    -- get exact value of seam
            seam=lon
        end
    end  
    
    -- shift points on the left side of the seam to the right side 
    for a,b in pairs(llTab) do        
        b.x=b.x-seam
        if b.x<-.01 then
            b.x=b.x+360
        end
    end 
         
    -- shift individual points of triangle if needed  
    for z=1,#llTab,3 do
        ax,ay=llTab[z].x,llTab[z].y
        bx,by=llTab[z+1].x,llTab[z+1].y
        cx,cy=llTab[z+2].x,llTab[z+2].y
        if ax>250 or bx>250 or cx>250 then
            if ax<.01 then 
                ax=360
            end
            if bx<.01 then
                bx=360
            end
            if cx<.01 then
                cx=360
            end  
        end  
        if ay==0 or ay==180 then
            ax=(bx+cx)*.5
        elseif by==0 or by==180 then
            bx=(ax+cx)*.5
        elseif cy==0 or cy==180 then
            cx=(ax+bx)*.5
        end  
               
        -- create uv table
        uvTab[z]=vec2(ax/360,ay/180)
        uvTab[z+1]=vec2(bx/360,by/180)
        uvTab[z+2]=vec2(cx/360,cy/180)
    end  
      
    -- reset tables 
    sph.model.uvs=uvTab 
    if not flat then
        sph.model.indices=iTab
        sph.model.positions=posTab
        sph.model.colors=colTab
        sph.model.normals=norTab
    end
    
    --update indices table for inside view    
    if insideView then
        iTab=sph.model.indices
        for z=#sph.model.indices,1,-1 do
            iTab[#iTab+1]=iTab[z]
        end
        sph.model.indices=iTab
    end
end

@dave1707 - wow, that’s superb, I can’t see how you are going to improve that. Very neat finishes at the top and bottom of the sphere. I’m sure that I found a reference in which the sphere was built largely as an icosphere but had the usual rectangular/triangles for the top and bottom.

I assume generation of the craft icosphere vertices is rapid and the slow times in the Codea Craft are due to texturing, is that true?

@dave1707 With each iteration you are approaching my code …

The poles are tricky because of their interaction with the seam. Since the poles officially lie on the seam, the triangles involved in the poles will pass a simple test for being on the seam, but not all those triangles should be shifted. It took me a few iterations to figure that one out.

@Bri_G As for improvements, I would like to get rid of the hard coded values and condense the one section of code that’s used by them. The level 0 icosphere is created by Codea with a flat surface at the top and bottom, whereas the level 1 thru 5 have the point at the top and bottom. So a level 0 icosphere wouldn’t texture at the top and bottom very good unless more calculations are done to rotate it first to get the point at the top and bottom. The time for Codea to create a blank level 5 icosphere is .10 seconds. Using my code to texture a level 5 icosphere both inside and outside takes .36 seconds. That’s on my iPad Air. @LoopSpace I’m not having trouble with the poles. The problem I’m having is with the rounded icospheres because so many of the points are shared with other values. When one of those points need to be shifted, then new values for the 3 points of the triangle need to be created and I have to be careful how they’re created.

Here’s an updated version. I removed the hard coded values. I still don’t like the way the level 0 icosphere looks, but why do a level 0 when the other levels are fast. There are still some minor pole problems, but they’re not that noticeable.

displayMode(FULLSCREEN)

function setup()   
    -- create test texture
    myImg=image(1024,512)
    setContext(myImg)
    background(170, 198, 223, 255)
    fill(255,0,0)
    ellipse(250,256,200,100)
    fill(0,255,0)
    ellipse(900,256,100,200)
    fill(255,255,0)
    rect(512,320,256,128)
    stroke(255,0,0)
    strokeWidth(8)
    line(0,0,1024,512)
    line(0,512,1024,0)
    strokeWidth(4)
    stroke(255,255,0)
    line(4,0,4,512)
    line(1020,0,1020,512)
    line(0,508,1024,508)
    line(0,4,1024,4)
    fill(0, 19, 255, 255)
    fontSize(25)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde",512,490)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcde",512,22)
    fontSize(40)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.8)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.65)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.5)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.35)
    text("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",512,512*.2)
    stroke(0)
    strokeWidth(3)
    for z=0,1024,64 do
        line(z,0,z,512)
    end
    for z=0,512,32 do
        line(0,z,1024,z)
    end
    setContext() 
    -- end test texture   

    level=1     --  0 thru 5
    flat=true  -- true (triangles) or false (rounded)
    insideView=true -- show inside view of sphere

    assert(craft, "Please include Craft as a dependency")
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
    scene = craft.scene()
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 350, 0, 600)
    createSphere()
end

function draw()
    update(DeltaTime)
    scene:draw()
    sprite(myImg,WIDTH*.85,100,300)
end

function update(dt)
    scene:update(dt)
    sph.rotation = quat.eulerAngles(-90,0,0)
end

function createSphere()
    sph=scene:entity()
    sph.position=vec3(0,0,0)
    sph.model = craft.model.icosphere(100,level,flat)
    sph.material = craft.material("Materials:Standard")    
    sph.material.map=myImg
    local llTab={}  -- latitude, longitude table
    local uvTab={}  -- uvs table    
    local seam=0
    local ax,bx,cx,ay,by,cy,c,lat,lon    
    -- convert sphere positions to latitude and longitude
    local posTab=sph.model.positions
    local norTab=sph.model.normals
    local colTab=sph.model.colors
    local indTab=sph.model.indices
    --for a,b in pairs(sph.model.positions) do
    for a,b in pairs(posTab) do
        c=b:normalize()
        lat=math.deg(math.asin(c.z))+90
        lon=math.deg(math.atan2(c.y,c.x))
        table.insert(llTab,vec2(lon,lat))     -- save lon, lat in table
        if lon//1==-149 then    -- get exact value of seam
            seam=lon
        end
    end  
    -- shift points on the left side of the seam to the right side 
    for a,b in pairs(llTab) do        
        b.x=b.x-seam
        if b.x<-.01 then
            b.x=b.x+360
        end
    end      
    -- shift individual point of 3 if needed  
    for z=1,#llTab,3 do
        ax,ay=llTab[z].x,llTab[z].y
        bx,by=llTab[z+1].x,llTab[z+1].y
        cx,cy=llTab[z+2].x,llTab[z+2].y
        if ax>250 or bx>250 or cx>250 then
            if ax<.01 then 
                ax=360
            end
            if bx<.01 then
                bx=360
            end
            if cx<.01 then
                cx=360
            end  
        end 
        if ay==0 or ay==180 then
            ax=(bx+cx)/2
        elseif by==0 or by==180 then
            bx=(ax+cx)/2
        elseif cy==0 or cy==180 then
            cx=(ax+bx)/2
        end 
        -- create uv table
        table.insert(uvTab,vec2(ax/360,ay/180))
        table.insert(uvTab,vec2(bx/360,by/180))
        table.insert(uvTab,vec2(cx/360,cy/180))
    end
        
    -- fix bad triangles on rounded icospheres
    if not flat then
        for z=1,#indTab,3 do
            v1=indTab[z] v2=indTab[z+1] v3=indTab[z+2]
            u1=uvTab[v1] u2=uvTab[v2]   u3=uvTab[v3]
            if u1.x<.005 or u1.x>.995 or u2.x<.005 or 
                    u2.x>.995 or u3.x<.005 or u3.x>.995 then
                v=0
                if uvTab[v1].x>.5 then
                    v=v+1 
                end
                if uvTab[v2].x>.5 then
                    v=v+2  
                end
                if uvTab[v3].x>.5 then
                    v=v+4 
                end        
                xxx=0
                if v==1 or v==6 then
                    xxx=z
                elseif v==2 or v==5 then
                    xxx=z+1
                elseif v==3 or v==4 then
                    xxx=z+2
                end
                if level==1 and (v==3 or v==4) then
                    for m=0,1 do
                        local val=indTab[z+m]
                        table.insert(uvTab,vec2(1-uvTab[val].x,uvTab[val].y))
                        table.insert(posTab,posTab[val])
                        table.insert(colTab,colTab[val])
                        table.insert(norTab,norTab[val])
                        indTab[z+m]=#uvTab                    
                    end  
                elseif xxx>0 then
                    val=indTab[xxx]
                    table.insert(uvTab,vec2(1-uvTab[val].x,uvTab[val].y))
                    table.insert(posTab,posTab[val])
                    table.insert(colTab,colTab[val])
                    table.insert(norTab,norTab[val])
                    indTab[xxx]=#uvTab
                end
            end            
        end
    end
    sph.model.indices=indTab
    sph.model.uvs=uvTab 
    sph.model.positions=posTab
    sph.model.colors=colTab
    sph.model.normals=norTab
    -- update indices table for inside view
    if insideView then
        local ind=sph.model.indices 
        for z=#sph.model.indices,1,-1 do
            table.insert(ind,ind[z])
        end
        sph.model.indices=ind
    end
end