Not clear on UV mapping ...

After looking at @dave1707 's little plane sign and dice and skybox and sphere … I can often make an image or image portion go where I want, kind of. What I’m not clear on, at least not well enough to explain it to a child, how the UV points are going to relate to the 3D shape. I’d love to be pointed to a bit of a write up that relates Codea indices, vertices, and UVs.

I’ll put here what I sort of think I know.

UV “coordinates” refer to sections of the image.

This code:

    local uvs = {}
    table.insert(uvs,vec2(0.5,1))
    table.insert(uvs,vec2(1,1))
    table.insert(uvs,vec2(1,0))
    table.insert(uvs,vec2(0.5,0))

Beyond that I’m flailing …

Refers to the right half of whatever the image is. In the case of a plane, it maps that half of the image onto the whole visible part of the plane.

When I do a simple cube with the text map image, it shows up on all the faces. Dave’s die display has a loop that selects 1/6 of the flat die picture and adds it to UVs. So it seems there is an implicit unwrapping of the cube, apparently into rectangular faces, even though the faces are all really (at least) two triangles.

I do mostly understand how to do UV mapping in Blender. FWIW.

I’m happy to read posts but so far haven’t found much explanation. I can see from this code (and the delightful sphere one) how to do mappings to the plane, cube, and sphere … but darned if I can get a sense of what’s making those scripts be the right answer.

Advise me, please. Thanks!

@RonJeffries There isn’t much of an explanation of how those things work. I figured it out, somewhat, just by trial and error. So here’s what I understand. If you draw a plane, (craft.model.plane), you have 4 corners. The 4 corners of the image maps to those 4 corners. So you would have 0,0 0,1 1,1 1,0 in a clockwise direction. If you go in a counterclockwise direction, I think the image is reversed. So basically, you can stretch the image over the plane by changing the values. If you go with .25,.25 .25,.75 .75,.75, .25,.75 then your using a small inner portion of the image. Basically you just specify a percentage of the image you want to use and where to put it. That’s how you wrap a cube. You just stretch the image over all the points (indices) of the cube.

@RonJeffries - I look at it in a simple manner - taking each of the three axes on a 3D model. When a model is recorded in a configuration the limits in each direction is recorded. A central pivoting point is calculated and each point of the object when placed in a 3D World will have a range x,y,z limit against each axis scale. The uv values for each axis is calculated by the axis value divided by the axis range and is therefore a decimal fraction since the range equates to 1.0. In this way as you move and scale an object the uv point for each axis can be used to scale and position that point accordingly.

I’m an amateur in this area but that is how I got my head round it and at times I have to re-think it to make progress - doesn’t slot easily into my old grey matter.

The table of uvs corresponds to the table of vertices. So each uv coordinate has a corresponding vertex. It determines what part of the image is placed at that vertex. When the model is assembled, the uv coordinates of the vertices are interpolated into the faces so that the texture is mapped to the face.

It’s a little bit simpler with the old meshes so in experiments I would start with a mesh, but craft works in essentially the same way.

You can think of each model as being made up of triangles. Sometimes you can specify faces as quads, but I think that they are split further into triangles deeper down.

All my mesh code, available via the PseudoMesh class, comes with correct uv coordinates for putting textures on models.

Here’s a thing I did to understand the cube unwrapping. It turns out to be … unusual. I’ll comment further below, but this labels each cube face +X +Z etc.

displayMode(FULLSCREEN)

function setup()
    noSmooth()
    rectMode(CORNER)

    img=image(600,100)    
    setContext(img)

    fontSize(40)
    fill(255,0,0)
    --rect(0,0,100,100)
    text("+X", 50, 50)
    fill(33, 255, 0, 255)
    --rect(100,0,100,100)
    text("+Z", 150,50)
    fill(22, 0, 255, 255)
    --rect(200,0,100,100)
    text("-X", 250,50)
    fill(255, 230, 0, 255)
    --rect(300,0,100,100)
    text("-Z", 350,50)
    fill(255, 0, 167, 255)
    --rect(400,0,100,100)
    text("+Y", 450, 50)
    fill(0, 255, 221, 255)
    --rect(500,0,100,100)
    text("-Y", 550,50)
    setContext()

    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")

    scene = craft.scene()
    m = scene:entity()
    m.model = craft.model.cube(vec3(1,1,1))
    m.material = craft.material("Materials:Specular")
    m.material.map = img  

    uvs1={}
    c=0
    for x=1,6 do
        table.insert(uvs1,vec2(c/6,1))
        table.insert(uvs1,vec2((c+1)/6,1))
        table.insert(uvs1,vec2((c+1)/6,0))
        table.insert(uvs1,vec2(c/6,0))
        c=c+1
    end
    m.model.uvs=uvs1
    
    s = scene:entity()
    s.model = craft.model.cube(vec3(1,1,1)/10)
    s.material = craft.material("Materials:Basic")
    s.position = vec3(0,0,1)

    viewer = scene.camera:add(OrbitViewer, vec3(0), 10, 0, 2000)
    viewer.camera.farPlane=3000
