@LoopSpace - just realised what you mean, with a regular rectangular uv where the triangles just fit i.e. don’t overlap (like we have used up to date) it’s not a problem. The icosahedron model could overlap ?
@LoopSpace I’ve seen icosahedron mentioned as the 20 sided object. But since we’re using Codea and the Codea code uses icosphere, I refer to the six objects (0 thru 5) as icospheres. I don’t know if the other objects (1 thru 5) have specific names or not.
@dave1707 Yes, so I added some colouring to help me see the structure of the sphere a little better, but that should be taken out of “final code”. I left it in here so you could see where the seam is, and so where to look closely to see if it looks right.
There do appear to still be a couple of triangles at the poles that aren’t right.
When it’s right, we can serialise it into an overlay class on the isosphere and then it won’t take nearly so long to initialise.
@Bri_G Almost. It’s not that the triangles overlap so much, just that they share vertices.
@LoopSpace I wonder if you used 3 images placed lengthwise would fix the problem. That way using the middle image and going off the left or right edge of the middle image, you would still have a correct image on the left or right to work with.
@dave1707 - I’ve used a similar system for another purpose but you need to cover all perimeter faces including the corners, something I was considering too.
The size of the overlaps depends of the size of the triangles themselves.
No, that wouldn’t work. The problem is with the interpolation: when the texture coordinates are at either end of the texture, then the interpolation fills the triangle with the entire texture rather than the wrap-around part.
I think I need to draw y’all a picture … but it’s getting late so it’ll have to be tomorrow now.
@dave1707 @LoopSpace - links to four images showing banding and longitudinal seam and finally image from @dave1707 mesh sphere earlier 3D textured sphere.
See next post - had a lot of problems mainly due to image size.
@dave1707 @LoopSpace - I think the images above cause a problem with their physical size. I’ll replace them later after cutting and possibly shrinking (trying not to lose detail).
Latest stupid question - going back to first principles, with a textured mesh, the routines used to date define vertices based on two triangle/rectangles with the vectors for the uv tied to the vertices using a clockwise rotation of vertex/vector points using an ordered progression across the mesh/texture so absolute orientation and location are determined for each point on the texture based against zero vec2(0,0) on the texture.
On the ionosphere triangles are generated in a very prescribed, but diverse orientation relative to the the starting point. How do you allocate which point on the texture is the starting point and the order of progression in both points round the triangle and triangles round the texture/vertices? I’m just wondering if adjacent triangles could be misaligned. Secondly, with respect to lighting how does OpenGL determine light intensity etc on the triangles - is it based on normals?
Finally the Earth image I linked to above is 4096x2048, I have loaded one up in @dave1707’s sphere viewer but I have reduced the size of the images by a factor of 4 to use more practically. A bump map, normals and cloud image is also given - I reduced them all. Now working ou how to use them in Craft.
Other images:
@dave1707 @LoopSpace - I think you can see from @LoopSpace’s code with my mods. Built an image with border then used it as a texture for the sphere. Note the seam has gone. Reduce the x,y offsets in the txt:get() from 66,66 to 65,65 and the seam appears and even wider at 64,64.
-- LoopSpaceTxt
displayMode(FULLSCREEN)
displayMode(OVERLAY)
function setup()
assert(craft, "Please include Craft as a dependency")
assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")
sun = -32
-- parameter.integer("sun",0,-180,90)
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(sun,0,0)
v=scene.camera:add(OrbitViewer, vec3(0,0,0), 100, 0, 300)
rx,ry = 1024+128,768+128
txt = image(rx,ry)
pushMatrix()
setContext(txt)
background(122, 148, 211, 255)
fill(230, 19, 19, 255)
rectMode(CENTER)
rect(rx/2,ry/2,1024,768)
setContext()
popMatrix()
texture = txt:copy(66,66,1024,768)
spheres = {}
table.insert(spheres, createSphere1(vec3(-12,-30,0),3))
parameter.integer("angle",0,360,60)
end
function draw()
update(DeltaTime)
scene:draw()
sprite(txt,100,HEIGHT/2,200,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
@Bri_G Most spherical textures are based on a longitude/latitude system. So what I’m trying to do is figure out the longitude and latitude of a given vertex and assign that as the texture coordinates. What is causing the difficulty is that longitude 180 west is the same as longitude 180 east (this is what I’ve been calling “the seam”). So when we have a vertex on the seam, we have to decide whether it is part of an easterly triangle or a westerly one. I thought I’d figured this out, but it’s proving complicated.
@LoopSpace -surely if you average the longitude components of the triangle versus the point in question, it will be predominantly left or right of the seam?
Here’s an example of what I was saying above by using multiple images side by side to get a single image. This isn’t exact, but it gives a close enough example. The top image has a grid in red that represents the icosphere grid. Just like the icosphere grid, the left and right edges don’t line up on a verticle line. You can slide the xx slider to move the red grid left and right. The bottom image is a result (not exact) of where the grid covers the upper images. If you cut out the bottom image and wrapped it around, it should be a continuous image no matter where you start with the upper images. Run this in landscape mode. A lot of the calculations are just hacked for this example.
function setup()
parameter.number("xx",0,.33,0)
im1=readImage("SpaceCute:Rocketship")
img=image(512,183)
setContext(img)
background(223, 209, 169, 255)
sprite(im1,128,92)
sprite(im1,128+256,92)
setContext()
m=mesh()
m.vertices={
vec2(100,0),vec2(100,183),vec2(0,183),
vec2(100,0),vec2(200,183),vec2(100,183),
vec2(100,0),vec2(200,0),vec2(200,183),
vec2(200,0),vec2(300,0),vec2(200,183),
vec2(300,0),vec2(300,183),vec2(200,183),
vec2(300,0),vec2(400,0),vec2(300,183)
}
m.texture=img
m:setColors(255,255,255,255)
end
function draw()
background(0)
sprite(img,WIDTH/2,HEIGHT*.6)
map()
m.texCoords={
vec2(xx+.165,0),vec2(xx+.165,1),vec2(xx+0,1),
vec2(xx+.165,0),vec2(xx+.33,1),vec2(xx+.165,1),
vec2(xx+.165,0),vec2(xx+.33,0),vec2(xx+.33,1),
vec2(xx+.33,0),vec2(xx+.5,0),vec2(xx+.33,1),
vec2(xx+.5,0),vec2(xx+.5,1),vec2(xx+.33,1),
vec2(xx+0,0),vec2(xx+.165,0),vec2(xx+0,1)
}
translate(200,100)
m:draw()
translate()
end
function map()
w=m.vertices
stroke(255, 0, 0, 255)
strokeWidth(2)
for z=1,#m.vertices-1 do
line(xx*340+w[z].x+120,w[z].y+370,xx*340+w[z+1].x+120,w[z+1].y+370)
end
end
Here something interesting I ran across for icospheres 1 thru 5. When the sphere is rotated 31.7 degrees on the Y axis, the triangles line up to make a straight line that circles the icosphere. Here’s an example to show what I see. I draw a black line from the top of the screen to the bottom to make it easier to see the lineup. Drag your finger from top to bottom to rotate the sphere so you can see the lineup all the way around. By starting the texture at that line, it should wrap OK. Run this in landscape mode and change the 3 to the other values 1 thru 5. The 0 icosphere doesn’t work like this.
function setup()
parameter.number("angle",0,180,31.7)
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), 15, 0, 300)
createSphere(vec3(0,0,0),3) -- change the 3 to other values 1 thru 5
end
function draw()
update(DeltaTime)
scene:draw()
stroke(0,0,0)
strokeWidth(2)
line(WIDTH/2,0,WIDTH/2,HEIGHT)
end
function update(dt)
scene:update(dt)
pt.rotation = quat.eulerAngles(0,angle,0)
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 we go. I made life hard for myself by not knowing how to set positions etc for models, but once I figured that out then the rest was okay.
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("Surfaces:Stone Brick Height")
spheres = {}
local b=true
-- table.insert(spheres, createSphere(vec3(12,30,0),0,b))
table.insert(spheres, createSphere(vec3(12,15,0),1,b))
table.insert(spheres, createSphere(vec3(12,0,0),2,b))
table.insert(spheres, createSphere(vec3(12,-15,0),3,b))
table.insert(spheres, createSphere(vec3(12,-30,0),4,b))
b=nil
table.insert(spheres, createSphere1(vec3(-12,30,0),0,b))
table.insert(spheres, createSphere1(vec3(-12,15,0),1,b))
table.insert(spheres, createSphere1(vec3(-12,0,0),2,b))
table.insert(spheres, createSphere1(vec3(-12,-15,0),3,b))
table.insert(spheres, createSphere1(vec3(-12,-30,0),4,b))
parameter.integer("angle",0,360,300)
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,b)
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,b)
pt.material = craft.material("Materials:Standard")
return pt
end
function createSphere1(p,s,b)
local pt=scene:entity()
pt.position=vec3(p.x,p.y,p.z)
pt.model = craft.model.icosphere(5,s,b)
local uv={} -- need to change the uvs for texture
local pts={}
local pa,pb,pc
local ta,tb,tc,tm,tx
local pha,phb,phc
local u,v = vec3(0,1,0),vec3((math.sqrt(5)-1)/2,0,-1):normalize()--/math.sqrt(5)
local za,zb,zc
local sa,sb,sc
local nv = pt.model.vertexCount
local inv = pt.model.vertexCount
for z=1,pt.model.indexCount,3 do
za,zb,zc = pt.model.indices[z],pt.model.indices[z+1],pt.model.indices[z+2]
pa = pt.model.positions[za]
pb = pt.model.positions[zb]
pc = pt.model.positions[zc]
ta, pha = longlat(u,v,pa)
tb, phb = longlat(u,v,pb)
tc, phc = longlat(u,v,pc)
-- set defaults
uv[za] = vec2(ta,pha)
uv[zb] = vec2(tb,phb)
uv[zc] = vec2(tc,phc)
tm = {ta,tb,tc}
table.sort(tm)
-- See if the triangle wraps around the seam
if tm[3] - tm[1] > tm[1] - tm[3] + 1 then
sa = math.floor(ta+.5) -- 0 or 1, depending on which seam is closest
sb = math.floor(tb+.5)
sc = math.floor(tc+.5)
if (ta ~= .5 and tb ~= .5 and sa ~= sb) or (tc ~= .5 and tb ~= .5 and sb ~= sc) or (ta ~= .5 and tc ~= .5 and sc ~= sa) then
-- Need to ignore .5s
if ta ~= .5 and math.abs(sa - ta) > math.max(math.abs(sb - tb), math.abs(sc - tc)) then
-- a is furthest away from seam so use as basepoint
if tb ~= .5 and sb ~= sa then
-- shift point b
if not b then
table.insert(pts,zb)
nv = nv + 1
-- print(zb,nv,ta,tb,tc)
zb = nv
pt.model:index(z+1, zb)
end
tb = sa
end
if tc ~= .5 and sc ~= sa then
-- shift point c
if not b then
table.insert(pts,zc)
nv = nv + 1
-- print(zb,nv,ta,tb,tc)
zc = nv
pt.model:index(z+2,zc)
end
tc = sa
end
elseif tb ~= .5 and math.abs(sb - tb) > math.abs(sc - tc) then
-- b is furthest away
if ta ~= .5 and sa ~= sb then
-- shift point a
if not b then
table.insert(pts,za)
nv = nv + 1
-- print(zb,nv,ta,tb,tc)
za = nv
pt.model:index(z, za)
end
ta = sb
end
if tc ~= .5 and sc ~= sb then
-- shift point c
if not b then
table.insert(pts,zc)
nv = nv + 1
-- print(zb,nv,ta,tb,tc)
zc = nv
pt.model:index(z+2, zc)
end
tc = sb
end
else
-- c is furthest away
if tb ~= .5 and sb ~= sc then
-- shift point b
if not b then
table.insert(pts,zb)
nv = nv + 1
-- print(zb,nv,ta,tb,tc)
zb = nv
pt.model:index(z+1, zb)
end
tb = sc
end
if ta ~= .5 and sa ~= sc then
-- shift point a
if not b then
table.insert(pts,za)
nv = nv + 1
-- print(zb,nv,ta,tb,tc)
za = nv
pt.model:index(z, za)
end
ta = sc
end
end
-- print(ta,tb,tc)
end
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[za] = vec2(ta,pha)
uv[zb] = vec2(tb,phb)
uv[zc] = vec2(tc,phc)
end
if not b then
pt.model:resizeVertices(nv)
for k,v in ipairs(pts) do
pt.model:position(inv+k,pt.model.positions[v])
pt.model:normal(inv+k, pt.model.normals[v])
pt.model:color(inv+k, pt.model.colors[v])
end
end
pt.model.uvs = uv
for k=1,pt.model.vertexCount do
ta,pa = longlat(u,v,pt.model.positions[k])
pt.model:color(k,color(ta*127+127))
end
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
The code needs a big clean-up! Also, I set the colours according to the longitude which in a real use case would be removed. This was so that I could more easily see where the seam was.
Incidentally, @dave1707, the angle you spotted is where I put the seam. It’s embedded in the vector v
.
@LoopSpace It looks like there’s a problem when using table.insert(spheres, createSphere1(vec3(-12,-15,0),3,b)) with b nil around angle 214 on the slider. Same way with level 4. Ran these by themselves, level 4 took forever on my iPad Air.
@LoopSpace Have you looked at the example World Generator. It looks like they texture a sphere using a cube map.
@LoopSpace - absolutely superb, used the Earth texture from the above reference and the finish on the sphere is fantastic.
@dave1707 try commenting out the loop:
for k=1,pt.model.vertexCount do
ta,pa = longlat(u,v,pt.model.positions[k])
pt.model:color(k,color(ta*127+127))
end
Do you still see the problem? If so, can you describe it because I don’t know what you mean.