Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

I've added a .ply importer to @Ignatz 's object viewer (for vertex painting support)

in Code Sharing Posts: 2,020

I've hacked together a .ply importer as an extension to the .obj 3D model viewer/ importer that @Ignatz made.

.ply (aka Stanford) is, like .obj, a plaintext, easily parse-able 3D model format, supported by many 3D programs, including Blender.

So why bother adding support for it on top of .obj?

One advantage that .ply has over .obj, is that it can store colour information for each individual vertex, whereas .obj files only store colour values for each material that you use.1 This means that you can use Blender's rather nice "vertex painting" mode. When you import the model into Codea, Open GL takes over and shades each face of the model by smoothly interpolating between the colours for each point on the face.

vertex painting

This is useful if you have a model that would just look better with very smooth, resolution-independent shading, or you can't be bothered to add a texture, or you're running into memory/processor constraints and want to cut down on textures. You could also combine the vertex painting with a texture. This again could be useful if you want to reuse the same texture image over and over, but make it look a little different each time (and add some resolution-independence to how the object is rendered). eg:

vertex painting combined with texture

I'm going to clean up the code a little before I post it. It works as a sub-class of @Ignatz 's OBJ class, so it uses all of the I/O code that @Ignatz wrote. The .ply parser is modified from the code posted a couple of years back by @aciolino . Thanks/ apologies to both of them, I hope neither of them mind me mashing their projects together like this!


  1. There is apparently a flavour of .obj that does support having colour information for each vertex, but I think that's a somewhat non-standard version of .obj, and Blender cannot export those kind of .obj files. You could also "bake" the vertex painting into a raster image and export it as a texture, but that would defeeat the purpose somewhat. 