end

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

function draw()
    update(DeltaTime)
    scene:draw() 
    sprite(img,WIDTH/2,HEIGHT-100)   
end

This is just Dave’s program fiddled, of course, but I did fix the clockwise counter-clockwise thing.

@LoopSpace what I don’t know/see/understand is first, it seems to work in quads, at least for cube and plane. Second, to know other than by trial and error what’s going where, I feel like I need to know the order of the vertices, i.e. how the cube is unwrapped. I guess I can print them. Maybe I will, but this little program settles the issue for me for the cube.

Fascinatingly hard to think about.

Thanks!

@Bri_G I’m afraid I don’t get the picture you have in your head … but I’m getting one of my own. We’ll keep on plodding, I guess.

i inspected the positions table of a cube. it consists of 24 items, drawing 6 unclosed quads, one for each face, each traced counterclockwise. each quad is traced top left, top right, bottom right, bottom left.

since we do see triangles, for example in the plane, i wonder what’s really going on with cube and quads. i hypothesize that we could explode the cube by adjusting the coords in the positions table … unless there’s another controlling structure in there. but there may not be … the whole point of how 3d works is tris and quads.

interesting!

@RonJeffries - not sure it’s relevant to uv’s but @ignatz wrote a few introductory books for Codea learners, one of these was the basics of 3D and I believe he wrote a more advanced one. If you haven’t seen them they are linked to in the wiki.

Super! This looks real good. Thanks!

The good news is that the 3D book is very good if you’re doing your own 3D. It predates Craft, however, which changes a lot of the details.

If I want to write anything about Codea (or anything) @Ignatz sets a high bar for quality here. Very nice.

@RonJeffries - sounds like a good opportunity to write a good intro doc to Craft, hmmmm.

@RonJeffries

I feel like I need to know the order of the vertices, i.e. how the cube is unwrapped.

You absolutely do. The uv table and the vertices table correspond, so the first element in the uv table is associated with the first element of the vertices table. And so on.

@RonJeffries Heres something that might help a little with cube information. Slide the ind parameter. It will position a sphere at the corner of the cube and show the position, uvs values for that corner. Also, the indices are shown for the 4 cube corners. Those correspond to the 3 triangle corners that make the 2 triangles that make up each side of the cube.

displayMode(STANDARD)

function setup() 
    font("Courier")
    textMode(CORNER)
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")
    
    scene = craft.scene()
    m = scene:entity()
    m.model = craft.model.cube(vec3(2,2,2))
    m.material = craft.material("Materials:Specular")
    m.material.map = readImage(assets.builtin.Blocks.Trunk_Top)  
    
    tabP={}
    print("Positions ==========")
    for a,b in pairs(m.model.positions) do
        table.insert(tabP,b)
        print(b)
    end
    
    tabU={}
    print("uvs ==========")
    for a,b in pairs(m.model.uvs) do
        table.insert(tabU,b)
        print(b)
    end
    
    tabI={}
    print("indices ==========")
    for a,b in pairs(m.model.indices) do
        table.insert(tabI,b)
        print(b)
    end
        
    viewer = scene.camera:add(OrbitViewer, vec3(0), 10, 0, 2000)
    viewer.camera.farPlane=3000
    viewer.rx=20
    viewer.ry=-20
    parameter.integer("ind",1,24,1,show)   
end

function show()
    if sp~=nil then
        sp:destroy()
    end
    createSphere(tabP[ind])
end

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

function draw()
    update(DeltaTime)
    scene:draw() 
    fill(255)
    
    v=tabP[ind]
    str=string.format("IND %2d     x%3d   y%3d   z%3d",ind,v.x,v.y,v.z)
    text(str,200,HEIGHT-100)
    
    v=tabU[ind]
    str=string.format("UVS %2d     x%3d   y%3d",ind,v.x,v.y)
    text(str,200,HEIGHT-150)
    
    if ind>20 then s=31
    elseif ind>16 then s=25
    elseif ind>12 then s=19
    elseif ind>8 then s=13
    elseif ind>4 then s=7
    else s=1 end
    
    str=string.format("INDICES %2d %2d %2d  %2d %2d %2d",
        tabI[s],tabI[s+1],tabI[s+2],tabI[s+3],tabI[s+4],tabI[s+5])
    text(str,200,HEIGHT-200)
end

function createSphere(p)
    sp=scene:entity()
    sp.position=vec3(p.x,p.y,p.z)
    sp.model = craft.model.icosphere(.1,2)
    sp.material = craft.material("Materials:Specular")
    sp.material.diffuse=color(255,0,0)
end

Cute, Dave. I coded a thing that traced the faces one after another, a little cube bouncing around the corners. It’s doing what I thought it was doing, and I’m still not clear in my mind on the “why”, or maybe the “whazzup”.

Seems like, with the cube, at least, the UV mapping is quad-based, even though the cube, I think, is two tris per face. I’m wondering “how does it know”. But mostly, I know what to do and maybe one day it’ll come to me.

:slight_smile: