@LoopSpace Here’s a picture of what I was seeing. Commenting out the code above eliminated it.
This appears to have fixed the poles (note that it still looks a bit off, but that’s because the texture is designed for cubes).
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)
if isouvs and isouvs[s] and false then
print("using saved uvs")
pt.model.uvs = isouvs[s]
else
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()
local za,zb,zc
local sa,sb,sc
local da,db,dc
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)
da = math.abs(sa - ta)
db = math.abs(sb - tb)
dc = math.abs(sc - tc)
if ta == .5 then
da = 0
end
if tb == .5 then
db = 0
end
if tc == .5 then
dc = 0
end
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
if da > math.max(db, dc) 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
zb = nv
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
zc = nv
end
tc = sa
end
elseif db > dc 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
za = nv
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
zc = nv
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
zb = nv
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
za = nv
end
ta = sc
end
end
end
end
if pha == 1 or pha == 0 then
ta = (tb+tc)/2
if not b then
table.insert(pts,za)
nv = nv + 1
za = nv
end
end
if phb == 1 or phb == 0 then
tb = (ta+tc)/2
if not b then
table.insert(pts,zb)
nv = nv + 1
zb = nv
end
end
if phc == 1 or phc == 0 then
tc = (tb+ta)/2
if not b then
table.insert(pts,zc)
nv = nv + 1
zc = nv
end
end
pt.model:index(z, za)
pt.model:index(z+1, zb)
pt.model:index(z+2, zc)
uv[za] = vec2(ta,pha)
uv[zb] = vec2(tb,phb)
uv[zc] = vec2(tc,phc)
end
--[[
local tuv = {}
for k,v in ipairs(uv) do
table.insert(tuv, string.format("vec2(%f,%f)",v.x,v.y))
end
saveText("Project:Isosphere Textures " .. s,"uv[" .. s .. "] = {\
" .. table.concat(tuv,",\
") .. "\
}")
--]]
if not b then
pt.model:resizeVertices(nv)
-- print(nv,inv,#pts,#uv)
for k,v in ipairs(pts) do
-- print(k,v)
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
for k=1,nv do
if not uv[k] then
print(k)
end
end
pt.model.uvs = uv
--[[
for k=1,pt.model.vertexCount do
ta = pt.model.uvs[k].x
pt.model:color(k,color(ta*127+127))
end
]]
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
@LoopSpace Where’s local uvtab = readProjectTab(“Library Craft:IsosphereTexture”)
@dave1707 Sorry! That stuff should be commented out for the moment – I’ll edit the original post. That’s the first steps toward serialising the uvs.
There, that should work.
@LoopSpace - looks great, couldn’t see a defect. Tried to save uvs to tab but would only print the header. I’ll wait until you finalise the package.
Thanks for the hard work - have you alerted @John to this?
Here’s my version of texturing an icosphere. I know I’m not doing it the correct way, but it’s fast. I can texture a level 5 icosphere in less than a second. Setting flat to true (shows small triangle on icosphere) seems to do the texture correct. Setting flat to false (shows a more rounded icosphere) still has a few problems. At level 5, it’s not too noticeable. I set up my own texture to make checking the texture wrap easier. Slide the flip slider to change between my texture and the Stone Brick texture. Also, change the level value (0 to 5) to change the level of the icosphere and rerun the code. I’m still working on fixing the rounded icosphere problem. There seems to be some triangles that aren’t set the way I think they should be. I don’t know how all the icosphere arrays work together, but that’s something to figure out. Use the angle slider to rotate the sphere, or slide your finger on the screen.
function setup()
parameter.integer("angle",-180,180,-90)
parameter.boolean("flip",false)
-- 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)
fill(0, 19, 255, 255)
fontSize(25)
text("This is my texture example, move image slider for more.",512,490)
text("This is my texture example, move image slider for more.",512,22)
fontSize(40)
text("This is my texture example, move image slider for more.",512,512*.8)
text("This is my texture example, move image slider for more.",512,512*.65)
text("This is my texture example, move image slider for more.",512,512*.5)
text("This is my texture example, move image slider for more.",512,512*.35)
text("This is my texture example, move image slider for more.",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()
texture1=myImg
texture2=readImage("Surfaces:Stone Brick Height")
texture=texture1
level=1 -- 0 thru 5
flat=true -- true (triangles) or false (rounded)
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), 250, 0, 600)
createSphere()
end
function draw()
update(DeltaTime)
scene:draw()
if flip then
texture=texture2
else
texture=texture1
end
sph.material.map=texture
sprite(texture,WIDTH/2,140,texture.height/2)
end
function update(dt)
scene:update(dt)
sph.rotation = quat.eulerAngles(angle,0,0)
end
function createSphere()
sph=scene:entity()
sph.position=vec3(0,33,0)
sph.model = craft.model.icosphere(60,level,flat)
sph.material = craft.material("Materials:Standard")
sph.material.map=texture
print("indexs",sph.model.indexCount)
local temp={} -- temp table for latitude and longitude
local seam=0
-- convert to latitude and longitude
for a,b in pairs(sph.model.positions) do
local c=b:normalize()
local lat=math.deg(math.asin(c.z))+90
local lon=math.deg(math.atan2(c.y,c.x))
table.insert(temp,vec2(lon,lat)) -- save lat, lat in temp table
-- get exact value of seam
if lon//1==-149 then
seam=lon
end
end
-- shift points on the left side of the seam to the right side
for a,b in pairs(temp) do
b.x=b.x+seam*-1
if b.x<-.01 then
b.x=b.x+360
end
end
-- shift individual points if needed
for z=1,#temp,3 do
ax,bx,cx=temp[z].x,temp[z+1].x,temp[z+2].x
if ax>250 or bx>250 or cx>250 then
if ax<.01 then
temp[z].x=360
end
if bx<.01 then
temp[z+1].x=360
end
if cx<.01 then
temp[z+2].x=360
end
end
ax,ay=temp[z].x,temp[z].y
bx,by=temp[z+1].x,temp[z+1].y
cx,cy=temp[z+2].x,temp[z+2].y
if ay==0 or ay==180 then
temp[z].x=(bx+cx)/2
elseif by==0 or by==180 then
temp[z+1].x=(ax+cx)/2
elseif cy==0 or cy==180 then
temp[z+2].x=(ax+bx)/2
end
end
-- create uv table
local uvTab={}
for a,b in pairs(temp) do
table.insert(uvTab,vec2(b.x/360,b.y/180))
end
sph.model.uvs=uvTab
end
@dave1707 - excellent it is very fast rendering, level 3 seems to be the best compromise for my needs. It still suffers from the triangular banding effect, tested with EarthDay.png an most obvious on Sahara desert. Also, as with a few of the projects displayed one reflective triangle near the apex.
I think Ignatz made a point on the triangular lighting effect on his WOT demo,
He said the lighting was set for each triangle and he used a shader to normalise each pixel, see the following:
https://coolcodea.wordpress.com/2015/10/16/239-wot-building-a-tank/
That may resolve the problems with the icosphere, worth a look.
@Bri_G Was that level 3 flat=true, or flat=false. I haven’t seen any problems yet with flat=true for levels 1 thru 5.
@dave1707 - that was flat = true, switched to flat = false made the triangle/banding disappear and the single mirrored triangle at the apex became a small whit circle.
@dave1707 - the routine is certainly fast even at level 5. Just noticed when flat = false at level 3 there is something funny happening on the poles, one or two unrelated textured triangles. It’s not as noticeable when flat = true but the the triangles are pronounced with some banding.
@Bri_G How icospheres are drawn is kind of a mystery to me. There’s several arrays that are used, but they seem to be a little different when doing flat=true or flat=false. When flat=true, then you can see all the triangles that make up the icosphere. When flat=false, the icosphere has a rounded shape to it and the triangles are less noticeable at the higher levels. I’m still playing around with coloring individual triangles of the icospheres which might help with understanding how to get the texture to work in all instances.
Fixing those “small” issues is what makes my code slower. There is probably some optimisation to be had in my code, but the intention is to serialise the uvs rather than have to calculate them afresh every time.
The smooth isosphere cannot be done just by assigning texture coordinates to positions. You will always end up with defects on the seam. Again, that’s what all the extras in my code fix.
@Loopspace @dave1707 - looking at the plotting of points used for texture allocation, in the project written by @dave1707, you can see a series of longitudinal lines covering almost the height of the texture. There are only two points at the poles. If you triangulate from the longest longitude you are left with 4 triangles in each corner of the texture - it may be these allocations that are causing the problem.
@LoopSpace Let me ask you a question, does the indices array contain the index into the positions table to get the position for each corner to create the triangles.
Let me ask you a question, does the indices array hold an index into the positions array used to form the 3 vertices of each triangle.
Yes. The indices array should be thought of as consisting of triples, each triple being the indices of three positions making up a triangle.
In the non-smooth case, each position is contained in a unique triangle. This makes it possible to simply assign the texture coordinates based on the triangle. In the smooth case, positions are re-used and for positions near the seam (and poles) this means that they can have more than one texture coordinate. My code deals with this by duplicating those positions. This is the only way to fix this.
@LoopSpace That’s what I thought. I’m only using the position array in groups of 3 to create the triangles in my code. For the rough icosphere, only using the position array works OK. For the smooth icosphere, the majority of triplets are correct, but there are a few triplets that span across several other triangles which is what causes a problem in my code. I don’t see why the position array can’t have the correct info in both cases.
@dave1707 The way the icosphere is set up, in the non-smooth case then every position is in a unique triangle. This leads to vertices being duplicated quite a lot (roughly five or six times) but this is needed because although the positions are the same for each re-use of the vertex, other data (specifically the normals) is not.
However, in the smooth case, the normals depend only on the vertex and not on the triangle and therefore the vertices can be re-used in different triangles. For the vast majority of the vertices, this is okay with the texture as the texture coordinates only depend on the position on the sphere.
Where this fails is at the edges of the texture, which with the usual sphere wrapping are at the poles and at the seam. At these places, more than one point on the texture goes to the same point on the sphere. This is perhaps most obvious at the poles where the entire top of the texture maps to the north pole (and bottom to the south pole). Therefore, the texture coordinate at the north pole depends on which triangle is under consideration. So the pole needs duplicating, once for each triangle it is in. A similar thing happens down the seam.
@LoopSpace That explains why in my smooth icosphere the bad textures are at the poles and at certain places along the seam. I’ll have to keep looking at the way I do my code to see if there might be an easy way to fix the bad spots. The fun is in trying to get it to work, but I don’t want to spend a lot of time trying to fix it because I probably won’t do anything with it anyways.
@LoopSpace Here’s the problem I was running into using the positions array. The first image shows a grid of my longitude and longitude points. This is for the smooth icosphere. I draw the triangles in white and you can see where the green triangles and the green line across the middle are the problem areas. They cross over several other triangles. The second image is for the rough icosphere and you can see all the triangles are OK. Ignore the long triangles that run from the left edge to the right edge. Those would actually connect in the opposite direction.
It seems like my icosphere generator is causing a few issues for people. Honestly the best solution would probably be for me to include a UV sphere generator as I think they are much easier to properly texture automatically.
@dave1707
The reason why you see different mesh data between flat and smooth icospheres is due to the way it shares normals in smooth meshes to save on vertices.