Converting meshes to models

I have quite a lot of code that is heavily invested in meshes which I’d like to adapt to use with Craft, so I came up with a little helper class that can be used as a drop-in for a mesh with a converter to a model for use with Craft.

Latest version can be found on github

PseudoMesh = class()

function PseudoMesh:init()
    self.vertices = {}
    self.texCoords = {}
    self.normals = {}
    self.colors = {}
    self.size = 0
end

function PseudoMesh:vertex(k,v)
    if v then
        self.vertices[k] = v
    else
        return self.vertices[k]
    end
end

function PseudoMesh:normal(k,v)
    if v then
        self.normals[k] = v
    else
        return self.normals[k]
    end
end

function PseudoMesh:texCoord(k,v)
    if v then
        self.texCoords[k] = v
    else
        return self.texCoords[k]
    end
end

function PseudoMesh:color(k,v)
    if v then
        self.colors[k] = v
    else
        return self.colors[k]
    end
end

function PseudoMesh:resize(k)
    self.size = k
end

function PseudoMesh:invertNormals()
    for k,v in ipairs(self.normals) do
        self.normals[k] = -v
    end
end

function PseudoMesh:toModel()
    local m = craft.model()
    local i = {}
    local n = #self.vertices
    for k=1,n,3 do
        if (self.vertices[k+1] - self.vertices[k]):cross(self.vertices[k+2] - self.vertices[k]):dot(self.normals[k+1] + self.normals[k+2] + self.normals[k]) < 0 then
            table.insert(i,k)
            table.insert(i,k+1)
            table.insert(i,k+2)
        else
            table.insert(i,k)
            table.insert(i,k+2)
            table.insert(i,k+1)
        end
    end
    m.positions = self.vertices
    m.normals = self.normals
    m.uvs = self.texCoords
    m.colors = self.colors
    m.indices = i
    return m
end

Use as:

m = PseudoMesh()
-- set up m as if it were a mesh
e = scene:entity()
e.model = m:toModel()

The invertNormals is because meshes would look the same from both sides but models are viewable from one only, so it may be that ones mesh is inside out. This flips it round.

As an example, I’ve adapted my old Roller Coaster code to Craft using this technique (@Simeon, @John would you consider replacing the old code with a Crafty update?).

@LoopSpace would there be value in having something like this built-in?

We would totally love to replace your Roller Coaster with a Craft-version! Share the zip here.

@Simeon or keep both, for reference?

Hi @LoopSpace

Thanks for the prompt on inversion of normals with meshes, I mentioned this in a recent dialogue with @dave1707 on a 3D demo with Craft. I noticed it whilst building a skybox.

With respect to your above code - is this intended to be within project conversion ? So you add the new tab with a few mods and it runs with the in-house conversion of the mesh to an in-house obj model? If so very neat !!!

Would this allow you to write to an .obj model file from a built up mesh?

On the classic Roller Coaster demo a revision as a mesh to update to the latest Codea spec (didn’t you write your own Vec3() code?) and the equivalent Craft demo would be great. Good examples are always welcome.

On 3D objects only having one face is that true, I always wondered if it was lack of light source inside models?

Craft is fantastic and will take me a little time to digest properly. But, meshes appear more flexible if a little bit harder to create.

Can meshes and models(Craft) be used together or do the Craft scenes use the mesh and overwrite it?

@LoopSpace ,

Just thinking - your code may allow us to build our own model conversion utils. There is already a Codea ply loader.

In regards to model loading / saving. I’m planning to add a loader that supports dozens of file formats for both saving and loading as well as bones, morph targets, etc.

@Simeon I think there could be value in having something like the PseudoMesh class available, but I’m not sure if it’s useful for it to be built in. I’m using it to convert old code over to Craft, but I’m not sure how much I’ll use it for new code. At the moment, I tend to think in terms of meshes so my instincts are all centred on that. As I get more used to Craft, I’ll probably be able to switch more easily between models and meshes.

I could put together a sample project with PseudoMesh as the non-Main tab, then it could be included via the project import feature.

Just in case my code above is of any use to you (or anyone else) …

I hereby place the above PseudoMesh code in the public domain to the extent governable by law. Explicitly, I place it under the CC0.

Note Regarding Attribution

Code that is placed in the public domain does not require attribution. However, if you have found it useful, the best way to say “Thank you” is to point others to it. So if you wish to acknowledge the source of the code, a link or bare URL text pointing to http://loopspace.mathforge.org would be nice.

I have a Craft version of the Roller Coaster almost ready - it currently pulls in some other code via cmodule so I just need to make it into a self-contained project.

