Craft sphere texture

Here’s an example of Craft spheres, one row without a texture and another row using a texture. Each face uses the texture. The columns show the spheres with higher and higher subdivisions. On the left is the image that’s being used. Try different images to see what you get. The sphere can be wrapped with a single texture, but it’s too involved to show here. I was able to wrap part of the sphere, but gave up trying to wrap the whole sphere because you can’t wrap an icosphere with a rectangular texture and have it work OK.

displayMode(FULLSCREEN)

function setup()
    assert(craft, "Please include Craft as a dependency")
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(158, 202, 223, 255)
    skyMaterial.horizon=color(98, 166, 114, 255)
    scene.sun.rotation=quat.eulerAngles(-90,0,0)
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 100, 0, 300)
    
    texture=readImage("Small World:Heart Flat")
    
    createSphere(vec3(12,30,0),0)
    createSphere(vec3(12,15,0),1)
    createSphere(vec3(12,0,0),2)
    createSphere(vec3(12,-15,0),3)
    createSphere(vec3(12,-30,0),4)
    
    createSphere1(vec3(-12,30,0),0)
    createSphere1(vec3(-12,15,0),1)
    createSphere1(vec3(-12,0,0),2)
    createSphere1(vec3(-12,-15,0),3)
    createSphere1(vec3(-12,-30,0),4)
end

function draw()
    update(DeltaTime)
    scene:draw()
    sprite(texture,100,HEIGHT/2,100)
    fill(255)
    text("No texture",WIDTH/2-120,HEIGHT-30)
    text("Use texture",WIDTH/2+120,HEIGHT-30)
    text("Texture used",100,HEIGHT/2+70)
end

function update(dt)
    scene:update(dt)
end

function createSphere(p,s)  
    pt=scene:entity()
    pt.position=vec3(p.x,p.y,p.z)
    pt.model = craft.model.icosphere(5,s,1)
    pt.material = craft.material("Materials:Standard")
end

function createSphere1(p,s)
    pt=scene:entity()
    pt.position=vec3(p.x,p.y,p.z)
    pt.model = craft.model.icosphere(5,s,1)
    uv={}   -- need to change the uvs for texture
    for z=1,#pt.model.uvs/3 do
        table.insert(uv,vec2(0,0))
        table.insert(uv,vec2(0,1))
        table.insert(uv,vec2(1,1))  
    end
    pt.model.uvs = uv
    pt.material = craft.material("Materials:Standard")
    pt.material.map=texture
end

Looks great! Good job! But have to say that I didn’t really figured out what you meant with that dependency addition :slight_smile:

@dave1707 - you are a star, I can see how you’ve done that, and I think there is a way of texturing an icosphere if we can get the formula used to derive the vertex points for the object.

I had decided to go down the obvious route and get a sphere obj model and materials mtl file built for it to use any texture I need, then resizing as required. But I had difficulty finding a file and some of them won’t load or only load the obj file resulting in an untextured model. I assume @John’s loader within Craft is rightly built on the strict obj file model, but there are variants for different packages and I am useless with Blender.

Using sphere = Craft.model(“sphere”) you can load any legal obj file, mtl file and texture. Perhaps we could get an obj file written using Codea -I have a feeling @Loopspace wrote one for his Craft demo - perhaps we could get that to write an obj file and mtl file to tabs in one of the textured sphere projects?

@Bri_G There is a way to texture it, maybe later I’ll have a demo showing it. The only problem is getting the right formula to do it automatically. For a 0 type icosphere, it has 20 faces or 60 vertices. For a type 4 icosphere it has 5120 faces and 15360 vertices. I can probably do 20 faces manually, but not the 5120 faces.

@dave1707 - I think you may be able to texture it by dropping perpendiculars onto the 2D texture from each vertex, it is needed for two faces and needs ordering for triangles. I’m looking for a formula now.

@Bri_G Here’s my latest example, coloring each face with a random color.

function setup()
    assert(craft, "Please include Craft as a dependency")
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(158, 202, 223, 255)
    skyMaterial.horizon=color(98, 166, 114, 255)
    scene.sun.rotation=quat.eulerAngles(-90,0,0)
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 100, 0, 300)
    createSphere(vec3(0,30,0),0)
    createSphere(vec3(0,18,0),1)
    createSphere(vec3(0,6,0),2)
    createSphere(vec3(0,-6,0),3)
    createSphere(vec3(0,-18,0),4)
    createSphere(vec3(0,-30,0),5)
