Help with uvs and indices in Craft please

I’m proud to say I’ve managed to turn the voxel terrains that @dave1707 generated into Craft models you can walk around on; run the project and tap “spawn player” and you’ll see it in action.

I’m abashed to say that for the life of me I can’t figure out how to do the uvs and/or the indices so that the texture shows up properly.

It seems like I should be able to either have the entire model just show the entire texture as one big square, or tile the texture across the model as many times as I like—but no matter how I play with it all I get are stripes.

Please help (and kindly don’t laugh at me setting the uvs to some bizarre number, I was just at my wits’ end and trying anything)!

I’m also ready to learn a little bit about OpenGL ES, including UV, and I suggest you take a look at some of Ignatz’s previous tutorials on Codea’s mesh drawing, which craft is basically similar to. craft.model.uvs is mesh.texCoords.

@binaryblues it seems like texture mapping is affected both by uvs and by Craft’s unique indices system, which I don’t think Ignatz ever addressed. I’m hesitant to consult a tutorial that might confuse me even more.

Yes, Uvs, normals and vertices are one-to-one, and the index table?model.indices? gives the order in which the vertices are arranged, in groups of three, to form triangles, so the index table also determines the order in which UVS and normals are used, exactly the same order in which the vertices are used.

Maybe I’m wrong but it seems to me that indices add a non-superfluous layer of complexity here, meaning I’m not going to get the bugs worked out of my code without guidance that includes them explicitly.

As far as I can tell managing indices in practice is such a headache that none other than @LoopSpace needed help with it, so I’m not confident I’m gonna get it right on my own.

@UberGoober Working with the uvs, positions, indices, etc tables aren’t that hard. The hard pard is remembering how they work. You have to dig into how those tables work with each other. My problem is I forget how they work when I don’t use them for awhile. I then have to find one of my programs and look thru it, play with it, to remember what’s going on. As mentioned earlier by binaryblues, it’s all triangles and tables for the triangles. Tables of the positions, tables of what positions make up the triangles, tables of how the light is supposed to reflect off the triangles, tables of the image parts on each triangle etc. Someday I should create an example that goes into details of how they work. I haven’t done that so far because I figured nobody else wanted to know that much. That might help me as much as someone else when working with the tables.

@dave1707 I sure could use that!

And I feel like saying “it’s all just triangles” dramatically understates the challenge here, because if you put the same indices in one order the triangle looks one way and in another order it looks another way and in some orders it doesn’t appear at all.

@UberGoober It is just triangles, but the x,y,z positions have to be listed in a certain direction. I always use a counterclockwise direction. The other tables should also match that direction.

PS. I’m working on an example to explain this.

"UberGoober - the mesh may be laid out in the same way that the obj class for models was originally defined where rules for order of vertices and layout/orientation of triangles were defined. This may help:

Wikipedia Obj model

@UberGoober Now I know why I never created a good explanation for the tables. There’s a lot going on but once you understand it, it’s not too bad. Here’s an example where I tried to comment things, but there’s still more to it. The points P1 thru P4 show the counterclockwise direction that I use. The triangles are also created counterclockwise. Near the bottom is the layout of how to overlay an image onto a rectangle created from triangles. An image value goes from 0 to 1 in both x,y directions. You can use fractional values to use part of an image if you want. So basically larger things are created by the buildup of triangles. The tables just get bigger based on how many triangles you use.

I know I’m not explaining this very well, so if you have other questions just ask.

viewer.mode=FULLSCREEN

function setup()  
    assert(OrbitViewer, "Please include Cameras as a dependency")        
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(72, 195, 234)
    skyMaterial.horizon=color(160, 226, 117)
    scene.sun.rotation=quat.eulerAngles(40,60,30)
    v=scene.camera:add(OrbitViewer,vec3(0,0,0), 100, 0, 1000)
    v.ry=180
    v.camera.farPlane=1000
    fill(255)
    
    -- coordinates of triangles for creating a rectangle
    p1=vec3(-10,0,0)
    p2=vec3(10,0,0)
    p3=vec3(10,10,0)
    p4=vec3(-10,10,0)
    
    -- create normals table. used for reflections and brightness of image texture    
    nor1={}
    norm1(p1,p2,p3) 
    norm1(p1,p3,p4)
       
    pos={p1,p2,p3,p1,p3,p4}  -- triangle coord in counter clockwise direction
    
    -- double the size of pos table gives both sides
    ind={1,2,3,4,5,6,6,5,4,3,2,1} -- both sides visible when rotated
    --ind={1,2,3,4,5,6} -- one side visible when rotated
    
    -- define image values, vec2 for each point in pos table
    uvs={vec2(0,0),vec2(1,0),vec2(1,1),vec2(0,0),vec2(1,1),vec2(0,1)}
    
    createTile1()
end

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

function showText()
    sprite(asset.builtin.Blocks.Missing,WIDTH/2,200)
    text("(P1= -10,0,0)",WIDTH/2-100,HEIGHT/2+200)
    text("(P2= 10,0,0)",WIDTH/2+100,HEIGHT/2+200)
    text("(P3= 10,10,0)",WIDTH/2+100,HEIGHT/2+300)
    text("(P4= -10,10,0)",WIDTH/2-100,HEIGHT/2+300)  
    text("triangle 1= P1,P2,P3  triangle 2= P1,P3,P4",WIDTH/2,HEIGHT/2+160)  
    text("(0,0)",WIDTH/2-70,120)
    text("(1,0)",WIDTH/2+70,120)
    text("(1,1)",WIDTH/2+70,280)
    text("(0,1)",WIDTH/2-70,280)    
end

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

function createTile1()
    r=scene:entity()
    r.model = craft.model()
    
    r.model.positions=pos
    r.model.indices=ind

    -- uvs table defines image area to use, values between 0 and 1
    r.model.uvs=uvs

    -- color table used with Materials Basic
    c=color(255)
    r.model.colors={c,c,c,c,c,c} 
              
    -- normals table used with Material Standard and Specular for reflection
    r.model.normals=nor1 
    
    r.material = craft.material(asset.builtin.Materials.Specular)      
    r.material.map=readImage(asset.builtin.Blocks.Missing)
end

function norm1(a,b,c)
    v1=b-a
    v2=c-b
    n1=v1:cross(v2)
    table.insert(nor1,n1)
    table.insert(nor1,n1)
    table.insert(nor1,n1)
end

@dave1707 I think that’s just what I needed, thank you so much.

I expanded your demo to include a more complex grid, and I think I did it right.

If you are so inclined take a look and tell me if I did anything weird.

@UberGoober Looks like you got things right. I tried your uvs3 table that expanded a single image over all 4 squares. That looked good. Try changing the z values of some of the squares a little (+ or -) so it looks like a crumpled up piece of paper.

PS. That’s where you’ll notice the different brightness of parts of the image from the normals table.

I think I understood everything except the normals code. Can you tell me what the normals code is doing?

Also @John I highly recommend something like this project as an example Codea project, because this stuff is near impossible to suss without help otherwise.

@UberGoober The only thing I know about the normals table is that it controls how light reflects off the surface. I’m not even sure if that’s a good explanation. The normal function I use I found with Google. It seems to work but I don’t understand what the cross statement is really doing. I guess it has something to do with how the surface of a triangle is facing a light source giving a reflection. If you figure out more, post it here.

@UberGoober I noticed in your uvs2 table you have values above 1. The layout of an image normally goes from 0 to 1 or any value in between. Not sure how values of 2 are OK unless the range your using is still 0 to 1. I’ll have to play with that to see what’s really happening.

This is how I would do the uvs2 table. Just the same values for each of the 4 squares.

    uvs2={
        --ll square:
        vec2(1,1),vec2(0,1),vec2(0,0),
        vec2(1,1),vec2(0,0),vec2(1,0),
        --lr square:
        vec2(1,1),vec2(0,1),vec2(0,0),
        vec2(1,1),vec2(0,0),vec2(1,0),
        --ul square:
        vec2(1,1),vec2(0,1),vec2(0,0),
        vec2(1,1),vec2(0,0),vec2(1,0),
        --ur square:
        vec2(1,1),vec2(0,1),vec2(0,0),
        vec2(1,1),vec2(0,0),vec2(1,0)
    }

@UberGoober Try this link and scroll near the bottom. You’ll see my code that created different tables to texture a sphere.

https://codea.io/talk/discussion/9133/craft-sphere-texture#latest

