m.model.position vs obj

@Simeon Is there a reason why the m.model.positions file doesn’t contain all the position values. Here’s an example showing the positions (white spheres) that make up the Capsule model using m.model.positions. There are just a few white spheres at the top and bottom of the Capsule. If you tap on the drawObj parameter, the obj file is read and all the positions are shown for the Capsule.

viewer.mode=STANDARD

function setup()
    assert(OrbitViewer, "Please include Cameras as a dependency")
    
    parameter.action("drawObj",drawObj)
    
    scene = craft.scene()
    m = scene:entity()
    m.model = craft.model(asset.builtin.Primitives.Capsule)
    m.material = craft.material(asset.builtin.Materials.Standard)
    m.material.map = readImage(asset.builtin.Blocks.Wood_Red)
    
    v = scene.camera:add(OrbitViewer, vec3(0,0,0), 5, 0, 2000)
    v.rx=60
    
    msg="vertices drawn (white spheres) using model positions file"
    for a,b in pairs(m.model.positions) do
        createSphere(b,.01)
    end
    
    fill(255)
end

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

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

function drawObj()
    str=readText(asset.builtin.Primitives.Capsule)
    for a,b,c in string.gmatch(str,"v (%g+) (%g+) (%g+)") do
        createSphere(vec3(a,b,c),.01)
    end
    msg="vertices drawn (white spheres) using model obj file"
end

function createSphere(p,s)
    local pt=scene:entity()
    pt.position=p
    pt.model = craft.model.icosphere(s,2)
    pt.material = craft.material(asset.builtin.Materials.Specular)
    pt.material.diffuse=color(255)
end

@dave1707 - could that be due to the model being based on calculated curves needing only the centre and first circle of vertices ? I know that’s a bit off the wall but most of my simpler models seem to show the spheres up correctly.

@Bri_G There’s no way the Capsule could be drawn with just the vertices that are used in the models.positions file. If those vertices were used, the Capsule would only have a diameter of the first circle of spheres. It’s the same with the other models. The models.positions files only have a fraction of the vertices of the obj file.

@dave1707 - I agree, I said it was off the wall. But, an obj can include curves but how they are interpreted/displayed I’m not sure. My thought ran along the lines of capsule top with lines/triangles. Connect to curve then lines for a tube before connecting to a curve then lines to bottom vertices. My thoughts were that the use of curved surfaces would disrupt the indices of the capsule.

Have you tried listing the indices to a tab to check?

@Bri_G I dumped the obj files to see what’s in them when I first started to look at this and noticed that the obj files had more positions than the model.positions file. You can see that with the Capsule example. I’m just curious why the model.positions and obj files are different.

@dave1707 - the links below to the Wavefront Wikipedia pages mentioning freeform structures and NURBS (splines) format may have the key.

https://en.wikipedia.org/wiki/Wavefront_Technologies
https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline

@Bri_G Not sure about those. It looked like the 3D objects were still within the bounds of the vertices points. Using just the model.positions values doesn’t seem to be enough to define the shape of the Capsule.

@dave1707 That’s odd since it has to have those positions in the model for it to display correctly. I wonder if there’s an issue properly reading the positions out of the model itself…

@dave1707 @John - I downloaded a basic capsule.obj from the net and it showed the same effect. A central sphere and a small circle of spheres at the poles of the capsule. This was a basic model, just searched for it for comparison. Note had to build a txt file for it as the forum won’t permit attaching .obj files. Messy, but it works - just save as a .obj file on a PC or Mac. Maybe should have zipped it - doooh!!!

In this case it was 8 top 8 bottom spheres, from a potential of 42. Obj file attached.

@dave1707 I tried your example and found that for some reason the capsule in question has 2 separate materials. This means that the model is made out of 2 submeshes (the submeshCount property returns 2).

By changing the second submesh material via

m:get(craft.renderer):setMaterial(craft.material(asset.builtin.Materials.Standard), 2)

You can see that the caps on the top and bottom are separate. This was probably due to an export issue when I made the capsule model

The model class doesn’t really give a good API for accessing individual submeshes though so it’s not very obvious

@John The watercraft models are the same way. You don’t get all of the vertices positions if you use the model.positions file. If you try using those models in the Capsule code you’ll see different white sphere positions.