end

function draw()
    update(DeltaTime)
    scene:draw()
end

function update(dt)
    scene:update(dt)
end

function createSphere(p,s)
    pt=scene:entity()
    pt.position=vec3(p.x,p.y,p.z)
    pt.model = craft.model.icosphere(5,s,1)
    pt.material = craft.material("Materials:Standard")
    cc={}
    for z=1,#pt.model.colors/3 do
        rc=color(math.random(255),math.random(255),math.random(255))
        table.insert(cc,rc)
        table.insert(cc,rc)
        table.insert(cc,rc)
    end
    pt.model.colors=cc
end

Here’s a texturing. It takes too long for the more detailed isosphere, my ipad pro is fine up to level 3, but takes a significant while with level 4. Of course, one could extract the coordinates and hard code them rather than calculating them each time. It’s not great with level 0 as a dodecahedron doesn’t have a meridian line.

displayMode(FULLSCREEN)
displayMode(OVERLAY)

function setup()
    assert(craft, "Please include Craft as a dependency")
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")        
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(158, 202, 223, 255)
    skyMaterial.horizon=color(98, 166, 114, 255)
    scene.sun.rotation=quat.eulerAngles(-90,0,0)
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 100, 0, 300)

    texture=readImage("Cargo Bot:Crate Green 2")
    spheres = {}
    table.insert(spheres, createSphere(vec3(12,30,0),0))
    table.insert(spheres, createSphere(vec3(12,15,0),1))
    table.insert(spheres, createSphere(vec3(12,0,0),2))
    table.insert(spheres, createSphere(vec3(12,-15,0),3))
    table.insert(spheres, createSphere(vec3(12,-30,0),4))

    table.insert(spheres, createSphere1(vec3(-12,30,0),0))
    table.insert(spheres, createSphere1(vec3(-12,15,0),1))
    table.insert(spheres, createSphere1(vec3(-12,0,0),2))
    table.insert(spheres, createSphere1(vec3(-12,-15,0),3))
    table.insert(spheres, createSphere1(vec3(-12,-30,0),4))
    parameter.integer("angle",0,360,60)
end

function draw()
    update(DeltaTime)
    scene:draw()
    sprite(texture,100,HEIGHT/2,100)
    fill(255)
    text("No texture",WIDTH/2-120,HEIGHT-30)
    text("Use texture",WIDTH/2+120,HEIGHT-30)
    text("Texture used",100,HEIGHT/2+70)
end

function update(dt)
    for k,v in ipairs(spheres) do
        v.rotation = quat.eulerAngles(0,angle,0)
    end
    scene:update(dt)
end

function createSphere(p,s)  
    local pt=scene:entity()
    pt.position=vec3(p.x,p.y,p.z)
    pt.rotation=quat.eulerAngles(0,90,0)
    pt.model = craft.model.icosphere(5,s,1)
    pt.material = craft.material("Materials:Standard")
    return pt
end

function createSphere1(p,s)
    local pt=scene:entity()
    pt.position=vec3(p.x,p.y,p.z)
    pt.model = craft.model.icosphere(5,s,1)
    local uv={}   -- need to change the uvs for texture
    local pa,pb,pc
    local ta,tb,tc,tm,tx
    tm,tx = 1,0
    local pha,phb,phc
    local u,v = vec3(0,1,0),vec3(math.cos(2*math.pi/3),0,math.sin(2*math.pi/3))
    for z=1,pt.model.indexCount,3 do
        pa = pt.model.positions[pt.model.indices[z]]
        pb = pt.model.positions[pt.model.indices[z+1]]
        pc = pt.model.positions[pt.model.indices[z+2]]
        ta, pha = longlat(u,v,pa)
        tb, phb = longlat(u,v,pb)
        tc, phc = longlat(u,v,pc)
        if ta > .9 and (tb < 0.5 or tc < .5) then
            ta = 0
        end
        if tb > .9 and (tc < 0.5 or ta < .5) then
            tb = 0
        end
        if tc > .9 and (ta < 0.5 or tb < .5) then
            tc = 0
        end
        if pha == 1 or pha == 0 then
            ta = (tb+tc)/2
        end
        if phb == 1 or phb == 0 then
            tb = (ta+tc)/2
        end
        if phc == 1 or phc == 0 then
            tc = (tb+ta)/2
        end
        uv[pt.model.indices[z]] = vec2(ta,pha)
        uv[pt.model.indices[z+1]] = vec2(tb,phb)
        uv[pt.model.indices[z+2]] = vec2(tc,phc)
    end
    pt.model.uvs = uv
    pt.material = craft.material("Materials:Standard")
    pt.material.map=texture
    return pt