@dave1707 I think we actually both did it wrong.

I liked your way of doing it so much because it made creating the indices so easy, but the problem is that it requires putting multiple vertices in the exact same place, which I think is not the way it’s supposed to be done.

I think the entire point of the index table is that vertices can be reused, keeping the vertex count lower, even though it’s a bigger hassle to get right—so I’ve rewritten both of our examples to demonstrate the models being drawn without duplicate vertices.

@UberGoober I’m not saying the way I did it is correct, it works and was easy to do. This whole thing started long ago when I was trying to texture an icosphere. There wasn’t anyway to do it. After a lot of trial and error, I figured out a way and the results are in the link just above for the sphere texture. I kind of used those steps any time I did other textures.

In your latest example, it there a reason why the ? aren’t all in the same orientation or was that on purpose. Maybe you can do a better example and explanation of how all of this fits together. You seem to be good at that based on all of your previous examples of different things.

You should try doing a texture on a cylinder and then a sphere. I have a cylinder example someplace that I’ll post.

Here it is. The diameter and number of sides can be changed.

viewer.mode=FULLSCREEN

function setup()  
    scene = craft.scene()
    v=scene.camera:add(OrbitViewer,vec3(px,100,pz), 1000, 0, 10000)
    v.camera.farPlane=10000
    pos={}
    
    cm = craft.material(asset.builtin.Materials.Basic)  
    --cm = craft.material(asset.builtin.Materials.Specular)  
    --cm = craft.material(asset.builtin.Materials.Standard)  
    rm=readImage(asset.builtin.Cargo_Bot.Startup_Screen)
    
    dia=100
    sides=10
    
    for a=359,0,-360/sides do
        x=math.cos(math.rad(a))*dia
        y=math.sin(math.rad(a))*dia
        z=200
        table.insert(pos,vec3(x,y,z))
        x=math.cos(math.rad(a))*dia
        y=math.sin(math.rad(a))*dia
        z=-200
        table.insert(pos,vec3(x,y,z))
    end
    
    s1=0
    for z=1,#pos-2,2 do
        p1=pos[z]
        p2=pos[z+2]
        p3=pos[z+3]
        p4=pos[z+1]
        nor={}
        norm(p1,p3,p4)
        norm(p1,p2,p3)        
        createTile(p1,p2,p3,p4,s1,s1+1/sides) 
        s1=s1+1/sides 
    end
    p1=pos[#pos-1]
    p2=pos[1]
    p3=pos[2]
    p4=pos[#pos]
    nor={}
    norm(p1,p3,p4)
    norm(p1,p2,p3)     
    createTile(p1,p2,p3,p4,s1,s1+1/sides)   
end

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

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

function createTile(p1,p2,p3,p4,s1,s2)
    local c=color(255, 255, 255, 255)
    r=scene:entity()
    r.model = craft.model()
    r.model.positions={p1,p3,p4,p1,p2,p3}
    r.model.indices={1,2,3,4,5,6,6,5,4,3,2,1}
    r.model.colors={c,c,c,c,c,c}
    r.model.uvs={vec2(s1,0),vec2(s2,1),vec2(s1,1),vec2(s1,0),vec2(s2,0),vec2(s2,1)}
    r.model.normals=nor
    r.material = cm
    r.material.map=rm
end

function norm(a,b,c)
    v1=b-a
    v2=c-b
    n1=v1:cross(v2)
    table.insert(nor,n1)
    table.insert(nor,n1)
    table.insert(nor,n1)
end

The symbols being flipped is a drawback of not duplicating the vertices. Because each vertex can only have one uv definition, to repeat the symbol without strangeness requires having the center vertex, for example, always correlate with the upper right corner of the image, no matter what triangle it’s a part of.

Which might be a drawback of more efficient vertex management, but otoh I think repeating the image exactly is more easily doable with one of the parameters on the model material—I think the renderer can tile the image all on its own regardless of vertices.

This is all very hard and takes a lot of trial and error, I agree, which is why I asked for help and am very glad you chimed in.

@UberGoober Just some info for you. The table sizes of a level 5 icosphere is 61,440 entries. So it would take a lot of work if you’re going to remove duplicates to reduce the size of the tables. Duplicates are in the tables created by Craft, so that’s why I didn’t worry about them when I did my code examples.