@dave1707 Yes this happens any time a model has multiple materials, internally a model is built from several submeshes, each one with its own material. I can add a model.submeshIndex property that would let you access the additional submeshes directly for this kind of thing

@John - it looks like the addtion for submesh indexing would be a good idea. Also, would it be useful to add a model.submeshCount to identify the number of submeshes in the model - or is that already present?

A question for you - many models have multiple textures to add features like damage, flashing lights etc. also cloud levels round planets - how do we approach models of this type in Craft?

Edit: I know you can add say cloud textures by using two spheres one for the terra and one for the cloud with the latter at higher radius.

@John Here’s a program that dumps the mtl and obj files for watercraftPack__1. You can see in this dump that the mtl file shows 7 different material groups. If you scroll through the obj file, you’ll see the “v” records which are the vertices positions values. All of those should be in the m.model.positions file, but aren’t.

I show the record counts for some of the different records in the obj file.

So I’m not sure why the m.model.positions files for the watercraft, primitives, and some other models don’t have the correct number of vertices positions in the positions file.

Slide your finger up/down to scroll.

Edit. Corrected the code for the fCnt.

viewer.mode=STANDARD

function setup()
    textMode(CORNER)
    fill(255)
    tab={}
    
    table.insert(tab,"\
")
    table.insert(tab,"===== The following lines are from the mtl file =====\
")
    table.insert(tab,"\
")
    
    str=readText(asset.builtin.Watercraft.watercraftPack_001_mtl)
    cnt=0
    for a in string.gmatch(str,"(.-\
)") do
        cnt=cnt+1
        table.insert(tab,cnt.."    "..a) 
    end
    print("mtl file size  "..cnt)
    
    table.insert(tab,"\
")
    table.insert(tab,"===== The following lines are from the obj file =====\
")
    table.insert(tab,"\
")
    
    str=readText(asset.builtin.Watercraft.watercraftPack_001_obj)
    cnt=0
    vCnt,vtCnt,vnCnt,fCnt=0,0,0,0
    for a in string.gmatch(str,"(.-\
)") do
        cnt=cnt+1
        table.insert(tab,cnt.."    "..a) 
        if string.sub(a,1,2)=="v " then
            vCnt=vCnt+1
        elseif string.sub(a,1,2)=="vt" then
            vtCnt=vtCnt+1
        elseif string.sub(a,1,2)=="vn" then
            vnCnt=vnCnt+1
        elseif string.sub(a,1,2)=="f " then
            fCnt=fCnt+1
        end
    end
    
    print("obj file size  "..cnt)
    print("== Count of obj records ==")
    print("#v  "..vCnt)
    print("#vt "..vtCnt)
    print("#vn "..vnCnt)
    print("#f  "..fCnt)
    
    rec=scroll(tab)
end

function draw()
    background(0)
    rec:draw()
end

function touched(t)
    rec:touched(t)
end

scroll=class()

function scroll:init(tab,len)
    self.dy=0
    self.h=0 
    self.fs=fontSize()
    self.tab=tab
    self.sz=(HEIGHT/self.fs-2)
    if len then
        self.sz=len
    end
end

function scroll:draw()
    local st=(self.dy/self.fs+1)//1
    local en=math.min(#self.tab,st+self.sz)
    for z=st,en do
        text(self.tab[z],20,HEIGHT-(z-st)*self.fs-50)
    end
end

function scroll:touched(t)
    if t.state==BEGAN then
        self.h=t.y
    elseif t.state==CHANGED then
        self.dy=math.max(0,self.dy+t.y-self.h)       
        self.h=t.y  
    end
end

@dave1707 - have you got the f count wrong the counter definition isn’t used in the count loop as you use vnCnt for it. Correcting I got 756 f instances?

@Bri_G Good catch. I did a copy and paste and must have missed that. I corrected the code.

@dave1707 - thanks for that. Good to know I’m not the only one that misses issues from cut and paste. Frequently catches me out and loses me time chasing them up. This is a rarity for you !!!

@Bri_G I was in a hurry to get that posted before I left to do something else. Normally I try to do just one copy/paste and make whatever changes need to be made before doing the next copy paste. In that case, I guess I copied a lot of code and didn’t realize I missed that. Good to see that you caught my errors.