end

function longlat(u,v,p)
    local p = p:normalize()
    local ph = math.asin(p:dot(u))/math.pi + .5
    local x,y = p:dot(v), p:dot(u:cross(v))
    local th = math.atan(y,x)/(2*math.pi)+.5
    return th, ph
end

@dave1707 - just in the middle of expressing a view on how complex this model is when I saw the code from @LoopSpace and ran it. Cut it down to one sphere size 3 and textured with the EarthDay map. Wow!!!, I new it was feasible but the complexity makes it a very slow process and I think that’s why there’s been little progress/interest in complex textured models with Codea Craft. Thanks a lot for that @LoopSpace it’s nice to have a mathematician on this forum.

I think it’s Back to the standard sphere obj model and texture to get what I need. Would have posted an image of the textured sphere from this code but can’t take a snapshot.

@LoopSpace Nice job. What looks really nice is using the texture “Surfaces:Stone Brick Height”. I commented out the last one in each group so I only did 4 per group which took 18 seconds on my iPad Air. Can you give a quick explanation of how you calculate the uv table, not mathematically, but just what you’re doing. It looks like you might be taking the vertices and placing them on the texture map ( not literally ) and then calculating its position between 0 and 1 in width and height. Not exactly sure what’s going on in the longlat function.

@Bri_G I suspect that serialising the uv coordinates will speed it up considerably. There’s quite a bit of calculation going on to figure out the coordinates and since the isosphere is the same each time, that could easily be done once for all. Maybe I’ll gist them later.

The advantage of the model that I have in my MeshExt class (which I posted about in this discussion) is that everything is defined in terms of latitude and longitude so the texture coordinates don’t need any heavy calculation.

@LoopSpace - not a criticism, just an observation: I just run at level 3 for convenience in time, but I notice banding of the texture around the sphere. I presume this is due to overlap either inaccuracies in the calcs or slight positional inaccuracies in the individual textures. Can you see it?

I run on an iPad Pro 10’’ it may be due to the retina screen.

@Bri_G @LoopSpace When I run this on my 12" iPad Pro at level 3 using texture=readImage(“Surfaces:Stone Brick Height”), I see triangular faces on half of the sphere which might be the cause of the banding. The Stone Brick Height is a 1024x1024 image which gives a better resolution for the wrap. When I remove the 1 so pt.model = craft.model.icosphere(5,s,1) is pt.model = craft.model.icosphere(5,s) to show a smooth surface on the full icosphere, there is a bow tie effect at the seam. I don’t know if that can be corrected.

@dave1707 @LoopSpace good observation, I was going to report that there is a longitudinal seam visible, which on the rear of the sphere turns to a demarcation line between light and dark. Also when run at level 4 the banding seems reduced but the longitudinal band is more pronounced.

I wonder if this is due to the difference between Codea and OpenGL in addressing textures - I think Codea starts at 1,1 and OpenGL at 0,0?

(Replying in order)

@Bri_G I’m not sure what you mean by “banding”. I’m on an iPad Pro too, and I don’t see any obvious defects. Of course, using a texture that is meant for a rectangle on a sphere will lead to deformations, but that’s due to geometry not to errors in calculation. That’s not to say that there aren’t any errors - I don’t guarantee that - but I’d need to see a screenshot to know what you mean.

@dave1707 The original code used the 1 flag meaning that the sphere was facetted, and that’s the triangles that you’re seeing. I also don’t know if that’s what @Bri_G means by banding. When you remove the 1 flag then it switches to a smooth sphere and that’s a different kettle of fish (see below).

@Bri_G A longitudinal seam will be visible if the texture doesn’t tile, so @dave1707’s suggestion of using the Stone Brick texture is a good one since those tile. Do you still see the seam with that? Any difference between Codea and OpenGL is irrelevant in this, by the way.