@Bri_G Yes, the intention is to make it easy to convert from meshes to models. The above is not enough, but certainly helps me a lot. I have quite a lot of legacy code which I’d like to be able to use with Craft, for example my Mesh Extension Library adds lots of common shapes to the mesh object. Via the PseudoMesh, I can use it all with models as well without faffing around modifying the code.

Your mention of the Vec3 code takes me back! Story of my life as regards Codea:

  • October 2011 asking this question on the Apple Stack Exchange led me to Codify (as it was known then).

    I don’t remember what questions I emailed to Simeon but clearly the answer satisfied me that I’d be able to do what I wanted. Interesting that I was a bit wary about learning lua!

  • December 2011 Write a shape explorer including my own font renderer, UI, touch handler, 3D capability … then watch as over the years Codea implements just about all of those features itself.

    I like to kid myself that some of what I did with Codea in the early days inspired Simeon and co to implement stuff natively.

Codea is simply amazing and made programming actually joyful rather than just how to get some task done. From fonts to meshes to shaders and now craft, it’s been a bit of a roller coaster journey itself!

Sorry … I’m waxing lyrical. @Bri_G got me in a nostalgic mood. I’d better stop before I start going on about the early days with Bortels and Fred and Nat …

@LoopSpace
Nostalgia - I assume Nat is @Ignatz, there are many names you could add but I’m still using @Jmv38 code for spherical mesh generation, for my skybox. Codea is making great strides but some of the older coders code is still solid!!!

@Bri_G No, Nat is not Ignatz. Ignatz joined in 2013. I’m talking about 2011 - the really old days. Back then, we didn’t even have text, let alone meshes and shaders. Not even 3D - we had to write our own 3D-to-2D projection stuff back in those days. I regard Ignatz as a youngster in Codea terms!

But now you’ve really got me going. I’ve started on "Back in my day … "!

@LoopSpace your font renderer is exactly what made me implement native text rendering! I got to see how valuable it was and so felt motivated to include it. A lot of Codea’s features get implemented like that.

@LoopSpace in the comments of your sphere mesh, you hint that it could be possible (but not yet implemented) to smooth a faceted sphere. In order to keep the fps high i use a low number of facets. Would a smoothed sphere with low number of facets still be ‘fast’? If so, could you be persuaded to implement the smooth sphere option? Of course the correct texturing of the sphere should be retained. The in-built craft spheres do not seem to apply the texture correctly.

Hi @piinthesky,

Simply put: don’t believe everything you read! Looking through the code, that has been implemented (if you trace through the code at https://github.com/loopspace/Codea-Library-Graphics/blob/master/MeshExt.lua then line 1318 is the key line: f is what the faceted option has become and this defines the normal accordingly.

Faceting vs smoothing is all about the normals and has no effect on where the texture coordinates are (though it will affect the shading). I’m pretty sure that my texture coordinates are in the right place.

The default appears to be smooth, so if you have a sphere with my code it ought to be smooth. Let me know if not.

@LoopSpace you are right i should not believe what i read! i set faceted true and i can clearly see the difference. Interestingly with faceted true my texture misbehaves-it seems to rotate around the sphere as i view the sphere from different angles (works ok for the default faceted false).

i was hoping to get the ‘profile’ of the sphere more circular but i guess only increasing the number of facets can help with that (but too slow).

Setting up my 2000 craft/pseudomesh spheres take a lot of time. Can you think of a clever way to speed that up?

@piinthesky Are you trying to draw 2000 craft spheres with a texture on them. How long does it take you to draw your 2000 spheres.

@dave1707 drawing is not so bad (fps around 20-30ish with everything else going on). Setting up takes 14.2secs for 1800 craft spheres on ipad pro 12.9’’. It was slow enough i introduced a coroutine to display an animation during the setup (coroutine was disabled for the timing quoted above).

@piinthesky I’m working on something right now that draws 2000 level 2 craft spheres (smooth) with textures at 22 fps on my iPad Air. It takes approx 7 seconds to create them. I’m tidying up the code now. I’ll try it on my iPad Pro and see what the times are.

@piinthesky On my iPad Pro it takes 3 seconds to load and runs at 54 fps.

@dave1707 mine are craft models made via the pseudomesh with number=6. i am going to try copying the vertices and normals from the first sphere and use in the subsequent spheres. this should avoid a lot of calculations.

@piinthesky That’s how I got mine to run faster. I created all the sphere tables first and then use them when I created the other spheres. I looked at the code from the link above, but when I saw it was 1609 lines, I closed it. Too many lines for me to look at.

@dave1707 it worked, down from 14 secs to 1 sec -great!