Howdy, Stranger!

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

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.

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?).

Comments

  • SimeonSimeon Admin Mod
    Posts: 4,624

    @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.

  • em2em2
    Posts: 194

    @Simeon or keep both, for reference?

  • edited October 2017 Posts: 1,014

    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?

  • Posts: 1,014

    @LoopSpace ,

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

  • JohnJohn Admin Mod
    Posts: 522
    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.
  • Posts: 422

    @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 ...

  • Posts: 1,014

    @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!!!

  • Posts: 422

    @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 ... "!

  • SimeonSimeon Admin Mod
    Posts: 4,624

    @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.

  • Posts: 337

    @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.

  • Posts: 422

    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.

  • Posts: 337

    @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?

  • dave1707dave1707 Mod
    edited December 2 Posts: 7,155

    @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.

  • Posts: 337

    @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).

  • dave1707dave1707 Mod
    edited December 3 Posts: 7,155

    @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.

  • dave1707dave1707 Mod
    Posts: 7,155

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

  • Posts: 337

    @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.

  • dave1707dave1707 Mod
    Posts: 7,155

    @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.

  • Posts: 337

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

  • Posts: 422

    @piinthesky Are you creating a mesh and converting it to a model for every single sphere? I'm pretty sure that you don't need to do that. I'll need to experiment, but I think that once the model is created then you can use it for as many entities as you like.

  • Posts: 337

    @LoopSpace ahhh, yes i was stupid! Just using the same model for all sphere.entities works and is even faster.

  • Posts: 422

    @piinthesky That's good to know. I still don't feel that I have completely figured out the basics of craft.

    Every time I think I've understood what's going on in Codea, they add new features! "Fortunately", none of my iPads will do Face AR so I can't play with that.

  • JohnJohn Admin Mod
    Posts: 522

    I was going to mention it but it looks like you guys already figured it out. You can use the same model for as many entities as you like :smile:

    @LoopSpace what stuff would you say you find the most difficult to grasp with Craft?

  • dave1707dave1707 Mod
    Posts: 7,155

    @John if you’re asking questions about Craft, would you happen to have something like a flowchart that shows how Craft is layed out. For instance you start with craft.scene and from there you have aaa bbb ccc . From aaa bbb ccc you have other things etc. I don’t have a good understanding of how Craft is layed out.

  • Posts: 1,014
    @John - pardon my ignorance but is there a simple way of scaling a model (around its core central point) so individual spheres as mentioned above could be modified quickly?

    On the same theme is there a simple way of moving a model by reference to its core central point?

    I'm about to play with a few model ideas and you could save me a little time.
  • JohnJohn Admin Mod
    Posts: 522

    @dave1707 That's a good idea, I'll see what I can do

    @Bri_G, I assume you mean the pivot point when you say core central point. All models pivot around zero, which for most things is right in the middle (cubes, sphere, in particular).
    You can use myEntity.scale = vec3(0.5, 0.5, 0.5) to scale something down to 50% size. Each entry in vec3 is a different axis, so if you want to scale non-uniformly then you can use different amounts. For positioning you can use myEntity.position and rotation using myEntity.rotation.
    If you want to modify where the centre of a model is there are a few options:

    • modify where the centre is by changing the model vertices
    • manually adjust the position you are setting
    • use an empty entity as a parent and adjust the position of the child entity

    The 3rd option is nice in that scaling the parent will scale the child as if you had adjusted the model's pivot point.

    Here’s an example of scaling and positioning some spheres:

    -- Scale Test
    
    -- Use this function to perform your initial setup
    function setup()
        print("Hello Scaling!")
    
        scene = craft.scene()
        -- Move the camera back and rotate 180 degrees to face the center of the world
        scene.camera.z = 15
        scene.camera.rotation = quat.eulerAngles(0,180,0)
    
        sphere = craft.model("Primitives:Sphere")
    
        -- Models come with a default material loaded from their file (usually Materials:Specular)
        sphere:getMaterial().diffuse = color(213, 175, 12, 255)
    
        spheres = {}
    
        -- Create 10 entities that all use the same sphere model
        for i = 1, 10 do 
            local s = scene:entity()
            s.model = sphere
            -- Adjust position to keep them spaced evenly relative to the center of the world
            s.position = vec3((-10 * 0.5 + i), 0, 0)
            table.insert(spheres, s)
        end
    
        -- I forgot to document this but models have a bounds object
        -- Bounds includes an offset and size, min and max location 
        -- which is in local space relative to the vertices in the model
        print("Bounds (offset, size): ", sphere.bounds.offset, sphere.bounds.size)
    
    end
    
    -- This function gets called once every frame
    function draw()
    
        -- Update sphere scale based on position and time using sine wave
        local v = vec3(1,1,1)
        for i = 1, #spheres do 
            spheres[i].scale = v * math.abs(math.sin(i*0.25 + ElapsedTime) * 0.5)
        end
    
        scene:update(DeltaTime)
        scene:draw()    
    end
    
  • Posts: 337

    @John while we are on the subject of scaling! I reported previously there is an bug with scaling when in AR mode-if the scaling is small all hell breaks lose! I made a video to convince you....

  • JohnJohn Admin Mod
    Posts: 522

    @piinthesky How are you doing the scaling, can you show me the code?

  • Posts: 1,014
    @John - thanks for the feedback and example, I want to move models in a figure of 8 path away from the screen then back rotating through 360 as it moves. Should be able to figure it out now. Thanks.
  • Posts: 337

    @John here is the AR example i modified for scaling. Myscene is the parent of each block. Myscene is then scaled in the parameter slider.....

    -----------------------------------------
    -- AR
    -- Written by John Millard
    -----------------------------------------
    -- Description:
    -- A basic Augmented Reality demo using ARKit internally.
    -- Use scene.ar to setup and pause AR* mode.
    --
    -- * Please note that only devices with an A9 processor or above support AR.
    --   This is an iOS 11 only feature.
    -----------------------------------------
    
    
    displayMode(FULLSCREEN)
    
    function setup()
        -- Create a new craft scene
        scene = craft.scene()
        scene.sun:get(craft.light).intensity = 0.7
        myscene=scene:entity()
        parameter.number("sceneScale",0,2,1, function(c) myscene.scale=vec3(1,1,1)*c end)
    
        if craft.ar.isSupported then
            -- Enable AR session
            scene.ar:run()
    
            -- Keep a list of detected planes
            planes = {}
    
            -- Option to turn plane detection on and off
            parameter.boolean("PlaneDetection", true, function(b)
                scene.ar.planeDetection = b
            end)
    
            -- Option to draw any detected planes using camera rendering mask
            parameter.boolean("DrawPlanes", true, function(b)
                local c = scene.camera:get(craft.camera)
                if b then
                    c.mask = ~0
                else
                    c.mask = 1
                end
            end)
    
            parameter.boolean("DrawPointCloud", true)
    
            local grid = readImage("Project:GridWhite")
    
            scene.ar.didAddAnchors = function(anchors)
                for k,v in pairs(anchors) do
                    local p = scene:entity():add(Plane, v, grid)
                    planes[v.identifier] = p
                end
            end
    
            scene.ar.didUpdateAnchors = function(anchors)
                for k,v in pairs(anchors) do
                    local p = planes[v.identifier]
                    p:updateWithAnchor(v)
                end
            end
    
            scene.ar.didRemoveAnchors = function(anchors)
                for k,v in pairs(anchors) do
                    local p = planes[v.identifier]
                    p.entity:destroy()
                    planes[v.identifier] = nil
                end
            end   
    
            trackingState =
            {
                [AR_NOT_AVAILABLE] = "Not Available",
                [AR_LIMITED] = "Limited",
                [AR_NORMAL] = "Normal"
            }
    
            cross = image(16,16)
            setContext(cross)
            pushStyle()
            fill(255, 198, 0, 255)
            noStroke()
            rectMode(CENTER)
            rect(cross.width/2, cross.height/2, 3, cross.height)
            rect(cross.width/2, cross.height/2, cross.width, 3)
            popStyle()
            setContext()
    
        end
        parameter.action("ar pause", function()  scene.ar:pause() end)
        parameter.action("ar run", function()  scene.ar:run() end)
    end
    
    function update(dt)
        scene:update(dt)   
    end
    
    -- Called automatically by codea 
    function draw()
        update(DeltaTime)
    
        -- Draw the scene
        scene:draw()    
    
        local status = nil
        if craft.ar.isSupported then
            status = trackingState[scene.ar.trackingState]
    
            if DrawPointCloud then
                local c = scene.camera:get(craft.camera)
                for k,v in pairs(scene.ar.points) do
                    local p = c:worldToScreen(v)
                    sprite(cross, p.x, p.y)
                end
            end
        else
            status = "AR Not Supported"
        end
        fill(255, 255, 255, 255)
        text(status, WIDTH/2, HEIGHT - 50)
    
    end
    
    function touched(touch)
        if craft.ar.isSupported and touch.state == BEGAN then
            local results = scene.ar:hitTest(
                vec2(touch.x, touch.y),
                AR_EXISTING_PLANE_CLIPPED)
    
            for k,v in pairs(results) do
                local e = scene:entity()
                e.parent=myscene
                local cube = e:add(Cube, v.position + vec3(0,0.5,0), 0.1)
                break
            end
        end
    end
    
  • Posts: 337

    @john you could also add an ar.pause() and ar.run() to the above to see the problem with restarting after a pause.

Sign In or Register to comment.