Right, on to facetted vs smooth. With the facetted sphere, each vertex belongs to a unique triangle. This is because although the vertex position may repeat, the normals depend on the triangle. Therefore, you can’t reuse an old vertex in a new triangle. This makes it easy to assign texture coordinates.

With a smooth sphere, the normals now only depend on the vertex position and not on the triangle it is in. It is therefore possible to re-use vertices in different triangles. Most of the time this is okay, but when a vertex lies on the seam, it will need to have different texture coordinates depending on whether it is at the start of the texture or the end. For this to work, we need to duplicate the vertex.

I’m working on this, and have it working for levels 0,1,2 but something goes wrong with levels 3 and 4 so I need to think a bit more carefully about my code!

I’ve also serialised the coordinates for the facetted sphere, and with those then there’s no observable slow-down in creating the spheres. Once we’re happy with those then I’ll upload them to github.

@LoopSpace - on the banding issue; if you look at @dave1707’s example with coloured triangles (levels 2 and 3) you can see bands of triangles which circle part of the sphere. Many in ‘parallel’, that’s in inverted commas as they are not truly parallel - many of the triangles in these bands are different sizes so the line is not truly straight. You can see these bands in the fully textured level 3 model.

On the longitudinal seam - I saw it with the tiled Stone Brick Structure, which is 1024x1024. I didn’t see it with the EarthDay image (1024x512) but much of the image is dark, which also seemed to reduce the banding but not totally.

A couple of things to throw into the discussion. I have seen references to isospheres based on the icosahedron which look to involve several parallel bands centred around the ‘equator’ with a different, more random, partial sphere at the top and bottom - are there different approaches to isospheres?

Secondly, I am now thinking that this may be a lighting effect as when I reduced the sun’s x position from -90 to -32 the triangles became more prominent. Probably due to shadow effects on the sphere. Are all the points on the isosphere equidistant from its centre?

On your point about the seam and different vertices - if the texture is truly tolerable/wrapable surely new vertices are not needed. How is the effect with levels 3 and 4 manifested?

Let us know when you have serialised the vertices and placed them on github - keen to try that approach.

@Bri_G As to the construction of an icosphere, the lowest level is created by using 3 equal sized rectangles placed at right angles to each other and the corners of the rectangles are connected to form triangles. Each level above that is created by placing a smaller inverted triangle inside each larger triangle connected at the center of the lines of the larger triangle. The new points are pushed out so they are all the same distance from the center. That’s a visual way of creating the icospheres. There’s probably a nice formula that calculates every point for each of the different sized icosphere.

@Bri_G I ran across this link looking for a formula. Scroll to the bottom of this link. That’s for the level 0 icosphere, 20 sides. I don’t know if it can be expanded for the other levels. If I find something I’ll update this.

https://geometrictools.com/Documentation/PlatonicSolids.pdf

Here’s another link

https://schneide.wordpress.com/2016/07/15/generating-an-icosphere-in-c/

Here’s another link.

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

@dave1707 - you’re going down the same road I went down, got about 5 of them logged in my browser bookmarks. It’s all interesting stuff. iPads recharging, I’ll compare notes when its up and running.

@dave1707 - just found this link, for personal use only. Textured Earth with full texture images. Will be playing with these also when we’re fully charged:

https://free3d.com/free-3d-models/obj-sphere

You’ll have to download this yourself according to licence - personal use only.

@Bri_G On the banding issue, do you just mean that you can see the individual triangles? If so, that’s just to do with how the normals are defined. With a facetted model, each triangle is considered to be flat and reflects light accordingly. With a smooth model, the triangles are considered to be curved and reflect light accordingly (they aren’t actually curved, but the light reflects as if they were). That’s nothing to do with the texture.

@dave1707:

the lowest level is created by using 3 equal sized rectangles placed at right angles to each other and the corners of the rectangles are connected to form triangles

That’ll be an icosahedron, then.

Now, with regard to the seam and the texture. Even with a tileable texture, you need to know which end of the texture the triangle should be cut from, and so a vertex on the seam could sometimes be at one end of the texture and sometimes at the other.

Are either of you seeing a longitudinal seam on the facetted isosphere? It’s definitely there on the smooth one, but I currently don’t see it on the facetted one.