Howdy, Stranger!

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

Competition! Using a new 3D shape library

IgnatzIgnatz Mod
in Competition Posts: 5,396

@LoopSpace is releasing a great code library of 3D shapes which you can use freely, to produce results like this

It includes some lighting effects, because 3D shapes need some texture or shading.

Here is the code, which produces the examples above when you run it. The code library has extra features you can discover by reading the descriptions of the functions. You don't need to know much about 3D to enjoy playing with the shapes.

LoopSpace and I are co-sponsoring a competition to see who can create the best results with it. You could make a
* "three in a row" jewel game,
* pretty patterns, or
* just create a 3D model using basic shapes

We'll offer a modest prize of $20 in iTunes credit, but the real reward is the title of ShapeMaster of Codea. ^:)^

You don't need to be a 3D expert to try it, because you can use the code provided, to play with the shapes. However, if you want to write your own code, you will need to know something about meshes and 3D.

Post any entries here, along with comments or queries.

Comments

  • Jmv38Jmv38 Mod
    Posts: 3,278

    @ignatz @loopspace fantastic job here! Thanks for sharing.
    ^:)^ ^:)^ ^:)^ ^:)^ ^:)^ ^:)^ ^:)^ ^:)^

  • beebee
    Posts: 381

    Great job! This should be included as an example for the next Codea update. Along with Cider. Please... :)

  • IgnatzIgnatz Mod
    Posts: 5,396

    The code is all by LoopSpace

  • i am having difficulties copying the code from the github, is there a recommended method?

  • IgnatzIgnatz Mod
    Posts: 5,396

    There is a "raw" button at top left of the code, this puts it on a page by itself, that may help.

  • Jmv38Jmv38 Mod
    Posts: 3,278

    This library is great.
    One thing i'll probably modify is to add the option to modify an existing shape rather than adding a new one.
    Is this something you've already planned to do, or would consider doing?

  • IgnatzIgnatz Mod
    Posts: 5,396

    @Jmv38 - actually, the library allows you to add a shape using an existing mesh and vertices, and specify which vertex to start from, so you can overwrite existing vertices with a new shape.

  • IgnatzIgnatz Mod
    Posts: 5,396

    This is what you can do with just a few cylinders (the ends can be different sizes).

  • Jmv38Jmv38 Mod
    Posts: 3,278

    @ignatz great! Good thing i've asked!

  • edited February 2015 Posts: 2,039

    This is very cool.

    Here's a quick little match game I made with it:

    -- Match
    
    displayMode(FULLSCREEN)
    function setup()
        wScale, hScale = WIDTH / 1024, HEIGHT / 768
    
        local light = vec3(0,1,0)
    
        objects = {}
    
        objects[1]=addBlock{center=vec3(0,0,0),size=15,color=color(0, 255, 0, 255),light=light,ambience=.5}
    
        objects[2]=addSphere{centre=vec3(0,0,0),size=10,texture="Cargo Bot:Starry Background",light=light,ambience=.5} 
    
        objects[3]=addSphereSegment{centre=vec3(0,0,0),size=10,color=color(0, 192, 255, 255),
            startLongitude=30,deltaLongitude=100,light=light,ambience=.5} 
    
        objects[4]=addJewel{centre=vec3(0,0,0),size=15,sides=10,color=color(203, 78, 188, 255),light=light,ambience=.5}
    
        objects[5]=addCylinder{centre=vec3(0,0,0),height=20,radius=7.5,color=color(107, 59, 108, 255),
                light=light,ambience=.5,faceted=false,size=20}
    
    
    
        shapes = {}
        local gap = 50
        for i = 1, 16 do
            shapes[i] = {}
    
            shapes[i].id = math.random(1, #objects)
    
            local a, b = math.modf((i-1)/4)
    
            shapes[i].pos = vec3(a-1.5, b*4-1.5, 0) * gap
            shapes[i].rpos = vec2(WIDTH / 2 + (a-1.5)*185, HEIGHT / 2 + (b-0.375)*750)
            shapes[i].row, shapes[i].col = b * 4, a
            shapes[i].scale = 1
        end
    
        selected = {}
    
        rot = vec3(0,0,0)  
        deltaRot = vec3(.3,.3,.3)*4
        score = 0
        lost = false
    end
    
    function orientationChanged()
        wScale, hScale = WIDTH / 1024, HEIGHT / 768
    
        if shapes then
            for i = 1, 16 do
                local a, b = math.modf((i-1)/4)
                shapes[i].rpos = vec2(WIDTH / 2 + (a-1.5)*185, HEIGHT / 2 + (b-0.375)*750)
            end
        end
    end
    
    function resetBoard()
        for i, s in ipairs(shapes) do
            s.id = math.random(1, #objects)
            tween(0.5, s, { scale = 1 })
        end
        score = 0
        lost = false
    
        ANIMATING = true
        tween.delay(0.6, function() 
            ANIMATING = false
        end)
    end
    
    function draw()
        background(255, 255, 255, 255)
    
        textAlign(CENTER)
        if not lost then
            perspective()  --puts Codea in 3D mode
            camera(0,0,250 / wScale,0,0,0) --camera is 250 pixels back from 0, looking at (0,0,0)
            for _,s in pairs(shapes) do
                pushMatrix()
                translate(s.pos.x,s.pos.y,s.pos.z)
                rotate(rot.x,1,0,0) rotate(rot.y,0,1,0) rotate(rot.z,0,0,1)
                scale(s.scale, s.scale, s.scale)
                --use this next line with all your objects that use lighting
    
                if s.id ~= 0 then
                    local ob = objects[s.id]
                    if ob.shader then ob.shader.invModel = modelMatrix():inverse():transpose() end
                    ob:draw()
                end
    
                popMatrix()  
            end
    
            viewMatrix( matrix() )
            ortho()
    
            for i, s in ipairs(selected) do
                fill(184, 68, 68, 255) stroke(184,68,68,200) strokeWidth(25)
    
                if i < #selected then
                    line(s.pos.x, s.pos.y, selected[i+1].pos.x, selected[i+1].pos.y)
                end
    
                noStroke()
                ellipse(s.pos.x, s.pos.y, 35)
            end
    
            fill(0, 127) fontSize(WIDTH / 4)
            text(math.floor(score+0.5), WIDTH / 2, HEIGHT / 2)
        else
            fontSize(WIDTH / 8)
            text("game over\ntap to restart", WIDTH / 2, HEIGHT / 2)
        end
    
    
        rot=rot+deltaRot --change rotation
    end
    
    function checkMoves()
        local match = false
        for i, s in ipairs(shapes) do
            local up, right = shapes[i+1], shapes[i+4]
    
            if (s.row < 3 and up ~= nil and s.id == up.id) or (right ~= nil and s.id == right.id) then
                match = true
                break
            end
        end
    
        if not match then
            sound("Game Sounds One:Pac Death 2")
            ANIMATING = true
            for i, s in ipairs(shapes) do
                tween(0.25, s, { scale = 0 })
            end
            tween.delay(0.6, function() 
                ANIMATING = false
                lost = true
            end)
        end
    end
    
    function touched(t)
        if ANIMATING then return end
        if lost and t.state == ENDED then
            resetBoard()
        elseif lost then
            return
        end
    
        if t.state == BEGAN or t.state == MOVING then
            for i,s in ipairs(shapes) do
                if vec2(t.x, t.y):dist(s.rpos) <= 75 and not s.selected then
                    local rdif,cdif = 2,2
                    if #selected > 0 then
                        rdif, cdif = math.abs(s.row - selected[#selected].row), math.abs(s.col - selected[#selected].col)
                    end
                    if #selected == 0 or (s.id == selected[1].id and rdif + cdif <= 1) then
                        table.insert(selected, { pos = s.rpos, id = s.id, row = s.row, col = s.col })
                        s.selected = true
                        sound("Game Sounds One:Block 1")
                    end
                end
            end
        elseif t.state == ENDED then
            if #selected > 1 then
                sound("Game Sounds One:Block 3")
                tween(0.5, _G, { score = score + ((2^(#selected)) / 2) })
            end
    
            for i, s in ipairs(shapes) do
                s.switch = false
                s.d_id = i
                if s.selected and #selected > 1 then
                    s.id = 0
                    s.switch = true
                end
                s.o_id = s.id
                s.selected = false
            end
    
            if #selected > 1 then
                for i = 1, #shapes, 1 do
                    if shapes[i].row > 0 then
                        for j = 1, shapes[i].row do
                            if shapes[i-j].switch then
                                local ors = shapes[i-j].switch
                                shapes[i-j].switch = shapes[i-j+1].switch
                                shapes[i-j+1].switch = ors
    
                                local op = shapes[i-j].d_id
                                shapes[i-j].d_id = shapes[i-j+1].d_id
                                shapes[i-j+1].d_id = op
                            end
                        end
                    end
                end
    
                -- animate and replace empty spots
                ANIMATING = true
                for i, s in ipairs(shapes) do
                    local dest = s.pos
                    local og_id, og = s.d_id, shapes[s.d_id].pos
                    tween(0.5, shapes[s.d_id], { pos = dest }, nil, function()
                        shapes[og_id].pos = og
                        s.id = shapes[s.d_id].o_id
    
                        if s.id == 0 then
                            s.id = math.random(1, #objects)
                            s.scale = 1
                        end
    
                    end)
                end
                tween.delay(0.6, function()
                    ANIMATING = false
                    checkMoves()
                end)
            end
    
            selected = {}
        end
    end
    

    EDIT: added animated movement of gems after scoring, changed scoring to be exponential (2^number_of_gems_selected)

    EDIT 2: sound effects, losing, no diagonal matches (is that the correct rule?)

    EDIT 3: portrait mode support

  • Just a short comment to say that although the core code is mine, it wouldn't have seen the light of day if not for Ignatz' persuasion and suggestions for improvements so Ignatz deserves lots of credit too.

    Also, the core code is already in an example project with Codea: the Roller Coaster. But it's not as user-friendly so maybe I should rewrite the 'Coaster to use this version.

    Now to download and try JakAttak's game ...

  • edited February 2015 Posts: 942

    Hi @LoopSpace, @ignatz,

    First a big thank you for the excellent 3D package that you provided for us. I am still digesting it, trying to figure out how it works etc.

    Initially I tried to compress it - strip out the comments and move functions, libs etc to separate tabs - shader and comments no problem but moving (or copying) object functions e.g. function addJewel(t) onto a separate tab resulted in an error 'attempt to call global __initmesh (a nil value)'.

    Is this down to tab order?

    My objective is to have the libraries in a separate 'applette' as a library which I can call via dependencies. Can you help here?

    Other thing - you declare local __initmesh etc locally outside any function - are there any requirements positionally for these declarations and what influence does that have on moving functions to tabs?

    Now - I'm going to try out JakAttack's game too ....

    Thanks again,

    Bri_G

    :-?

  • Posts: 942

    HI @JakAttack,

    Neat game - very cool. How does it score and what are the objectives?
    Might have guessed I haven't read your code yet - back to the game.

    Thanks,

    Bri_G

    :D

  • hey everyone, I got bored and decided to make a little something with this :)

    You get a cube, you can rotate it with swipes, press a single time on the screen, and the face which has the most 'visible area' on the 2D screen, will get 'loaded', some of the faces contain more shapes

    There's a text parameter which you need to type something in, and then press the 'check' button, if you have the right answer, the output will tell you

    I didn't have an aweful lot of time, so returning to the cube view while viewing a face, is also done with a parameter

    It's just a little thing, it's my first 3D experience and I'd really like to thank @loopspace and @ignatz for making this something that's fairly easy to understand, even if you haven't done 3D before :)

    function setup()
    
        startShapes()
    
        CUBE = 0
        GREEN = 1
        YELLOW = 2
        CYAN = 3
        BLUE = 4
        RED = 5
        MAGENTA = 6
    
        state = CUBE
    
        timer = false
        RGB = ''
        pos = {x = 0, y = 0}
        statepos = {x = 0, y = 0}
        parameter.integer('state', 0, 6, 0)
        parameter.action('Back to cube', function() state = CUBE end)
        parameter.text('RGB', '')
        parameter.action('check answer', check)
    
        tt = false
    
        time = 0
    end
    
    function draw()
    
        pushMatrix()
        background(127, 127, 127, 255)
        perspective(45)
        camera(0,0,400,0,0,0, 0,1,0)
        if state == CUBE then
            rotate(pos.x, 1, 0, 0)  rotate(pos.y, 0, 1, 0)
            test:draw()
            test2:draw()
            test3:draw()
        else
            if state == GREEN then
                rotate(45, 1, 0, 0)
                test2:draw()
                test:draw()
                time = time + 1
                if time%60 == 0 and time <= 600 then
                    tt = not(tt)
                end
                if time >= 1000 then
                    time = 0
                end
                if tt then
                    testShape2:draw()
                else
                    testShape:draw()
                end
            end
            if state == YELLOW then
                rotate(270, 1, 0, 0)
                test2:draw()
            end
            if state == CYAN then
                test:draw()
                rotate(statepos.x, 1, 0, 0) rotate(statepos.y, 0, 1, 0)
            end
            if state == BLUE then
                rotate(270, 0, 1, 0) rotate(45, 0, 0, 1)
                test3:draw()
                test2:draw()
                jewel:draw()
                minus:draw()
                cyl:draw()
            end
            if state == RED then
                rotate(180, 0, 1, 0)
                test:draw()
                rotate(statepos.x, 1, 0, 0) rotate(statepos.y, 0, 1, 0)
                for i = 0, #jewels do
                    jewels[i]:draw()
                end
            end
            if state == MAGENTA then
                rotate(90, 0, 1, 0)
                test3:draw()
                rotate(statepos.x, 1, 0, 0) rotate(statepos.y, 0, 1, 0)
            end
        end
        popMatrix()
    
    end
    
    function touched(t)
        if state == CUBE then
            if math.abs(t.deltaY) > 5 then pos.x = pos.x - t.deltaY/3 reset() end
            if math.abs(t.deltaX) > 5 then pos.y = pos.y + t.deltaX/3 reset() end
            if pos.x >= 360 then pos.x = pos.x - 360 end
            if pos.x < 0 then pos.x = pos.x + 360 end
            if pos.y >= 360 then pos.y = pos.y - 360 end
            if pos.y < 0 then pos.y = pos.y + 360 end
    
            if t.state == BEGAN then
                timer = true
            end
    
            if t.state == ENDED and timer then
                reset()
                start()
            end
        else
            if math.abs(t.deltaY) > 5 then statepos.x = statepos.x - t.deltaY/3 reset() end
            if math.abs(t.deltaX) > 5 then statepos.y = statepos.y + t.deltaX/3 reset() end
        end
    end
    
    function reset()
        timer = false
    end
    
    function start()
        if pos.x > 45 and pos.x < 135 then
            state = GREEN
        elseif pos.x > 225 and pos.x < 315 then
            state = YELLOW
        else
            if pos.y > 45 and pos.y < 135 then
                if pos.x > 135 and pos.x < 225 then
                    state = BLUE
                else
                    state = MAGENTA
                end
            elseif pos.y > 315  or pos.y < 45 then
                if pos.x > 135 and pos.x < 225 then
                    state = RED
                else
                    state = CYAN
                end
            elseif pos.y < 315 and pos.y > 225 then
                if pos.x > 135 and pos.x < 225 then
                    state = MAGENTA
                else
                    state = BLUE
                end
            else
                if pos.x > 135 and pos.x < 225 then
                    state = CYAN
                else
                    state = RED
                end
            end
        end
    end
    function startShapes()
    
        -- yellow, jewels
        jewels = {}
        jewels[0] = addJewel{size = 20, color = color(255, 0, 0, 255), origin = vec3(100,0,0), light = light, ambience = 0.75}
        jewels[1] = addJewel{size = 20, color = color(255, 255, 0, 255), origin = vec3(-100,0,0), light = light, ambience = 0.75}
        jewels[2] = addJewel{size = 20, color = color(255, 0, 255, 255), origin = vec3(0,100,0), light = light, ambience = 0.75}
        jewels[3] = addJewel{size = 20, color = color(0, 255, 0, 255), origin = vec3(0,-100,0), light = light, ambience = 0.75}
        jewels[4] = addJewel{size = 20, color = color(0, 255, 255, 255), origin = vec3(0,0,100), light = light, ambience = 0.75}
        jewels[5] = addJewel{size = 20, color = color(0, 0, 255, 255), origin = vec3(0,0,-100), light = light, ambience = 0.75}
    
    
        --green, tests
        testShape = addCylinder{center = vec3(0,50,0), height = 20, radius = 30, color = color(255, 113, 0, 255), light = light, ambient = 0,8, faceted = false, size = 20}
        testShape2 = addCylinder{center = vec3(0,45,0), height = 20, radius = 30, color = color(255, 113, 0, 255), light = light, ambient = 0,8, faceted = false, size = 20}
    
        -- blue, sum/...
        jewel = addJewel{size = 10, axis = vec3(1,0,0), color = color(255, 0, 0, 255), origin = vec3(60,0,30), light = light, ambience = 0.75}
        minus = addBlock{center = vec3(60,0,0), width = 5, height = 5, depth = 20, color = color(255, 255, 255, 255), light = light, ambience = 0.9}
        cyl = addCylinder{center = vec3(50,0,-30), height = 20, radius = 10, color = color(0, 255, 0, 255), light = light, ambient = 0,8, faceted = false, size = 20, axis = vec3(1,0,0)}
    
    
    
        --cube
        local colors = {
        color(255, 0, 0, 255),
        color(255, 0, 0, 255),
        color(255, 0, 0, 255),
        color(255, 0, 0, 255),
        color(0, 255, 255, 255),
        color(0, 255, 255, 255),
        color(0, 255, 255, 255),
        color(0, 255, 255, 255)
        }
        local BFaces = {
            {1,2,3,4},
            {5,7,6,8},
            {1,1,1,1},
            {1,1,1,1},
            {1,1,1,1},
            {1,1,1,1}
        }
        test = addBlock{center = vec3(0,0,0), size = 100, color = colors, faces = BFaces}
    
        local colors = {
        color(255, 255, 0, 255),
        color(255, 255, 0, 255),
        color(0, 255, 0, 255),
        color(0, 255, 0, 255),
        color(255, 255, 0, 255),
        color(255, 255, 0, 255),
        color(0, 255, 0, 255),
        color(0, 255, 0, 255)
        }
        local BFaces = {
            {1,1,1,1},
            {1,1,1,1},
            {1,5,2,6},
            {3,4,7,8},
            {1,1,1,1},
            {1,1,1,1}
        }
        test2 = addBlock{center = vec3(0,0,0), size = 100, color = colors, faces = BFaces}
    
        local colors = {
        color(255, 0, 255, 255),
        color(0, 0, 255, 255),
        color(255, 0, 255, 255),
        color(0, 0, 255, 255),
        color(255, 0, 255, 255),
        color(0, 0, 255, 255),
        color(255, 0, 255, 255),
        color(0, 0, 255, 255)
        }
        local BFaces = {
            {1,1,1,1},
            {1,1,1,1},
            {1,1,1,1},
            {1,1,1,1},
            {2,6,4,8},
            {1,3,5,7}
        }
        test3 = addBlock{center = vec3(0,0,0), size = 100, color = colors, faces = BFaces}
    end
    
    function check()
        if RGB == '651' then print("VICTORY!!!") else print("NOPE") end
    end
    
  • Posts: 2,039

    @Bri_G, thanks. Currently there is no objective but I may expand it. Scoring is exponential so two in a row is worth 2 points and then every addition block doubles the value

  • @Bri_G I'm not sure what you are trying to accomplish by moving code. The logic of the naming of the functions is as follows: any function which starts __ is an internal function and not exposed to global use. All other functions are in the global namespace.

    You can't really split the code up, though, because every global function uses several internal functions so they need access to those internal functions and so need to be in the same chunk. There are one or two exceptions, such as the lighting function (though note that __initmesh uses it), but the point of the internal functions was to avoid repeating stuff common to several shapes and so it needs to be available to all of those shapes.

    Taking out comments is, of course, always going to be fine.

    What are you trying to split up? If you put that library in its own tab (without the draw and setup functions) then it ought to work just fine. If you want to include it as a dependency, put the draw and setup functions in Main and everything else in a single separate tab.

  • Posts: 942

    HI @LoopSpace,

    Thanks for the feedback - told me what I needed to know. My main interest is in how you've approached this and if I can make it modular so that I can reduce the overall code size. Need to digest your code a bit longer.

    Thanks again for putting this together - smart piece of work.

    Bri_G

    :D

  • @Bri_G I've never quite understood this desire to have minimal size code. Ignatz keeps going on at me to reduce the code as well.

    You could remove shapes that you don't need, I suppose. But if you put it in a library and include it as a dependency, then what does the size matter?

    If you did want to remove a few shapes, I could do a map of the dependencies between the functions if you like. Then you could easily see which ones are needed by which shapes.

  • @LoopSpace Very cool, well done.

  • edited February 2015 Posts: 486

    @LoopSpace Lua can be very verbose and this can lead to very long sized code, which essentially does not much (other than stealing space and file size). Therefor I try to minimize code whenever I can (but only if it makes sense). As a result have advantages and disadvantages alike:

    • + short, precise code (not an essay)
    • + cleaner syntax/structure
    • + smaller file size
    • + easier to maintain things (search/change/override)
    • + satisfaction for being a 'genius', because its cool to find clever solutions/notations :D
    • - code may get (unnecessary) complicated

    Advantages are predominant, but you have to be kind of balanced. As rules of thumb I try:

    • to make thing work with as less code as possible.
    • to use 'local' variables as much as possible.
    • describe complicated processes with easy to understand algorithms.

    And while all this are nice coding-style optimizations, is actually comes down to one thing: 'If it looks right - it is right!' ;)

  • IgnatzIgnatz Mod
    Posts: 5,396

    I built a nice 3D Star Wars star destroyer with the help of this library. It did the tricky rounded bits like the engines and radar domes.

  • a stardestroyer. Perhaps the coolest 3d model I've seen on the forums yet. Wow.

Sign In or Register to comment.