Comments

  • IgnatzIgnatz Mod
    Posts: 5,396

    No problem, this is what our community is all about, helping and sharing, thanks for your efforts

  • edited March 2015 Posts: 2,020

    Here's the code. This is to be added to @Ignatz 's 3D model viewer v5: code, blog post with documentation

    -- PLY reader, with support for textures, normals, and vertex painting
    -- CREDITS: adapted by Yojimbo2000 from Antonio Ciolino's parser
    -- and appended to Ignatz 's .obj reader.
    -- INSTALL: This tab must be to the right of OBJ tab. It inherits everything except ProcessData() from the OBJ class
    -- USAGE: if using a texture image, you have to manually add it within the header of the .ply file, syntax:
    -- comment texture imageName imageURL
    -- nb this uses comment so that the syntax doesnt break.
    -- If not using a texture the .ply file can be used as is.
    -- NOTES: currently only supports triangular faces. If unsure, add a triangulate modifier to your object before export from blender.
    -- currently only supports a single object with a single texture.
    
    function ShowModel() --replaces the function in Main
        state=State.Loading
        if string.find(Models[Choose].url, ".ply", -4) then
            newModel=PLY(Models[Choose].name,Models[Choose].url)
        else
            newModel=OBJ(Models[Choose].name,Models[Choose].url)
        end
    end
    
    PLY = class(OBJ)
    
    function PLY:ProcessData()
        --PROCESS HEADER
        local elements={}        -- elements of this file
        local endheader = string.find(self.data, "end_header")
        local header = string.sub(self.data, 1, endheader-1)
        local body = string.sub(self.data, endheader+11, 999999)
        local tex = {}
        local hasNormals, hasTexCoords, hasColours  = false, false, false
        local tbl,token,texture,entry,items,vtx
        -- make a table to interpret the header. this is the guide to read the file.
        for token in string.gmatch(header, "[^\r\n]+") do --iterator, longest possible string of anything not a return
            -- pack each string and process
            -- special case find the texture file if it exists
            local i= string.find(token, "comment texture")
            if (i~=nil) then
                texture=string.sub(token, string.find(token, "comment texture")+16, 99)
                tex=split(texture,"%s")
            end
    
            i=string.find(token,"element")
            if (i~=nil) then
                --we have a new element
                tbl = split(token, " ")
                entry= { name=tbl[2], value=tbl[3], properties={}, lines={} }
                table.insert(elements, entry)
            end
            i=string.find(token, "property")
            if (i~=nil) then
                tbl = split(token, " ")
                --indexed = 0
                --[[
                if tbl[2] == "list" then
                -- this data is a lookup list. unused, we are hardcoding our lookups
                --against texcoords and faces.
                -- later we should deal with uchar, list data
                -- indexed = 1
            end
                ]]
                if tbl[3] == "nx" then hasNormals=true
                elseif tbl[3] == "s" then hasTexCoords=true
                elseif tbl[3] == "red" then hasColours=true
                end
                --prop = { property=tbl[table.maxn(tbl)], index=indexed} (me:not sure what this does)
                --table.insert(entry.properties, prop)
            end
        end
    
        --PROCESS BODY
        local newMesh=mesh()
        local points = {}        --each unique point, eventually used to build faces
        local pcolor={}        --colors, texCoords, normals, if defined, per vertex.
        local ptex={}
        local pnorm={}
    
        local vertices={}       --tables that get sent to the mesh
        local colors={}
        local texcoord={}
        local normals={}
    
        -- read through the body string using the header as a guide
        -- all elements have a value
        local lines=split(body, "[\r\n]")
    
        -- lines=GetTable(body, "[^\r\n]+")
    
        local offset=0
        for k,v in pairs(elements) do
            v.lines = {}
            -- get value number of lines and process
            for i=1, v.value do
                table.insert(v.lines, lines[i+offset])
            end
            offset=v.value
            --process verts these are points?
            if v.name=="vertex" then
    
                for lk, lv in pairs(v.lines) do
                    local pt = vec3(0,0,0)
                    -- items=GetTable(lv, "[^%s]+") --"([-+]?[0-9]*\.?[0-9]+)")
                    items=split(lv, "%s") --"([-+]?[0-9]*\.?[0-9]+)")
    
                    --for k,v in pairs(items) do  --needss to be aa case stmt later
                    pt.x=items[1]
                    pt.y=items[2]
                    pt.z=items[3]
                    table.insert(points, pt)
                    local p=3 --place holder
    
                    if hasNormals then
                        local no = vec3(0,0,0)
                        no.x = items[4]
                        no.y = items[5]
                        no.z = items[6]
                        table.insert(pnorm, no)
                        p=6
                    end
                    if hasTexCoords then
                        local tc=vec2(0,0)
                        tc.x = items[1+p]
                        tc.y = items[2+p]
                        table.insert(ptex,tc)
                        p = p + 2
                    end
                    if hasColours then
                        --colors of the verts go here
                        local r,g,b
                        r = items[1+p]
                        g = items[2+p]
                        b = items[3+p]
                        --  a = items[7]+0
                        table.insert(pcolor, color(r,g,b,255))
                    end
    
                end
            end
    
            if v.name=="face" then
                --takes us from points to faces...we pack these into the mesh!
                for lk, lv in pairs(v.lines) do
                    --items=GetTable(lv, "[^%s]+")
                    items=split(lv, "%s")
    
                    vtx =vec3(0,0,0)
    
                    for a=2,4 do
                        vtx = items[a]+1
                        table.insert(vertices, points[vtx])
                        if hasNormals then table.insert(normals, pnorm[vtx]) end
                        if hasTexCoords then table.insert(texcoord, ptex[vtx]) end
                        if hasColours then table.insert(colors, pcolor[vtx]) end
                    end
    
                    --if we have teturecoords, read then here (me: is this a different version of .ply that places texCoords in the face section of the file?)
                    --[[
                    if (#items > 4) then
                    uv=vec2(items[6], items[7])
                    table.insert(texcoord, uv)
                    uv=vec2(items[8], items[9])
                    table.insert(texcoord, uv)
                    uv=vec2(items[10], items[11])
                    table.insert(texcoord, uv)
                end
                    ]]
                end
            end
        end
    
        --print ("final points "..#points)
        -- print ("final verts "..#vertices)
        --  print ("final colors "..#colors)
        -- print ("final texcoord "..#texcoord)
    
        newMesh.vertices=vertices  -- a vec3 of points
        if hasColours then newMesh.colors=colors end
        if hasTexCoords then newMesh.texCoords=texcoord
            -- newMesh:setColors(255,255,255,255)  --IF WE HAVE A TEXTURE, IGNORE COLOR FOR NOW
            newMesh.settings={map=tex[1]} --add name of texture
        end
        if hasNormals then newMesh.normals=normals end
    
        self.m={}
        self.m[1]= newMesh              --
        --download images if not stored locally
        self.MissingImages={}
        -- for i,O in pairs(self.mtl) do
        if #tex>0 then
            local y=readImage(OBJ.imgPrefix..tex[1])
            if not y then self.MissingImages[#self.MissingImages+1]={tex[1],tex[2]} end
        end
        --  end
        if #self.MissingImages>0 then self:LoadImages()
        else self.ready=true return end
    
    end
    
    function split(str, pat) --could maybe use the split functions in the obj class
        local t = {}
        local fpat = "(.-)" .. pat
        local last_end = 1
        local s, e, cap = str:find(fpat, 1)
        while s do
            if s ~= 1 or cap ~= "" then
                table.insert(t,cap)
            end
            last_end = e+1
            s, e, cap = str:find(fpat, last_end)
        end
        if last_end <= #str then
            cap = str:sub(last_end)
            table.insert(t, cap)
        end
        return t
    end
    
  • edited March 2015 Posts: 2,020

    Compared to .obj:

    Pros

    • supports colour-per-vertex and therefore vertex-painting (this was the main reason I added it)

    • .ply is held in a single file (.obj is split across two) and therefore less file wrangling needed. If there is no texture, you can just use the .ply file as is. Moreover, adding the URL to the .ply file doesn't break the .ply syntax, as it uses a "comment" field. So the workflow between Codea and Blender is a little simpler.

    Cons

    • .ply (or at least the .ply Blender exports) doesn't seem to store data for various material properties such as reflectivity etc.

    • This only supports single objects with single textures. The .ply that Blender exports doesn't seem to acknowledge multi-object or multi-texture scenes at all. For complex rigs, .obj is the way to go

    • The .ply exporter in Blender is a little less fully featured. It doesn't have a triangulate option like the .obj exporter has, so you have to add a Triangulate modifier before you export (you don't have to click "apply" on the modifier though).

  • Posts: 2,020

    OK, I've added a gist here that has everything you need to test it, including the "vertex painting" object used for the screen shot above. Just download the "3D Model Importer", you don't need to download the .ply file, as the Importer will automatically download it to your global data.

    https://gist.github.com/Oliver-D/49460797dbc81b24e33d

  • Jmv38Jmv38 Mod
    edited March 2015 Posts: 3,295

    @yojimbo2000 when i choose the object and press load, there is a brief change in the output panel (orange text) then the program seems to restart and nothing is showing on the screen.
    I guess there is an error but i cant read it because the message is erased.

  • edited March 2015 Posts: 2,020

    Did you get the code from the gist I posted?

    https://gist.github.com/Oliver-D/49460797dbc81b24e33d

    This one, not the links at the top of the post. I made a couple of changes to the gist right after I posted, which you might not have if you downloaded it right away.

    Are you selecting the last model, "vertex paint"? (Choose slider all the way to the right)

    It would be great if you could Comment out the two lines towards the end of configureModel() and let me know what the error is

       -- output.clear()
       -- for i=1,#Models do print(i,Models[i].name) end
    
  • Jmv38Jmv38 Mod
    edited March 2015 Posts: 3,295

    i get a 'shader compile error' with last model

    ERROR: 0:5: Attempt to redeclare 'reflect' as a variable
    ERROR: 0:49: Attempt to use 'reflect' as a variable
    ERROR: 0:50: Use of undeclared identifier 'totalColor'
    ERROR: 0:51: Use of undeclared identifier 'totalColor'
    
    

    I tried to download again, but still get bad result.
    Thanks for helping me out!

  • Posts: 2,020

    That's weird. I redownloaded the code from the gist into a separate project to check, I don't get any of those errors. The "attempt to redeclare reflect as a variable" error is odd, reflect is only declared once in the shader. Is it possible that the code got messed up in the paste into project somehow? How did you get the code into Codea? (ie was it with the in-app browser "copy" button from the gist, or some other method?)

  • Posts: 154

    It loads ok for me. But for some reason no models show up.
    It looks like this, no matter which model i choose:

    http://s15.postimg.org/pedgnauh7/image.jpg

  • Posts: 2,020

    The more complex models (like the shuttle) will take a few seconds to download the first time you load them. 6, the vertex painting example, should be quick, because there's no texture, so it only has to request a few kb from GitHub. @juce you don't see anything when you load number 6?

  • edited March 2015 Posts: 154

    @yojimbo2000, sorry, i spoke too soon. If i comment out lines 64 and 65, then i see the same error as @Jmv38 for model 6:

    ERROR: 0:5: Attempt to redeclare 'reflect' as a variable
    ERROR: 0:49: Attempt to use 'reflect' as a variable
    ERROR: 0:50: Use of undeclared identifier 'totalColor'
    ERROR: 0:51: Use of undeclared identifier 'totalColor'
    
  • edited March 2015 Posts: 154

    It seems that reflect is a reserved word, and perhaps cannot be used as a variable. I did a global replace of it with reflectx and now it all works like a charm. Very cool! Awesome 3D models too.

  • edited March 2015 Posts: 2,020

    @juce glad you got it working and thanks for the bug report. I wonder why I don't have that issue? Are you a Codea beta tester? I'm on Codea 2.2(35) and iOS 8.1.3. Did I read somewhere that the shader specification changed at some point? @Jmv38 could you also try to do a search replace on reflect?

  • Posts: 2,020

    I made the vertex painting model very quickly in Blender, and it has some overlapping faces because the handwriting-style fonts overlap, so it does flicker a bit. Probably best to use a non-joined-up typeface for that kind of thing

  • Jmv38Jmv38 Mod
    Posts: 3,295

    @yojimbo2000 @Juce the trick worked great. I wonder how it could work on your side with the reserved word? Anyway, these models are AWSOME. Thanks for sharing.

  • Jmv38Jmv38 Mod
    Posts: 3,295

    @yojimbo2000 maybe you could update the gist with the new code?

  • IgnatzIgnatz Mod
    edited March 2015 Posts: 5,396

    I haven't run it yet, but I saw the models include the space shuttle, ME262, spitfire etc that I put together a while back. As I recall, the Merc(edes) model was broken.

    There is a bit of an art in selecting models that work well in Codea. I generally looked for models that weren't too large (eg files size below 1 nb), didn't have complex texturing, and didn't require animation. And then not all models work, for one reason or another.

    I've since added quite a few more models for use in my 3D dungeon, including weapons, a metal cage, a couple of nasty spiders and zombies etc.

  • edited March 2015 Posts: 2,020

    OK, the gist has been updated. Does the totalColor error also disappear once you change the name of the reflect variable? I wonder why I don't get that error.... Do different iPads run slightly different versions of Open GL ES? I'm on an iPad Air

  • edited March 2015 Posts: 154

    totalColor error disappears because it used reflect in its definition, so once that was sorted out, totalColor was good too. I am on iPad 2, btw. (The old one, not Retina). iOS 7.1.2

  • IgnatzIgnatz Mod
    Posts: 5,396

    Actually, I think the reflect error was caused by either a Codea or iOS update a while back. That old code hasn't been updated.

  • Posts: 2,020

    I think I might have found the answer here:

    http://codea.io/talk/discussion/comment/39186/#Comment_39186

  • Posts: 2,020

    Thanks to all for the bug reports. The .ply parsing code has not been plumbed in in a very elegant way (my bad) but it seems to work

  • Posts: 1,595

    As long as it works that's all that matters =D>

  • Posts: 2,020

    Do you mind if I name the villain in my next game Zordon?

  • IgnatzIgnatz Mod
    Posts: 5,396

    He's gone so he won't mind

  • Posts: 2,042

    @Ignatz, did you have to delete his posts by hand, or does Vanilla do that now when you delete a user?

  • IgnatzIgnatz Mod
    Posts: 5,396

    Vanilla gives you a choice of deleting posts or not.

    In this case, I went for the head shot.

Sign In or Register to comment.